Skip to content

Commit 4183e4f

Browse files
Darrick J. Wongdchinner
authored andcommitted
xfs: share xattr name and value buffers when logging xattr updates
While running xfs/297 and generic/642, I noticed a crash in xfs_attri_item_relog when it tries to copy the attr name to the new xattri log item. I think what happened here was that we called ->iop_commit on the old attri item (which nulls out the pointers) as part of a log force at the same time that a chained attr operation was ongoing. The system was busy enough that at some later point, the defer ops operation decided it was necessary to relog the attri log item, but as we've detached the name buffer from the old attri log item, we can't copy it to the new one, and kaboom. I think there's a broader refcounting problem with LARP mode -- the setxattr code can return to userspace before the CIL actually formats and commits the log item, which results in a UAF bug. Therefore, the xattr log item needs to be able to retain a reference to the name and value buffers until the log items have completely cleared the log. Furthermore, each time we create an intent log item, we allocate new memory and (re)copy the contents; sharing here would be very useful. Solve the UAF and the unnecessary memory allocations by having the log code create a single refcounted buffer to contain the name and value contents. This buffer can be passed from old to new during a relog operation, and the logging code can (optionally) attach it to the xfs_attr_item for reuse when LARP mode is enabled. This also fixes a problem where the xfs_attri_log_item objects weren't being freed back to the same cache where they came from. Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: Dave Chinner <[email protected]> Signed-off-by: Dave Chinner <[email protected]>
1 parent 22a68ba commit 4183e4f

File tree

5 files changed

+223
-134
lines changed

5 files changed

+223
-134
lines changed

