[PATCH 24/29] xfs: teach online repair to evaluate fsverity xattrs

Darrick J. Wong
2024-03-30T00:42:20Z

From: Darrick J. Wong <djwong@kernel.org>

Teach online repair to check for unused fsverity metadata and purge it
on reconstruction.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
fs/xfs/scrub/attr.c | 139 ++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/scrub/attr.h | 6 ++
fs/xfs/scrub/attr_repair.c | 50 ++++++++++++++++
fs/xfs/scrub/trace.c | 1
fs/xfs/scrub/trace.h | 31 ++++++++++
5 files changed, 226 insertions(+), 1 deletion(-)

diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c
index 2e8a2b2e82fbd..be121625c14f0 100644
--- a/fs/xfs/scrub/attr.c
+++ b/fs/xfs/scrub/attr.c
@@ -18,6 +18,7 @@
#include "xfs_attr_leaf.h"
#include "xfs_attr_sf.h"
#include "xfs_parent.h"
+#include "xfs_verity.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/dabtree.h"
@@ -25,6 +26,8 @@
#include "scrub/listxattr.h"
#include "scrub/repair.h"

+#include <linux/fsverity.h>
+
/* Free the buffers linked from the xattr buffer. */
static void
xchk_xattr_buf_cleanup(
@@ -126,6 +129,53 @@ xchk_setup_xattr_buf(
return 0;
}

+#ifdef CONFIG_FS_VERITY
+/*
+ * Obtain merkle tree geometry information for a verity file so that we can
+ * perform sanity checks of the fsverity xattrs.
+ */
+STATIC int
+xchk_xattr_setup_verity(
+ struct xfs_scrub *sc)
+{
+ struct xchk_xattr_buf *ab;
+ int error;
+
+ /*
+ * Drop the ILOCK and the transaction because loading the fsverity
+ * metadata will call into the xattr code. S_VERITY is enabled with
+ * IOLOCK_EXCL held, so it should not change here.
+ */
+ xchk_iunlock(sc, XFS_ILOCK_EXCL);
+ xchk_trans_cancel(sc);
+
+ error = xchk_setup_xattr_buf(sc, 0);
+ if (error)
+ return error;
+
+ ab = sc->buf;
+ error = fsverity_merkle_tree_geometry(VFS_I(sc->ip),
+ &ab->merkle_blocksize, &ab->merkle_tree_size);
+ if (error == -ENODATA || error == -EFSCORRUPTED) {
+ /* fsverity metadata corrupt, cannot complete checks */
+ xchk_set_incomplete(sc);
+ ab->merkle_blocksize = 0;
+ error = 0;
+ }
+ if (error)
+ return error;
+
+ error = xchk_trans_alloc(sc, 0);
+ if (error)
+ return error;
+
+ xchk_ilock(sc, XFS_ILOCK_EXCL);
+ return 0;
+}
+#else
+# define xchk_xattr_setup_verity(...) (0)
+#endif /* CONFIG_FS_VERITY */
+
/* Set us up to scrub an inode's extended attributes. */
int
xchk_setup_xattr(
@@ -150,9 +200,89 @@ xchk_setup_xattr(
return error;
}

- return xchk_setup_inode_contents(sc, 0);
+ error = xchk_setup_inode_contents(sc, 0);
+ if (error)
+ return error;
+
+ if (IS_VERITY(VFS_I(sc->ip))) {
+ error = xchk_xattr_setup_verity(sc);
+ if (error)
+ return error;
+ }
+
+ return error;
}

+#ifdef CONFIG_FS_VERITY
+/* Check the merkle tree xattrs. */
+STATIC void
+xchk_xattr_verity(
+ struct xfs_scrub *sc,
+ xfs_dablk_t blkno,
+ const unsigned char *name,
+ unsigned int namelen,
+ unsigned int valuelen)
+{
+ struct xchk_xattr_buf *ab = sc->buf;
+
+ /* Non-verity filesystems should never have verity xattrs. */
+ if (!xfs_has_verity(sc->mp)) {
+ xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, blkno);
+ return;
+ }
+
+ /*
+ * Any verity metadata on a non-verity file are leftovers from a
+ * previous attempt to enable verity.
+ */
+ if (!IS_VERITY(VFS_I(sc->ip))) {
+ xchk_ino_set_preen(sc, sc->ip->i_ino);
+ return;
+ }
+
+ /* Zero blocksize occurs if we couldn't load the merkle tree data. */
+ if (ab->merkle_blocksize == 0)
+ return;
+
+ switch (namelen) {
+ case sizeof(struct xfs_merkle_key):
+ /* Oversized blocks are not allowed */
+ if (valuelen > ab->merkle_blocksize) {
+ xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, blkno);
+ return;
+ }
+ break;
+ case XFS_VERITY_DESCRIPTOR_NAME_LEN:
+ /* Has to match the descriptor xattr name */
+ if (memcmp(name, XFS_VERITY_DESCRIPTOR_NAME, namelen))
+ xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, blkno);
+ return;
+ default:
+ xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, blkno);
+ return;
+ }
+
+ /*
+ * Merkle tree blocks beyond the end of the tree are leftovers from
+ * a previous failed attempt to enable verity.
+ */
+ if (xfs_merkle_key_from_disk(name, namelen) >= ab->merkle_tree_size)
+ xchk_ino_set_preen(sc, sc->ip->i_ino);
+}
+#else
+static void
+xchk_xattr_verity(
+ struct xfs_scrub *sc,
+ xfs_dablk_t blkno,
+ const unsigned char *name,
+ unsigned int namelen,
+ unsigned int valuelen)
+{
+ /* Should never see verity xattrs when verity is not enabled. */
+ xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, blkno);
+}
+#endif /* CONFIG_FS_VERITY */
+
/* Extended Attributes */

