Commit d7d02f75 authored by Chandan Babu R's avatar Chandan Babu R

Merge tag 'improve-attr-validation-6.10_2024-04-23' of...

Merge tag 'improve-attr-validation-6.10_2024-04-23' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux into xfs-6.10-mergeC

xfs: improve extended attribute validation

Prior to introducing parent pointer extended attributes, let's spend
some time cleaning up the attr code and strengthening the validation
that it performs on attrs coming in from the disk.
Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarChandan Babu R <chandanbabu@kernel.org>

* tag 'improve-attr-validation-6.10_2024-04-23' of https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfs-linux:
  xfs: enforce one namespace per attribute
  xfs: refactor name/value iovec validation in xlog_recover_attri_commit_pass2
  xfs: refactor name/length checks in xfs_attri_validate
  xfs: use local variables for name and value length in _attri_commit_pass2
  xfs: always set args->value in xfs_attri_item_recover
  xfs: validate recovered name buffers when recovering xattr items
  xfs: use helpers to extract xattr op from opflags
  xfs: restructure xfs_attr_complete_op a bit
  xfs: check shortform attr entry flags specifically
  xfs: fix missing check for invalid attr flags
  xfs: check opcode and iovec count match in xlog_recover_attri_commit_pass2
  xfs: use an XFS_OPSTATE_ flag for detecting if logged xattrs are available
  xfs: require XFS_SB_FEAT_INCOMPAT_LOG_XATTRS for attr log intent item recovery
  xfs: attr fork iext must be loaded before calling xfs_attr_is_leaf
