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;
}
......
......@@ -308,6 +308,12 @@ xfs_attrd_item_intent(
return &ATTRD_ITEM(lip)->attrd_attrip->attri_item;
}
static inline unsigned int
xfs_attr_log_item_op(const struct xfs_attri_log_format *attrp)
{
return attrp->alfi_op_flags & XFS_ATTRI_OP_FLAGS_TYPE_MASK;
}
/* Log an attr to the intent item. */
STATIC void
xfs_attr_log_item(
......@@ -460,14 +466,22 @@ xfs_attri_item_match(
return ATTRI_ITEM(lip)->attri_format.alfi_id == intent_id;
}
static inline bool
xfs_attri_validate_namelen(unsigned int namelen)
{
return namelen > 0 && namelen <= XATTR_NAME_MAX;
}
/* Is this recovered ATTRI format ok? */
static inline bool
xfs_attri_validate(
struct xfs_mount *mp,
struct xfs_attri_log_format *attrp)
{
unsigned int op = attrp->alfi_op_flags &
XFS_ATTRI_OP_FLAGS_TYPE_MASK;
unsigned int op = xfs_attr_log_item_op(attrp);
if (!xfs_is_using_logged_xattrs(mp))
return false;
if (attrp->__pad != 0)
return false;
......@@ -478,24 +492,48 @@ xfs_attri_validate(
if (attrp->alfi_attr_filter & ~XFS_ATTRI_FILTER_MASK)
return false;
/* alfi_op_flags should be either a set or remove */
if (!xfs_attr_check_namespace(attrp->alfi_attr_filter &
XFS_ATTR_NSP_ONDISK_MASK))
return false;
switch (op) {
case XFS_ATTRI_OP_FLAGS_SET:
case XFS_ATTRI_OP_FLAGS_REPLACE:
if (attrp->alfi_value_len > XATTR_SIZE_MAX)
return false;
if (!xfs_attri_validate_namelen(attrp->alfi_name_len))
return false;
break;
case XFS_ATTRI_OP_FLAGS_REMOVE:
if (attrp->alfi_value_len != 0)
return false;
if (!xfs_attri_validate_namelen(attrp->alfi_name_len))
return false;
break;
default:
return false;
}
if (attrp->alfi_value_len > XATTR_SIZE_MAX)
return false;
return xfs_verify_ino(mp, attrp->alfi_ino);
}
if ((attrp->alfi_name_len > XATTR_NAME_MAX) ||
(attrp->alfi_name_len == 0))
return false;
static int
xfs_attri_iread_extents(
struct xfs_inode *ip)
{
struct xfs_trans *tp;
int error;
return xfs_verify_ino(mp, attrp->alfi_ino);
error = xfs_trans_alloc_empty(ip->i_mount, &tp);
if (error)
return error;
xfs_ilock(ip, XFS_ILOCK_EXCL);
error = xfs_iread_extents(tp, ip, XFS_ATTR_FORK);
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_trans_cancel(tp);
return error;
}
static inline struct xfs_attr_intent *
......@@ -508,20 +546,28 @@ xfs_attri_recover_work(
{
struct xfs_attr_intent *attr;
struct xfs_da_args *args;
struct xfs_inode *ip;
int local;
int error;
error = xlog_recover_iget(mp, attrp->alfi_ino, ipp);
error = xlog_recover_iget(mp, attrp->alfi_ino, &ip);
if (error)
return ERR_PTR(error);
if (xfs_inode_has_attr_fork(ip)) {
error = xfs_attri_iread_extents(ip);
if (error) {
xfs_irele(ip);
return ERR_PTR(error);
}
}
attr = kzalloc(sizeof(struct xfs_attr_intent) +
sizeof(struct xfs_da_args), GFP_KERNEL | __GFP_NOFAIL);
args = (struct xfs_da_args *)(attr + 1);
attr->xattri_da_args = args;
attr->xattri_op_flags = attrp->alfi_op_flags &
XFS_ATTRI_OP_FLAGS_TYPE_MASK;
attr->xattri_op_flags = xfs_attr_log_item_op(attrp);
/*
* We're reconstructing the deferred work state structure from the
......@@ -531,24 +577,22 @@ xfs_attri_recover_work(
attr->xattri_nameval = xfs_attri_log_nameval_get(nv);
ASSERT(attr->xattri_nameval);
args->dp = *ipp;
args->dp = ip;
args->geo = mp->m_attr_geo;
args->whichfork = XFS_ATTR_FORK;
args->name = nv->name.i_addr;
args->namelen = nv->name.i_len;
args->hashval = xfs_da_hashname(args->name, args->namelen);
args->value = nv->value.i_addr;
args->valuelen = nv->value.i_len;
args->attr_filter = attrp->alfi_attr_filter & XFS_ATTRI_FILTER_MASK;
args->op_flags = XFS_DA_OP_RECOVERY | XFS_DA_OP_OKNOENT |
XFS_DA_OP_LOGGED;
args->owner = args->dp->i_ino;
ASSERT(xfs_sb_version_haslogxattrs(&mp->m_sb));
switch (attr->xattri_op_flags) {
switch (xfs_attr_intent_op(attr)) {
case XFS_ATTRI_OP_FLAGS_SET:
case XFS_ATTRI_OP_FLAGS_REPLACE:
args->value = nv->value.i_addr;
args->valuelen = nv->value.i_len;
args->total = xfs_attr_calc_size(args, &local);
if (xfs_inode_hasattr(args->dp))
attr->xattri_dela_state = xfs_attr_init_replace_state(args);
......@@ -561,6 +605,7 @@ xfs_attri_recover_work(
}
xfs_defer_add_item(dfp, &attr->xattri_list);
*ipp = ip;
return attr;
}
......@@ -592,7 +637,8 @@ xfs_attr_recover_work(
*/
attrp = &attrip->attri_format;
if (!xfs_attri_validate(mp, attrp) ||
!xfs_attr_namecheck(nv->name.i_addr, nv->name.i_len))
!xfs_attr_namecheck(attrp->alfi_attr_filter, nv->name.i_addr,
nv->name.i_len))
return -EFSCORRUPTED;
attr = xfs_attri_recover_work(mp, dfp, attrp, &ip, nv);
......@@ -615,16 +661,17 @@ xfs_attr_recover_work(
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
&attrip->attri_format,
sizeof(attrip->attri_format));
if (error) {
xfs_trans_cancel(tp);
goto out_unlock;
}
if (error)
goto out_cancel;
error = xfs_defer_ops_capture_and_commit(tp, capture_list);
out_unlock:
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_irele(ip);
return error;
out_cancel:
xfs_trans_cancel(tp);
goto out_unlock;
}
/* Re-log an intent item to push the log tail forward. */
......@@ -692,6 +739,47 @@ const struct xfs_defer_op_type xfs_attr_defer_type = {
.relog_intent = xfs_attr_relog_intent,
};
static inline void *
xfs_attri_validate_name_iovec(
struct xfs_mount *mp,
struct xfs_attri_log_format *attri_formatp,
const struct xfs_log_iovec *iovec,
unsigned int name_len)
{
if (iovec->i_len != xlog_calc_iovec_len(name_len)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, sizeof(*attri_formatp));
return NULL;
}
if (!xfs_attr_namecheck(attri_formatp->alfi_attr_filter, iovec->i_addr,
name_len)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, sizeof(*attri_formatp));
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
iovec->i_addr, iovec->i_len);
return NULL;
}
return iovec->i_addr;
}
static inline void *
xfs_attri_validate_value_iovec(
struct xfs_mount *mp,
struct xfs_attri_log_format *attri_formatp,
const struct xfs_log_iovec *iovec,
unsigned int value_len)
{
if (iovec->i_len != xlog_calc_iovec_len(value_len)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, sizeof(*attri_formatp));
return NULL;
}
return iovec->i_addr;
}
STATIC int
xlog_recover_attri_commit_pass2(
struct xlog *log,
......@@ -703,51 +791,106 @@ xlog_recover_attri_commit_pass2(
struct xfs_attri_log_item *attrip;
struct xfs_attri_log_format *attri_formatp;
struct xfs_attri_log_nameval *nv;
const void *attr_value = NULL;
const void *attr_name;
const void *attr_value = NULL;
size_t len;
attri_formatp = item->ri_buf[0].i_addr;
attr_name = item->ri_buf[1].i_addr;
unsigned int name_len = 0;
unsigned int value_len = 0;
unsigned int op, i = 0;
/* Validate xfs_attri_log_format before the large memory allocation */
len = sizeof(struct xfs_attri_log_format);
if (item->ri_buf[0].i_len != len) {
if (item->ri_buf[i].i_len != len) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[0].i_addr, item->ri_buf[0].i_len);
return -EFSCORRUPTED;
}
attri_formatp = item->ri_buf[i].i_addr;
if (!xfs_attri_validate(mp, attri_formatp)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[0].i_addr, item->ri_buf[0].i_len);
attri_formatp, len);
return -EFSCORRUPTED;
}
/* Validate the attr name */
if (item->ri_buf[1].i_len !=
xlog_calc_iovec_len(attri_formatp->alfi_name_len)) {
/* Check the number of log iovecs makes sense for the op code. */
op = xfs_attr_log_item_op(attri_formatp);
switch (op) {
case XFS_ATTRI_OP_FLAGS_SET:
case XFS_ATTRI_OP_FLAGS_REPLACE:
/* Log item, attr name, attr value */
if (item->ri_total != 3) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, len);
return -EFSCORRUPTED;
}
name_len = attri_formatp->alfi_name_len;
value_len = attri_formatp->alfi_value_len;
break;
case XFS_ATTRI_OP_FLAGS_REMOVE:
/* Log item, attr name */
if (item->ri_total != 2) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, len);
return -EFSCORRUPTED;
}
name_len = attri_formatp->alfi_name_len;
break;
default:
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[0].i_addr, item->ri_buf[0].i_len);
attri_formatp, len);
return -EFSCORRUPTED;
}
i++;
/* Validate the attr name */
attr_name = xfs_attri_validate_name_iovec(mp, attri_formatp,
&item->ri_buf[i], name_len);
if (!attr_name)
return -EFSCORRUPTED;
i++;
/* Validate the attr value, if present */
if (value_len != 0) {
attr_value = xfs_attri_validate_value_iovec(mp, attri_formatp,
&item->ri_buf[i], value_len);
if (!attr_value)
return -EFSCORRUPTED;
i++;
}
if (!xfs_attr_namecheck(attr_name, attri_formatp->alfi_name_len)) {
/*
* Make sure we got the correct number of buffers for the operation
* that we just loaded.
*/
if (i != item->ri_total) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[1].i_addr, item->ri_buf[1].i_len);
attri_formatp, len);
return -EFSCORRUPTED;
}
/* Validate the attr value, if present */
if (attri_formatp->alfi_value_len != 0) {
if (item->ri_buf[2].i_len != xlog_calc_iovec_len(attri_formatp->alfi_value_len)) {
switch (op) {
case XFS_ATTRI_OP_FLAGS_REMOVE:
/* Regular remove operations operate only on names. */
if (attr_value != NULL || value_len != 0) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[0].i_addr,
item->ri_buf[0].i_len);
attri_formatp, len);
return -EFSCORRUPTED;
}
attr_value = item->ri_buf[2].i_addr;
fallthrough;
case XFS_ATTRI_OP_FLAGS_SET:
case XFS_ATTRI_OP_FLAGS_REPLACE:
/*
* Regular xattr set/remove/replace operations require a name
* and do not take a newname. Values are optional for set and
* replace.
*/
if (attr_name == NULL || name_len == 0) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, len);
return -EFSCORRUPTED;
}
break;
}
/*
......@@ -755,9 +898,8 @@ xlog_recover_attri_commit_pass2(
* name/value buffer to the recovered incore log item and drop our
* reference.
*/
nv = xfs_attri_log_nameval_alloc(attr_name,
attri_formatp->alfi_name_len, attr_value,
attri_formatp->alfi_value_len);
nv = xfs_attri_log_nameval_alloc(attr_name, name_len,
attr_value, value_len);
attrip = xfs_attri_init(mp, nv);
memcpy(&attrip->attri_format, attri_formatp, len);
......
......@@ -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