/*
@@ -211,6 +341,13 @@ xchk_xattr_actor(
return -ECANCELED;
}

+ /* Check verity xattr geometry */
+ if (attr_flags & XFS_ATTR_VERITY) {
+ xchk_xattr_verity(sc, args.blkno, name, namelen, valuelen);
+ if (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT)
+ return -ECANCELED;
+ }
+
/*
* Local and shortform xattr values are stored in the attr leaf block,
* so we don't need to retrieve the value from a remote block to detect
diff --git a/fs/xfs/scrub/attr.h b/fs/xfs/scrub/attr.h
index 7db58af56646b..40b8c12384f55 100644
--- a/fs/xfs/scrub/attr.h
+++ b/fs/xfs/scrub/attr.h
@@ -22,6 +22,12 @@ struct xchk_xattr_buf {
/* Memory buffer used to extract xattr values. */
void *value;
size_t value_sz;
+
+#ifdef CONFIG_FS_VERITY
+ /* Geometry of the merkle tree attached to this verity file. */
+ u64 merkle_tree_size;
+ unsigned int merkle_blocksize;
+#endif
};

bool xchk_xattr_set_map(struct xfs_scrub *sc, unsigned long *map,
diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c
index 7c5e52ceae82e..040138610ae94 100644
--- a/fs/xfs/scrub/attr_repair.c
+++ b/fs/xfs/scrub/attr_repair.c
@@ -29,6 +29,7 @@
#include "xfs_exchrange.h"
#include "xfs_acl.h"
#include "xfs_parent.h"
+#include "xfs_verity.h"
#include "scrub/xfs_scrub.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
@@ -159,6 +160,44 @@ xrep_setup_xattr(
return xrep_tempfile_create(sc, S_IFREG);
}

+#ifdef CONFIG_FS_VERITY
+static int
+xrep_xattr_want_salvage_verity(
+ struct xrep_xattr *rx,
+ const void *name,
+ int namelen,
+ int valuelen)
+{
+ struct xchk_xattr_buf *ab = rx->sc->buf;
+
+ if (!xfs_has_verity(rx->sc->mp))
+ return false;
+ if (!IS_VERITY(VFS_I(rx->sc->ip)))
+ return false;
+
+ switch (namelen) {
+ case sizeof(struct xfs_merkle_key):
+ /* Oversized blocks are not allowed */
+ if (valuelen > ab->merkle_blocksize)
+ return false;
+ break;
+ case XFS_VERITY_DESCRIPTOR_NAME_LEN:
+ /* Has to match the descriptor xattr name */
+ return !memcmp(name, XFS_VERITY_DESCRIPTOR_NAME, namelen);
+ default:
+ return false;
+ }
+
+ /*
+ * Merkle tree blocks beyond the end of the tree are leftovers from
+ * a previous failed attempt to enable verity.
+ */
+ return xfs_merkle_key_from_disk(name, namelen) < ab->merkle_tree_size;
+}
+#else
+# define xrep_xattr_want_salvage_verity(...) (false)
+#endif /* CONFIG_FS_VERITY */
+
/*
* Decide if we want to salvage this attribute. We don't bother with
* incomplete or oversized keys or values. The @value parameter can be null
@@ -183,6 +222,9 @@ xrep_xattr_want_salvage(
return false;
if (attr_flags & XFS_ATTR_PARENT)
return xfs_parent_valuecheck(rx->sc->mp, value, valuelen);
+ if (attr_flags & XFS_ATTR_VERITY)
+ return xrep_xattr_want_salvage_verity(rx, name, namelen,
+ valuelen);

return true;
}
@@ -216,6 +258,11 @@ xrep_xattr_salvage_key(

trace_xrep_xattr_salvage_pptr(rx->sc->ip, flags, name,
key.namelen, value, valuelen);
+ } else if (flags & XFS_ATTR_VERITY) {
+ key.namelen = namelen;
+
+ trace_xrep_xattr_salvage_verity(rx->sc->ip, flags, name,
+ key.namelen, value, valuelen);
} else {
while (i < namelen && name[i] != 0)
i++;
@@ -667,6 +714,9 @@ xrep_xattr_insert_rec(
trace_xrep_xattr_insert_pptr(rx->sc->tempip, key->flags,
ab->name, key->namelen, ab->value,
key->valuelen);
+ else if (key->flags & XFS_ATTR_VERITY)
+ trace_xrep_xattr_insert_verity(rx->sc->ip, key->flags, ab->name,
+ key->namelen, ab->value, key->valuelen);
else
trace_xrep_xattr_insert_rec(rx->sc->tempip, key->flags,
ab->name, key->namelen, key->valuelen);
diff --git a/fs/xfs/scrub/trace.c b/fs/xfs/scrub/trace.c
index 6d8acb2f63d8a..69c234f2a4b32 100644
--- a/fs/xfs/scrub/trace.c
+++ b/fs/xfs/scrub/trace.c
@@ -22,6 +22,7 @@
#include "xfs_parent.h"
#include "xfs_imeta.h"
#include "xfs_rtgroup.h"
+#include "xfs_verity.h"
#include "scrub/scrub.h"
#include "scrub/xfile.h"
#include "scrub/xfarray.h"
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index 6fd91c13f25ff..787f409799a06 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -3069,6 +3069,37 @@ DEFINE_EVENT(xrep_pptr_salvage_class, name, \
DEFINE_XREP_PPTR_SALVAGE_EVENT(xrep_xattr_salvage_pptr);
DEFINE_XREP_PPTR_SALVAGE_EVENT(xrep_xattr_insert_pptr);

+DECLARE_EVENT_CLASS(xrep_verity_salvage_class,
+ TP_PROTO(struct xfs_inode *ip, unsigned int flags, const void *name,
+ unsigned int namelen, const void *value, unsigned int valuelen),
+ TP_ARGS(ip, flags, name, namelen, value, valuelen),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+ __field(unsigned long long, merkle_off)
+ ),
+ TP_fast_assign(
+ __entry->dev = ip->i_mount->m_super->s_dev;
+ __entry->ino = ip->i_ino;
+ if (namelen == sizeof(struct xfs_merkle_key))
+ __entry->merkle_off = xfs_merkle_key_from_disk(name,
+ namelen);
+ else
+ __entry->merkle_off = -1ULL;
+ ),
+ TP_printk("dev %d:%d ino 0x%llx merkle_off 0x%llx",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->merkle_off)
+)
+#define DEFINE_XREP_VERITY_SALVAGE_EVENT(name) \
+DEFINE_EVENT(xrep_verity_salvage_class, name, \
+ TP_PROTO(struct xfs_inode *ip, unsigned int flags, const void *name, \
+ unsigned int namelen, const void *value, unsigned int valuelen), \
+ TP_ARGS(ip, flags, name, namelen, value, valuelen))
+DEFINE_XREP_VERITY_SALVAGE_EVENT(xrep_xattr_salvage_verity);
+DEFINE_XREP_VERITY_SALVAGE_EVENT(xrep_xattr_insert_verity);
+
TRACE_EVENT(xrep_xattr_class,
TP_PROTO(struct xfs_inode *ip, struct xfs_inode *arg_ip),
TP_ARGS(ip, arg_ip),