parents 1321890a ea0b3e81
......@@ -87,6 +87,8 @@ xfs_attr_is_leaf(
struct xfs_iext_cursor icur;
struct xfs_bmbt_irec imap;
ASSERT(!xfs_need_iread_extents(ifp));
if (ifp->if_nextents != 1 || ifp->if_format != XFS_DINODE_FMT_EXTENTS)
return false;
......@@ -224,11 +226,21 @@ int
xfs_attr_get_ilocked(
struct xfs_da_args *args)
{
int error;
xfs_assert_ilocked(args->dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
if (!xfs_inode_hasattr(args->dp))
return -ENOATTR;
/*
* The incore attr fork iext tree must be loaded for xfs_attr_is_leaf
* to work correctly.
*/
error = xfs_iread_extents(args->trans, args->dp, XFS_ATTR_FORK);
if (error)
return error;
if (args->dp->i_af.if_format == XFS_DINODE_FMT_LOCAL)
return xfs_attr_shortform_getvalue(args);
if (xfs_attr_is_leaf(args->dp))
......@@ -420,14 +432,13 @@ xfs_attr_complete_op(
enum xfs_delattr_state replace_state)
{
struct xfs_da_args *args = attr->xattri_da_args;
bool do_replace = args->op_flags & XFS_DA_OP_REPLACE;
if (!(args->op_flags & XFS_DA_OP_REPLACE))
replace_state = XFS_DAS_DONE;
args->op_flags &= ~XFS_DA_OP_REPLACE;
args->attr_filter &= ~XFS_ATTR_INCOMPLETE;
if (do_replace)
return replace_state;
return XFS_DAS_DONE;
return replace_state;
}
static int
......@@ -870,6 +881,11 @@ xfs_attr_lookup(
return -ENOATTR;
}
/* Prerequisite for xfs_attr_is_leaf */
error = xfs_iread_extents(args->trans, args->dp, XFS_ATTR_FORK);
if (error)
return error;
if (xfs_attr_is_leaf(dp)) {
error = xfs_attr_leaf_hasname(args, &bp);
......@@ -1516,12 +1532,23 @@ xfs_attr_node_get(
return error;
}
/* Enforce that there is at most one namespace bit per attr. */
inline bool xfs_attr_check_namespace(unsigned int attr_flags)
{
return hweight32(attr_flags & XFS_ATTR_NSP_ONDISK_MASK) < 2;
}
/* Returns true if the attribute entry name is valid. */
bool
xfs_attr_namecheck(
unsigned int attr_flags,
const void *name,
size_t length)
{
/* Only one namespace bit allowed. */
if (!xfs_attr_check_namespace(attr_flags))
return false;
/*
* MAXNAMELEN includes the trailing null, but (name/length) leave it
* out, so use >= for the length check.
......
......@@ -529,6 +529,11 @@ struct xfs_attr_intent {
struct xfs_bmbt_irec xattri_map;
};
static inline unsigned int
xfs_attr_intent_op(const struct xfs_attr_intent *attr)
{
return attr->xattri_op_flags & XFS_ATTRI_OP_FLAGS_TYPE_MASK;
}
/*========================================================================
* Function prototypes for the kernel.
......@@ -555,7 +560,9 @@ enum xfs_attr_update {
int xfs_attr_set(struct xfs_da_args *args, enum xfs_attr_update op);
int xfs_attr_set_iter(struct xfs_attr_intent *attr);
int xfs_attr_remove_iter(struct xfs_attr_intent *attr);
bool xfs_attr_namecheck(const void *name, size_t length);
bool xfs_attr_check_namespace(unsigned int attr_flags);
bool xfs_attr_namecheck(unsigned int attr_flags, const void *name,
size_t length);
int xfs_attr_calc_size(struct xfs_da_args *args, int *local);
void xfs_init_attr_trans(struct xfs_da_args *args, struct xfs_trans_res *tres,
unsigned int *total);
......
......@@ -950,6 +950,11 @@ xfs_attr_shortform_to_leaf(
nargs.hashval = xfs_da_hashname(sfe->nameval,
sfe->namelen);
nargs.attr_filter = sfe->flags & XFS_ATTR_NSP_ONDISK_MASK;
if (!xfs_attr_check_namespace(sfe->flags)) {
xfs_da_mark_sick(args);
error = -EFSCORRUPTED;
goto out;
}
error = xfs_attr3_leaf_lookup_int(bp, &nargs); /* set a->index */
ASSERT(error == -ENOATTR);
error = xfs_attr3_leaf_add(bp, &nargs);
......@@ -1063,7 +1068,7 @@ xfs_attr_shortform_verify(
* one namespace flag per xattr, so we can just count the
* bits (i.e. hweight) here.
*/
if (hweight8(sfep->flags & XFS_ATTR_NSP_ONDISK_MASK) > 1)
if (!xfs_attr_check_namespace(sfep->flags))
return __this_address;
sfep = next_sfep;
......
......@@ -719,8 +719,13 @@ struct xfs_attr3_leafblock {
#define XFS_ATTR_ROOT (1u << XFS_ATTR_ROOT_BIT)
#define XFS_ATTR_SECURE (1u << XFS_ATTR_SECURE_BIT)
#define XFS_ATTR_INCOMPLETE (1u << XFS_ATTR_INCOMPLETE_BIT)
#define XFS_ATTR_NSP_ONDISK_MASK (XFS_ATTR_ROOT | XFS_ATTR_SECURE)
#define XFS_ATTR_ONDISK_MASK (XFS_ATTR_NSP_ONDISK_MASK | \
XFS_ATTR_LOCAL | \
XFS_ATTR_INCOMPLETE)
#define XFS_ATTR_NAMESPACE_STR \
{ XFS_ATTR_LOCAL, "local" }, \
{ XFS_ATTR_ROOT, "root" }, \
......
......@@ -192,20 +192,19 @@ xchk_xattr_actor(
if (xchk_should_terminate(sc, &error))
return error;
if (attr_flags & ~XFS_ATTR_ONDISK_MASK) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno);
return -ECANCELED;
}
if (attr_flags & XFS_ATTR_INCOMPLETE) {
/* Incomplete attr key, just mark the inode for preening. */
xchk_ino_set_preen(sc, ip->i_ino);
return 0;
}
/* Only one namespace bit allowed. */
if (hweight32(attr_flags & XFS_ATTR_NSP_ONDISK_MASK) > 1) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno);
return -ECANCELED;
}
/* Does this name make sense? */
if (!xfs_attr_namecheck(name, namelen)) {
if (!xfs_attr_namecheck(attr_flags, name, namelen)) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno);
return -ECANCELED;
}
......@@ -481,7 +480,6 @@ xchk_xattr_rec(
xfs_dahash_t hash;
int nameidx;
int hdrsize;
unsigned int badflags;
int error;
ASSERT(blk->magic == XFS_ATTR_LEAF_MAGIC);
......@@ -511,10 +509,15 @@ xchk_xattr_rec(
/* Retrieve the entry and check it. */
hash = be32_to_cpu(ent->hashval);
badflags = ~(XFS_ATTR_LOCAL | XFS_ATTR_ROOT | XFS_ATTR_SECURE |
XFS_ATTR_INCOMPLETE);
if ((ent->flags & badflags) != 0)
if (ent->flags & ~XFS_ATTR_ONDISK_MASK) {
xchk_da_set_corrupt(ds, level);
return 0;
}
if (!xfs_attr_check_namespace(ent->flags)) {
xchk_da_set_corrupt(ds, level);
return 0;
}
if (ent->flags & XFS_ATTR_LOCAL) {
lentry = (struct xfs_attr_leaf_name_local *)
(((char *)bp->b_addr) + nameidx);
......@@ -574,6 +577,15 @@ xchk_xattr_check_sf(
break;
}
/*
* Shortform entries do not set LOCAL or INCOMPLETE, so the
* only valid flag bits here are for namespaces.
*/
if (sfe->flags & ~XFS_ATTR_NSP_ONDISK_MASK) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
break;
}
if (!xchk_xattr_set_map(sc, ab->usedmap,
(char *)sfe - (char *)sf,
sizeof(struct xfs_attr_sf_entry))) {
......
......@@ -123,12 +123,10 @@ xrep_xattr_want_salvage(
return false;
if (namelen > XATTR_NAME_MAX || namelen <= 0)
return false;
if (!xfs_attr_namecheck(name, namelen))
if (!xfs_attr_namecheck(attr_flags, name, namelen))
return false;
if (valuelen > XATTR_SIZE_MAX || valuelen < 0)
return false;
if (hweight32(attr_flags & XFS_ATTR_NSP_ONDISK_MASK) > 1)
return false;
return true;
}
......
This diff is collapsed.
......@@ -82,7 +82,8 @@ xfs_attr_shortform_list(
(dp->i_af.if_bytes + sf->count * 16) < context->bufsize)) {
for (i = 0, sfe = xfs_attr_sf_firstentry(sf); i < sf->count; i++) {
if (XFS_IS_CORRUPT(context->dp->i_mount,
!xfs_attr_namecheck(sfe->nameval,
!xfs_attr_namecheck(sfe->flags,
sfe->nameval,
sfe->namelen))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
return -EFSCORRUPTED;
......@@ -122,7 +123,8 @@ xfs_attr_shortform_list(
for (i = 0, sfe = xfs_attr_sf_firstentry(sf); i < sf->count; i++) {
if (unlikely(
((char *)sfe < (char *)sf) ||
((char *)sfe >= ((char *)sf + dp->i_af.if_bytes)))) {
((char *)sfe >= ((char *)sf + dp->i_af.if_bytes)) ||
!xfs_attr_check_namespace(sfe->flags))) {
XFS_CORRUPTION_ERROR("xfs_attr_shortform_list",
XFS_ERRLEVEL_LOW,
context->dp->i_mount, sfe,
......@@ -177,7 +179,7 @@ xfs_attr_shortform_list(
cursor->offset = 0;
}
if (XFS_IS_CORRUPT(context->dp->i_mount,
!xfs_attr_namecheck(sbp->name,
!xfs_attr_namecheck(sbp->flags, sbp->name,
sbp->namelen))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
error = -EFSCORRUPTED;
......@@ -502,7 +504,8 @@ xfs_attr3_leaf_list_int(
}
if (XFS_IS_CORRUPT(context->dp->i_mount,
!xfs_attr_namecheck(name, namelen))) {
!xfs_attr_namecheck(entry->flags, name,
namelen))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
return -EFSCORRUPTED;
}
......@@ -544,6 +547,7 @@ xfs_attr_list_ilocked(
struct xfs_attr_list_context *context)
{
struct xfs_inode *dp = context->dp;
int error;
xfs_assert_ilocked(dp, XFS_ILOCK_SHARED | XFS_ILOCK_EXCL);
......@@ -554,6 +558,12 @@ xfs_attr_list_ilocked(
return 0;
if (dp->i_af.if_format == XFS_DINODE_FMT_LOCAL)
return xfs_attr_shortform_list(context);
/* Prerequisite for xfs_attr_is_leaf */
error = xfs_iread_extents(NULL, dp, XFS_ATTR_FORK);
if (error)
return error;
if (xfs_attr_is_leaf(dp))
return xfs_attr_leaf_list(context);
return xfs_attr_node_list(context);
......
......@@ -231,6 +231,13 @@ xfs_readsb(
mp->m_features |= xfs_sb_version_to_features(sbp);
xfs_reinit_percpu_counters(mp);
/*
* If logged xattrs are enabled after log recovery finishes, then set
* the opstate so that log recovery will work properly.
*/
if (xfs_sb_version_haslogxattrs(&mp->m_sb))
xfs_set_using_logged_xattrs(mp);
/* no need to be quiet anymore, so reset the buf ops */
bp->b_ops = &xfs_sb_buf_ops;
......@@ -829,6 +836,15 @@ xfs_mountfs(
goto out_inodegc_shrinker;
}
/*
* If logged xattrs are still enabled after log recovery finishes, then
* they'll be available until unmount. Otherwise, turn them off.
*/
if (xfs_sb_version_haslogxattrs(&mp->m_sb))
xfs_set_using_logged_xattrs(mp);
else
xfs_clear_using_logged_xattrs(mp);
/* Enable background inode inactivation workers. */
xfs_inodegc_start(mp);
xfs_blockgc_start(mp);
......
......@@ -444,6 +444,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)
#define XFS_OPSTATE_QUOTACHECK_RUNNING 10
/* Do we want to clear log incompat flags? */
#define XFS_OPSTATE_UNSET_LOG_INCOMPAT 11
/* Filesystem can use logged extended attributes */
#define XFS_OPSTATE_USE_LARP 12
#define __XFS_IS_OPSTATE(name, NAME) \
static inline bool xfs_is_ ## name (struct xfs_mount *mp) \
......@@ -472,6 +474,7 @@ __XFS_IS_OPSTATE(quotacheck_running, QUOTACHECK_RUNNING)
# define xfs_is_quotacheck_running(mp) (false)
#endif
__XFS_IS_OPSTATE(done_with_log_incompat, UNSET_LOG_INCOMPAT)
__XFS_IS_OPSTATE(using_logged_xattrs, USE_LARP)
static inline bool
xfs_should_warn(struct xfs_mount *mp, long nr)
......@@ -491,7 +494,8 @@ xfs_should_warn(struct xfs_mount *mp, long nr)
{ (1UL << XFS_OPSTATE_WARNED_SHRINK), "wshrink" }, \
{ (1UL << XFS_OPSTATE_WARNED_LARP), "wlarp" }, \
{ (1UL << XFS_OPSTATE_QUOTACHECK_RUNNING), "quotacheck" }, \
{ (1UL << XFS_OPSTATE_UNSET_LOG_INCOMPAT), "unset_log_incompat" }
{ (1UL << XFS_OPSTATE_UNSET_LOG_INCOMPAT), "unset_log_incompat" }, \
{ (1UL << XFS_OPSTATE_USE_LARP), "logged_xattrs" }
/*
* Max and min values for mount-option defined I/O
......
......@@ -31,7 +31,7 @@ xfs_attr_grab_log_assist(
int error = 0;
/* xattr update log intent items are already enabled */
if (xfs_sb_version_haslogxattrs(&mp->m_sb))
if (xfs_is_using_logged_xattrs(mp))
return 0;
/*
......@@ -48,6 +48,7 @@ xfs_attr_grab_log_assist(
XFS_SB_FEAT_INCOMPAT_LOG_XATTRS);
if (error)
return error;
xfs_set_using_logged_xattrs(mp);
xfs_warn_mount(mp, XFS_OPSTATE_WARNED_LARP,
"EXPERIMENTAL logged extended attributes feature in use. Use at your own risk!");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment