@@ -41,6 +41,7 @@ xfs-y += $(addprefix libxfs/, \
xfs_inode_buf.o \
xfs_log_rlimit.o \
xfs_ag_resv.o \
+ xfs_parent.o \
xfs_rmap.o \
xfs_rmap_btree.o \
xfs_refcount.o \
@@ -26,6 +26,7 @@
#include "xfs_trace.h"
#include "xfs_attr_item.h"
#include "xfs_xattr.h"
+#include "xfs_parent.h"
struct kmem_cache *xfs_attr_intent_cache;
@@ -1545,9 +1546,14 @@ xfs_attr_node_get(
/* Returns true if the attribute entry name is valid. */
bool
xfs_attr_namecheck(
- const void *name,
- size_t length)
+ struct xfs_mount *mp,
+ const void *name,
+ size_t length,
+ unsigned int flags)
{
+ if (flags & XFS_ATTR_PARENT)
+ return xfs_parent_namecheck(mp, name, length, flags);
+
/*
* MAXNAMELEN includes the trailing null, but (name/length) leave it
* out, so use >= for the length check.
@@ -552,7 +552,8 @@ int xfs_attr_get(struct xfs_da_args *args);
int xfs_attr_set(struct xfs_da_args *args);
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_namecheck(struct xfs_mount *mp, const void *name, size_t length,
+ unsigned int flags);
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);
@@ -757,6 +757,14 @@ xfs_attr3_leaf_name(xfs_attr_leafblock_t *leafp, int idx)
return &((char *)leafp)[be16_to_cpu(entries[idx].nameidx)];
}
+static inline int
+xfs_attr3_leaf_flags(xfs_attr_leafblock_t *leafp, int idx)
+{
+ struct xfs_attr_leaf_entry *entries = xfs_attr3_leaf_entryp(leafp);
+
+ return entries[idx].flags;
+}
+
static inline xfs_attr_leaf_name_remote_t *
xfs_attr3_leaf_name_remote(xfs_attr_leafblock_t *leafp, int idx)
{
new file mode 100644
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2024 Oracle.
+ * All rights reserved.
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_format.h"
+#include "xfs_da_format.h"
+#include "xfs_log_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_bmap_btree.h"
+#include "xfs_inode.h"
+#include "xfs_error.h"
+#include "xfs_trace.h"
+#include "xfs_trans.h"
+#include "xfs_da_btree.h"
+#include "xfs_attr.h"
+#include "xfs_dir2.h"
+#include "xfs_dir2_priv.h"
+#include "xfs_attr_sf.h"
+#include "xfs_bmap.h"
+#include "xfs_defer.h"
+#include "xfs_log.h"
+#include "xfs_xattr.h"
+#include "xfs_parent.h"
+#include "xfs_trans_space.h"
+
+/*
+ * Parent pointer attribute handling.
+ *
+ * Because the attribute value is a filename component, it will never be longer
+ * than 255 bytes. This means the attribute will always be a local format
+ * attribute as it is xfs_attr_leaf_entsize_local_max() for v5 filesystems will
+ * always be larger than this (max is 75% of block size).
+ *
+ * Creating a new parent attribute will always create a new attribute - there
+ * should never, ever be an existing attribute in the tree for a new inode.
+ * ENOSPC behavior is problematic - creating the inode without the parent
+ * pointer is effectively a corruption, so we allow parent attribute creation
+ * to dip into the reserve block pool to avoid unexpected ENOSPC errors from
+ * occurring.
+ */
+
+/* Return true if parent pointer EA name is valid. */
+bool
+xfs_parent_namecheck(
+ struct xfs_mount *mp,
+ const struct xfs_parent_name_rec *rec,
+ size_t reclen,
+ unsigned int attr_flags)
+{
+ if (!(attr_flags & XFS_ATTR_PARENT))
+ return false;
+
+ /* pptr updates use logged xattrs, so we should never see this flag */
+ if (attr_flags & XFS_ATTR_INCOMPLETE)
+ return false;
+
+ if (reclen != sizeof(struct xfs_parent_name_rec))
+ return false;
+
+ /* Only one namespace bit allowed. */
+ if (hweight32(attr_flags & XFS_ATTR_NSP_ONDISK_MASK) > 1)
+ return false;
+
+ return true;
+}
+
+/* Return true if parent pointer EA value is valid. */
+bool
+xfs_parent_valuecheck(
+ struct xfs_mount *mp,
+ const void *value,
+ size_t valuelen)
+{
+ if (valuelen == 0 || valuelen > XFS_PARENT_DIRENT_NAME_MAX_SIZE)
+ return false;
+
+ if (value == NULL)
+ return false;
+
+ return true;
+}
+
+/* Return true if the ondisk parent pointer is consistent. */
+bool
+xfs_parent_hashcheck(
+ struct xfs_mount *mp,
+ const struct xfs_parent_name_rec *rec,
+ const void *value,
+ size_t valuelen)
+{
+ struct xfs_name dname = {
+ .name = value,
+ .len = valuelen,
+ };
+ xfs_ino_t p_ino;
+
+ /* Valid dirent name? */
+ if (!xfs_dir2_namecheck(value, valuelen))
+ return false;
+
+ /* Valid inode number? */
+ p_ino = be64_to_cpu(rec->p_ino);
+ if (!xfs_verify_dir_ino(mp, p_ino))
+ return false;
+
+ /* Namehash matches name? */
+ return be32_to_cpu(rec->p_namehash) == xfs_dir2_hashname(mp, &dname);
+}
new file mode 100644
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2024 Oracle.
+ * All Rights Reserved.
+ */
+#ifndef __XFS_PARENT_H__
+#define __XFS_PARENT_H__
+
+/* Metadata validators */
+bool xfs_parent_namecheck(struct xfs_mount *mp,
+ const struct xfs_parent_name_rec *rec, size_t reclen,
+ unsigned int attr_flags);
+bool xfs_parent_valuecheck(struct xfs_mount *mp, const void *value,
+ size_t valuelen);
+bool xfs_parent_hashcheck(struct xfs_mount *mp,
+ const struct xfs_parent_name_rec *rec, const void *value,
+ size_t valuelen);
+
+#endif /* __XFS_PARENT_H__ */
@@ -206,7 +206,7 @@ xchk_xattr_actor(
}
/* Does this name make sense? */
- if (!xfs_attr_namecheck(name, namelen)) {
+ if (!xfs_attr_namecheck(sc->mp, name, namelen, attr_flags)) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno);
return -ECANCELED;
}
@@ -27,6 +27,7 @@
#include "xfs_error.h"
#include "xfs_log_priv.h"
#include "xfs_log_recover.h"
+#include "xfs_parent.h"
struct kmem_cache *xfs_attri_cache;
struct kmem_cache *xfs_attrd_cache;
@@ -687,7 +688,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(mp, nv->name.i_addr, nv->name.i_len,
+ attrp->alfi_attr_filter))
return -EFSCORRUPTED;
attr = xfs_attri_recover_work(mp, dfp, attrp, &ip, nv);
@@ -896,7 +898,8 @@ xlog_recover_attri_commit_pass2(
}
attr_name = item->ri_buf[i].i_addr;
- if (!xfs_attr_namecheck(attr_name, name_len)) {
+ if (!xfs_attr_namecheck(mp, attr_name, name_len,
+ attri_formatp->alfi_attr_filter)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
attri_formatp, len);
return -EFSCORRUPTED;
@@ -913,7 +916,8 @@ xlog_recover_attri_commit_pass2(
}
attr_new_name = item->ri_buf[i].i_addr;
- if (!xfs_attr_namecheck(attr_new_name, new_name_len)) {
+ if (!xfs_attr_namecheck(mp, attr_new_name, new_name_len,
+ attri_formatp->alfi_attr_filter)) {
XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
item->ri_buf[i].i_addr,
item->ri_buf[i].i_len);
@@ -931,6 +935,22 @@ xlog_recover_attri_commit_pass2(
}
attr_value = item->ri_buf[i].i_addr;
+ if ((attri_formatp->alfi_attr_filter & XFS_ATTR_PARENT) &&
+ !xfs_parent_valuecheck(mp, attr_value, value_len)) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
+ if ((attri_formatp->alfi_attr_filter & XFS_ATTR_PARENT) &&
+ (attr_name == NULL ||
+ !xfs_parent_hashcheck(mp, attr_name, attr_value,
+ value_len))) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
i++;
}
@@ -943,6 +963,22 @@ xlog_recover_attri_commit_pass2(
}
attr_new_value = item->ri_buf[i].i_addr;
+ if ((attri_formatp->alfi_attr_filter & XFS_ATTR_PARENT) &&
+ !xfs_parent_valuecheck(mp, attr_new_value, new_value_len)) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
+ if ((attri_formatp->alfi_attr_filter & XFS_ATTR_PARENT) &&
+ (attr_new_name == NULL ||
+ !xfs_parent_hashcheck(mp, attr_new_name, attr_new_value,
+ new_value_len))) {
+ XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp,
+ item->ri_buf[i].i_addr,
+ item->ri_buf[i].i_len);
+ return -EFSCORRUPTED;
+ }
i++;
}
@@ -59,9 +59,13 @@ xfs_attr_shortform_list(
struct xfs_attr_sf_sort *sbuf, *sbp;
struct xfs_attr_shortform *sf;
struct xfs_attr_sf_entry *sfe;
+ struct xfs_mount *mp;
int sbsize, nsbuf, count, i;
int error = 0;
+ ASSERT(context != NULL);
+ ASSERT(dp != NULL);
+ mp = dp->i_mount;
sf = (struct xfs_attr_shortform *)dp->i_af.if_u1.if_data;
ASSERT(sf != NULL);
if (!sf->hdr.count)
@@ -83,8 +87,9 @@ xfs_attr_shortform_list(
(dp->i_af.if_bytes + sf->hdr.count * 16) < context->bufsize)) {
for (i = 0, sfe = &sf->list[0]; i < sf->hdr.count; i++) {
if (XFS_IS_CORRUPT(context->dp->i_mount,
- !xfs_attr_namecheck(sfe->nameval,
- sfe->namelen))) {
+ !xfs_attr_namecheck(mp, sfe->nameval,
+ sfe->namelen,
+ sfe->flags))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
return -EFSCORRUPTED;
}
@@ -178,8 +183,9 @@ xfs_attr_shortform_list(
cursor->offset = 0;
}
if (XFS_IS_CORRUPT(context->dp->i_mount,
- !xfs_attr_namecheck(sbp->name,
- sbp->namelen))) {
+ !xfs_attr_namecheck(mp, sbp->name,
+ sbp->namelen,
+ sbp->flags))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
error = -EFSCORRUPTED;
goto out;
@@ -503,7 +509,8 @@ xfs_attr3_leaf_list_int(
}
if (XFS_IS_CORRUPT(context->dp->i_mount,
- !xfs_attr_namecheck(name, namelen))) {
+ !xfs_attr_namecheck(mp, name, namelen,
+ entry->flags))) {
xfs_dirattr_mark_sick(context->dp, XFS_ATTR_FORK);
return -EFSCORRUPTED;
}