Skip to content

Commit f4288f0

Browse files
author
Darrick J. Wong
committed
xfs: fix TOCTOU race involving the new logged xattrs control knob
I found a race involving the larp control knob, aka the debugging knob that lets developers enable logging of extended attribute updates: Thread 1 Thread 2 echo 0 > /sys/fs/xfs/debug/larp setxattr(REPLACE) xfs_has_larp (returns false) xfs_attr_set echo 1 > /sys/fs/xfs/debug/larp xfs_attr_defer_replace xfs_attr_init_replace_state xfs_has_larp (returns true) xfs_attr_init_remove_state <oops, wrong DAS state!> This isn't a particularly severe problem right now because xattr logging is only enabled when CONFIG_XFS_DEBUG=y, and developers *should* know what they're doing. However, the eventual intent is that callers should be able to ask for the assistance of the log in persisting xattr updates. This capability might not be required for /all/ callers, which means that dynamic control must work correctly. Once an xattr update has decided whether or not to use logged xattrs, it needs to stay in that mode until the end of the operation regardless of what subsequent parallel operations might do. Therefore, it is an error to continue sampling xfs_globals.larp once xfs_attr_change has made a decision about larp, and it was not correct for me to have told Allison that ->create_intent functions can sample the global log incompat feature bitfield to decide to elide a log item. Instead, create a new op flag for the xfs_da_args structure, and convert all other callers of xfs_has_larp and xfs_sb_version_haslogxattrs within the attr update state machine to look for the operations flag. Signed-off-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Allison Henderson <allison.henderson@oracle.com>
1 parent b13bacc commit f4288f0

File tree

6 files changed

+34
-22
lines changed

6 files changed

+34
-22
lines changed