fs/xfs/libxfs/xfs_attr.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ enum xfs_delattr_state {
502502
{ XFS_DAS_NODE_REMOVE_ATTR, "XFS_DAS_NODE_REMOVE_ATTR" }, \
503503
{ XFS_DAS_DONE, "XFS_DAS_DONE" }
504504

505+
struct xfs_attri_log_nameval;
506+
505507
/*
506508
* Context used for keeping track of delayed attribute operations
507509
*/
@@ -517,6 +519,12 @@ struct xfs_attr_intent {
517519

518520
struct xfs_da_args *xattri_da_args;
519521

522+
/*
523+
* Shared buffer containing the attr name and value so that the logging
524+
* code can share large memory buffers between log items.
525+
*/
526+
struct xfs_attri_log_nameval *xattri_nameval;
527+
520528
/*
521529
* Used by xfs_attr_set to hold a leaf buffer across a transaction roll
522530
*/

fs/xfs/libxfs/xfs_defer.c

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,35 +191,56 @@ static const struct xfs_defer_op_type *defer_op_types[] = {
191191
[XFS_DEFER_OPS_TYPE_ATTR] = &xfs_attr_defer_type,
192192
};
193193

194-
static bool
194+
/*
195+
* Ensure there's a log intent item associated with this deferred work item if
196+
* the operation must be restarted on crash. Returns 1 if there's a log item;
197+
* 0 if there isn't; or a negative errno.
198+
*/
199+
static int
195200
xfs_defer_create_intent(
196201
struct xfs_trans *tp,
197202
struct xfs_defer_pending *dfp,
198203
bool sort)
199204
{
200205
const struct xfs_defer_op_type *ops = defer_op_types[dfp->dfp_type];
206+
struct xfs_log_item *lip;
201207

202-
if (!dfp->dfp_intent)
203-
dfp->dfp_intent = ops->create_intent(tp, &dfp->dfp_work,
204-
dfp->dfp_count, sort);
205-
return dfp->dfp_intent != NULL;
208+
if (dfp->dfp_intent)
209+
return 1;
210+
211+
lip = ops->create_intent(tp, &dfp->dfp_work, dfp->dfp_count, sort);
212+
if (!lip)
213+
return 0;
214+
if (IS_ERR(lip))
215+
return PTR_ERR(lip);
216+
217+
dfp->dfp_intent = lip;
218+
return 1;
206219
}
207220

208221
/*
209222
* For each pending item in the intake list, log its intent item and the
210223
* associated extents, then add the entire intake list to the end of
211224
* the pending list.
225+
*
226+
* Returns 1 if at least one log item was associated with the deferred work;
227+
* 0 if there are no log items; or a negative errno.
212228
*/
213-
static bool
229+
static int
214230
xfs_defer_create_intents(
215231
struct xfs_trans *tp)
216232
{
217233
struct xfs_defer_pending *dfp;
218-
bool ret = false;
234+
int ret = 0;
219235

220236
list_for_each_entry(dfp, &tp->t_dfops, dfp_list) {
237+
int ret2;
238+
221239
trace_xfs_defer_create_intent(tp->t_mountp, dfp);
222-
ret |= xfs_defer_create_intent(tp, dfp, true);
240+
ret2 = xfs_defer_create_intent(tp, dfp, true);
241+
if (ret2 < 0)
242+
return ret2;
243+
ret |= ret2;
223244
}
224245
return ret;
225246
}
@@ -457,6 +478,8 @@ xfs_defer_finish_one(
457478
dfp->dfp_count--;
458479
error = ops->finish_item(tp, dfp->dfp_done, li, &state);
459480
if (error == -EAGAIN) {
481+
int ret;
482+
460483
/*
461484
* Caller wants a fresh transaction; put the work item
462485
* back on the list and log a new log intent item to
@@ -467,7 +490,9 @@ xfs_defer_finish_one(
467490
dfp->dfp_count++;
468491
dfp->dfp_done = NULL;
469492
dfp->dfp_intent = NULL;
470-
xfs_defer_create_intent(tp, dfp, false);
493+
ret = xfs_defer_create_intent(tp, dfp, false);
494+
if (ret < 0)
495+
error = ret;
471496
}
472497

473498
if (error)
@@ -514,10 +539,14 @@ xfs_defer_finish_noroll(
514539
* of time that any one intent item can stick around in memory,
515540
* pinning the log tail.
516541
*/
517-
bool has_intents = xfs_defer_create_intents(*tp);
542+
int has_intents = xfs_defer_create_intents(*tp);
518543

519544
list_splice_init(&(*tp)->t_dfops, &dop_pending);
520545

546+
if (has_intents < 0) {
547+
error = has_intents;
548+
goto out_shutdown;
549+
}
521550
if (has_intents || dfp) {
522551
error = xfs_defer_trans_roll(tp);
523552
if (error)
@@ -676,13 +705,15 @@ xfs_defer_ops_capture(
676705
if (list_empty(&tp->t_dfops))
677706
return NULL;
678707

708+
error = xfs_defer_create_intents(tp);
709+
if (error < 0)
710+
return ERR_PTR(error);
711+
679712
/* Create an object to capture the defer ops. */
680713
dfc = kmem_zalloc(sizeof(*dfc), KM_NOFS);
681714
INIT_LIST_HEAD(&dfc->dfc_list);
682715
INIT_LIST_HEAD(&dfc->dfc_dfops);
683716

684-
xfs_defer_create_intents(tp);
685-
686717
/* Move the dfops chain and transaction state to the capture struct. */
687718
list_splice_init(&tp->t_dfops, &dfc->dfc_dfops);
688719
dfc->dfc_tpflags = tp->t_flags & XFS_TRANS_LOWMODE;
@@ -759,6 +790,10 @@ xfs_defer_ops_capture_and_commit(
759790

760791
/* If we don't capture anything, commit transaction and exit. */
761792
dfc = xfs_defer_ops_capture(tp);
793+
if (IS_ERR(dfc)) {
794+
xfs_trans_cancel(tp);
795+
return PTR_ERR(dfc);
796+
}
762797
if (!dfc)
763798
return xfs_trans_commit(tp);
764799

0 commit comments

Comments
 (0)