fs/xfs/libxfs/xfs_attr.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -997,9 +997,11 @@ xfs_attr_set(
997997
/*
998998
* We have no control over the attribute names that userspace passes us
999999
* to remove, so we have to allow the name lookup prior to attribute
1000-
* removal to fail as well.
1000+
* removal to fail as well. Preserve the logged flag, since we need
1001+
* to pass that through to the logging code.
10011002
*/
1002-
args->op_flags = XFS_DA_OP_OKNOENT;
1003+
args->op_flags = XFS_DA_OP_OKNOENT |
1004+
(args->op_flags & XFS_DA_OP_LOGGED);
10031005

10041006
if (args->value) {
10051007
XFS_STATS_INC(mp, xs_attr_set);

fs/xfs/libxfs/xfs_attr.h

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ struct xfs_attr_list_context;
2828
*/
2929
#define ATTR_MAX_VALUELEN (64*1024) /* max length of a value */
3030

31-
static inline bool xfs_has_larp(struct xfs_mount *mp)
32-
{
33-
#ifdef DEBUG
34-
/* Logged xattrs require a V5 super for log_incompat */
35-
return xfs_has_crc(mp) && xfs_globals.larp;
36-
#else
37-
return false;
38-
#endif
39-
}
40-
4131
/*
4232
* Kernel-internal version of the attrlist cursor.
4333
*/
@@ -624,7 +614,7 @@ static inline enum xfs_delattr_state
624614
xfs_attr_init_replace_state(struct xfs_da_args *args)
625615
{
626616
args->op_flags |= XFS_DA_OP_ADDNAME | XFS_DA_OP_REPLACE;
627-
if (xfs_has_larp(args->dp->i_mount))
617+
if (args->op_flags & XFS_DA_OP_LOGGED)
628618
return xfs_attr_init_remove_state(args);
629619
return xfs_attr_init_add_state(args);
630620
}

fs/xfs/libxfs/xfs_attr_leaf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1530,7 +1530,7 @@ xfs_attr3_leaf_add_work(
15301530
if (tmp)
15311531
entry->flags |= XFS_ATTR_LOCAL;
15321532
if (args->op_flags & XFS_DA_OP_REPLACE) {
1533-
if (!xfs_has_larp(mp))
1533+
if (!(args->op_flags & XFS_DA_OP_LOGGED))
15341534
entry->flags |= XFS_ATTR_INCOMPLETE;
15351535
if ((args->blkno2 == args->blkno) &&
15361536
(args->index2 <= args->index)) {

fs/xfs/libxfs/xfs_da_btree.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ typedef struct xfs_da_args {
9292
#define XFS_DA_OP_NOTIME (1u << 5) /* don't update inode timestamps */
9393
#define XFS_DA_OP_REMOVE (1u << 6) /* this is a remove operation */
9494
#define XFS_DA_OP_RECOVERY (1u << 7) /* Log recovery operation */
95+
#define XFS_DA_OP_LOGGED (1u << 8) /* Use intent items to track op */
9596

9697
#define XFS_DA_OP_FLAGS \
9798
{ XFS_DA_OP_JUSTCHECK, "JUSTCHECK" }, \
@@ -101,7 +102,8 @@ typedef struct xfs_da_args {
101102
{ XFS_DA_OP_CILOOKUP, "CILOOKUP" }, \
102103
{ XFS_DA_OP_NOTIME, "NOTIME" }, \
103104
{ XFS_DA_OP_REMOVE, "REMOVE" }, \
104-
{ XFS_DA_OP_RECOVERY, "RECOVERY" }
105+
{ XFS_DA_OP_RECOVERY, "RECOVERY" }, \
106+
{ XFS_DA_OP_LOGGED, "LOGGED" }
105107

106108
/*
107109
* Storage for holding state during Btree searches and split/join ops.

fs/xfs/xfs_attr_item.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,27 +413,27 @@ xfs_attr_create_intent(
413413
struct xfs_mount *mp = tp->t_mountp;
414414
struct xfs_attri_log_item *attrip;
415415
struct xfs_attr_intent *attr;
416+
struct xfs_da_args *args;
416417

417418
ASSERT(count == 1);
418419

419-
if (!xfs_sb_version_haslogxattrs(&mp->m_sb))
420-
return NULL;
421-
422420
/*
423421
* Each attr item only performs one attribute operation at a time, so
424422
* this is a list of one
425423
*/
426424
attr = list_first_entry_or_null(items, struct xfs_attr_intent,
427425
xattri_list);
426+
args = attr->xattri_da_args;
427+
428+
if (!(args->op_flags & XFS_DA_OP_LOGGED))
429+
return NULL;
428430

429431
/*
430432
* Create a buffer to store the attribute name and value. This buffer
431433
* will be shared between the higher level deferred xattr work state
432434
* and the lower level xattr log items.
433435
*/
434436
if (!attr->xattri_nameval) {
435-
struct xfs_da_args *args = attr->xattri_da_args;
436-
437437
/*
438438
* Transfer our reference to the name/value buffer to the
439439
* deferred work state structure.
@@ -617,7 +617,10 @@ xfs_attri_item_recover(
617617
args->namelen = nv->name.i_len;
618618
args->hashval = xfs_da_hashname(args->name, args->namelen);
619619
args->attr_filter = attrp->alfi_attr_filter & XFS_ATTRI_FILTER_MASK;
620-
args->op_flags = XFS_DA_OP_RECOVERY | XFS_DA_OP_OKNOENT;
620+
args->op_flags = XFS_DA_OP_RECOVERY | XFS_DA_OP_OKNOENT |
621+
XFS_DA_OP_LOGGED;
622+
623+
ASSERT(xfs_sb_version_haslogxattrs(&mp->m_sb));
621624

622625
switch (attr->xattri_op_flags) {
623626
case XFS_ATTRI_OP_FLAGS_SET:

fs/xfs/xfs_xattr.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,18 @@ xfs_attr_rele_log_assist(
6868
xlog_drop_incompat_feat(mp->m_log);
6969
}
7070

71+
static inline bool
72+
xfs_attr_want_log_assist(
73+
struct xfs_mount *mp)
74+
{
75+
#ifdef DEBUG
76+
/* Logged xattrs require a V5 super for log_incompat */
77+
return xfs_has_crc(mp) && xfs_globals.larp;
78+
#else
79+
return false;
80+
#endif
81+
}
82+
7183
/*
7284
* Set or remove an xattr, having grabbed the appropriate logging resources
7385
* prior to calling libxfs.
@@ -80,11 +92,14 @@ xfs_attr_change(
8092
bool use_logging = false;
8193
int error;
8294

83-
if (xfs_has_larp(mp)) {
95+
ASSERT(!(args->op_flags & XFS_DA_OP_LOGGED));
96+
97+
if (xfs_attr_want_log_assist(mp)) {
8498
error = xfs_attr_grab_log_assist(mp);
8599
if (error)
86100
return error;
87101

102+
args->op_flags |= XFS_DA_OP_LOGGED;
88103
use_logging = true;
89104
}
90105

0 commit comments

Comments
 (0)