]> www.infradead.org Git - users/hch/xfs.git/commitdiff
xfs: enforce metadata inode flag
authorDarrick J. Wong <djwong@kernel.org>
Wed, 7 Aug 2024 22:54:11 +0000 (15:54 -0700)
committerChristoph Hellwig <hch@lst.de>
Sun, 22 Sep 2024 06:07:17 +0000 (08:07 +0200)
Add checks for the metadata inode flag so that we don't ever leak
metadata inodes out to userspace, and we don't ever try to read a
regular inode as metadata.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/libxfs/xfs_inode_buf.c
fs/xfs/libxfs/xfs_inode_buf.h
fs/xfs/libxfs/xfs_metafile.h
fs/xfs/scrub/common.c
fs/xfs/scrub/inode.c
fs/xfs/scrub/inode_repair.c
fs/xfs/xfs_icache.c
fs/xfs/xfs_inode.c

index cdd6ed4279649d7bc80cffcd483dea36d802729d..bdccdf81ac9eba64a4b41df75e005ad181d8f837 100644 (file)
@@ -19,6 +19,7 @@
 #include "xfs_ialloc.h"
 #include "xfs_dir2.h"
 #include "xfs_health.h"
+#include "xfs_metafile.h"
 
 #include <linux/iversion.h>
 
@@ -488,6 +489,69 @@ xfs_dinode_verify_nrext64(
        return NULL;
 }
 
+/*
+ * Validate all the picky requirements we have for a file that claims to be
+ * filesystem metadata.
+ */
+xfs_failaddr_t
+xfs_dinode_verify_metadir(
+       struct xfs_mount        *mp,
+       struct xfs_dinode       *dip,
+       uint16_t                mode,
+       uint16_t                flags,
+       uint64_t                flags2)
+{
+       if (!xfs_has_metadir(mp))
+               return __this_address;
+
+       /* V5 filesystem only */
+       if (dip->di_version < 3)
+               return __this_address;
+
+       if (be16_to_cpu(dip->di_metatype) >= XFS_METAFILE_MAX)
+               return __this_address;
+
+       /* V3 inode fields that are always zero */
+       if ((flags2 & XFS_DIFLAG2_NREXT64) && dip->di_nrext64_pad)
+               return __this_address;
+       if (!(flags2 & XFS_DIFLAG2_NREXT64) && dip->di_flushiter)
+               return __this_address;
+
+       /* Metadata files can only be directories or regular files */
+       if (!S_ISDIR(mode) && !S_ISREG(mode))
+               return __this_address;
+
+       /* They must have zero access permissions */
+       if (mode & 0777)
+               return __this_address;
+
+       /* DMAPI event and state masks are zero */
+       if (dip->di_dmevmask || dip->di_dmstate)
+               return __this_address;
+
+       /*
+        * User and group IDs must be zero.  The project ID is used for
+        * grouping inodes.  Metadata inodes are never accounted to quotas.
+        */
+       if (dip->di_uid || dip->di_gid)
+               return __this_address;
+
+       /* Mandatory inode flags must be set */
+       if (S_ISDIR(mode)) {
+               if ((flags & XFS_METADIR_DIFLAGS) != XFS_METADIR_DIFLAGS)
+                       return __this_address;
+       } else {
+               if ((flags & XFS_METAFILE_DIFLAGS) != XFS_METAFILE_DIFLAGS)
+                       return __this_address;
+       }
+
+       /* dax flags2 must not be set */
+       if (flags2 & XFS_DIFLAG2_DAX)
+               return __this_address;
+
+       return NULL;
+}
+
 xfs_failaddr_t
 xfs_dinode_verify(
        struct xfs_mount        *mp,
@@ -672,6 +736,12 @@ xfs_dinode_verify(
            !xfs_has_bigtime(mp))
                return __this_address;
 
+       if (flags2 & XFS_DIFLAG2_METADATA) {
+               fa = xfs_dinode_verify_metadir(mp, dip, mode, flags, flags2);
+               if (fa)
+                       return fa;
+       }
+
        return NULL;
 }
 
index 585ed5a110af4e72a65783adf91639cc09686ea7..8d43d2641c73280301a6827a076f9363eafe2c01 100644 (file)
@@ -28,6 +28,9 @@ int   xfs_inode_from_disk(struct xfs_inode *ip, struct xfs_dinode *from);
 
 xfs_failaddr_t xfs_dinode_verify(struct xfs_mount *mp, xfs_ino_t ino,
                           struct xfs_dinode *dip);
+xfs_failaddr_t xfs_dinode_verify_metadir(struct xfs_mount *mp,
+               struct xfs_dinode *dip, uint16_t mode, uint16_t flags,
+               uint64_t flags2);
 xfs_failaddr_t xfs_inode_validate_extsize(struct xfs_mount *mp,
                uint32_t extsize, uint16_t mode, uint16_t flags);
 xfs_failaddr_t xfs_inode_validate_cowextsize(struct xfs_mount *mp,
index 60fe1890611277bdc79098688f594a305d56ee7c..c66b0c51b461a8f0ed1988fa837e5ec63924e019 100644 (file)
@@ -6,6 +6,17 @@
 #ifndef __XFS_METAFILE_H__
 #define __XFS_METAFILE_H__
 
+/* All metadata files must have these flags set. */
+#define XFS_METAFILE_DIFLAGS   (XFS_DIFLAG_IMMUTABLE | \
+                                XFS_DIFLAG_SYNC | \
+                                XFS_DIFLAG_NOATIME | \
+                                XFS_DIFLAG_NODUMP | \
+                                XFS_DIFLAG_NODEFRAG)
+
+/* All metadata directories must have these flags set. */
+#define XFS_METADIR_DIFLAGS    (XFS_METAFILE_DIFLAGS | \
+                                XFS_DIFLAG_NOSYMLINKS)
+
 /* Code specific to kernel/userspace; must be provided externally. */
 
 int xfs_trans_metafile_iget(struct xfs_trans *tp, xfs_ino_t ino,
index b71768c2a8c129f953a7f41b96d13e410031944d..9d74c315418ffbef5d7ef6ac96d20f366be0c50d 100644 (file)
@@ -947,9 +947,15 @@ xchk_iget_for_scrubbing(
        if (sc->sm->sm_ino == 0 || sc->sm->sm_ino == ip_in->i_ino)
                return xchk_install_live_inode(sc, ip_in);
 
-       /* Reject internal metadata files and obviously bad inode numbers. */
-       if (xfs_internal_inum(mp, sc->sm->sm_ino))
+       /*
+        * On pre-metadir filesystems, reject internal metadata files.  For
+        * metadir filesystems, limited scrubbing of any file in the metadata
+        * directory tree by handle is allowed, because that is the only way to
+        * validate the lack of parent pointers in the sb-root metadata inodes.
+        */
+       if (!xfs_has_metadir(mp) && xfs_internal_inum(mp, sc->sm->sm_ino))
                return -ENOENT;
+       /* Reject obviously bad inode numbers. */
        if (!xfs_verify_ino(sc->mp, sc->sm->sm_ino))
                return -ENOENT;
 
index ec2c694c4083f8a438fb5afc44de8c99350014dd..45222552a51ccb659781272af1ca690a1e363a02 100644 (file)
@@ -60,6 +60,22 @@ xchk_install_handle_iscrub(
        if (error)
                return error;
 
+       /*
+        * Don't allow scrubbing by handle of any non-directory inode records
+        * in the metadata directory tree.  We don't know if any of the scans
+        * launched by this scrubber will end up indirectly trying to lock this
+        * file.
+        *
+        * Scrubbers of inode-rooted metadata files (e.g. quota files) will
+        * attach all the resources needed to scrub the inode and call
+        * xchk_inode directly.  Userspace cannot call this directly.
+        */
+       if (xfs_is_metadir_inode(ip) && !S_ISDIR(VFS_I(ip)->i_mode)) {
+               xchk_irele(sc, ip);
+               sc->ip = NULL;
+               return -ENOENT;
+       }
+
        return xchk_prepare_iscrub(sc);
 }
 
@@ -94,9 +110,15 @@ xchk_setup_inode(
                return xchk_prepare_iscrub(sc);
        }
 
-       /* Reject internal metadata files and obviously bad inode numbers. */
-       if (xfs_internal_inum(mp, sc->sm->sm_ino))
+       /*
+        * On pre-metadir filesystems, reject internal metadata files.  For
+        * metadir filesystems, limited scrubbing of any file in the metadata
+        * directory tree by handle is allowed, because that is the only way to
+        * validate the lack of parent pointers in the sb-root metadata inodes.
+        */
+       if (!xfs_has_metadir(mp) && xfs_internal_inum(mp, sc->sm->sm_ino))
                return -ENOENT;
+       /* Reject obviously bad inode numbers. */
        if (!xfs_verify_ino(sc->mp, sc->sm->sm_ino))
                return -ENOENT;
 
index fdc2f0d058d557546687268807ac784d992adcc5..1a7d2dfc2f405d19b1f4ac3444fc43e8e80d890d 100644 (file)
@@ -568,6 +568,16 @@ xrep_dinode_flags(
                dip->di_nrext64_pad = 0;
        else if (dip->di_version >= 3)
                dip->di_v3_pad = 0;
+
+       if (flags2 & XFS_DIFLAG2_METADATA) {
+               xfs_failaddr_t  fa;
+
+               fa = xfs_dinode_verify_metadir(sc->mp, dip, mode, flags,
+                               flags2);
+               if (fa)
+                       flags2 &= ~XFS_DIFLAG2_METADATA;
+       }
+
        dip->di_flags = cpu_to_be16(flags);
        dip->di_flags2 = cpu_to_be64(flags2);
 }
index 3b1bc1d54a83f98668ed765b3d4989d8b80e47f0..0a2bdd936826eeb579c4610e22f4e22fcd85f074 100644 (file)
@@ -861,13 +861,20 @@ xfs_trans_metafile_iget(
                mode = S_IFREG;
        if (inode_wrong_type(VFS_I(ip), mode))
                goto bad_rele;
+       if (xfs_has_metadir(mp)) {
+               if (!xfs_is_metadir_inode(ip))
+                       goto bad_rele;
+               if (metafile_type != ip->i_metatype)
+                       goto bad_rele;
+       }
 
        *ipp = ip;
        return 0;
 bad_rele:
        xfs_irele(ip);
 whine:
-       xfs_err(mp, "metadata inode 0x%llx is corrupt", ino);
+       xfs_err(mp, "metadata inode 0x%llx type %u is corrupt", ino,
+                       metafile_type);
        return -EFSCORRUPTED;
 }
 
index 0ad0e4eab3d9aed9fe962f34ff27472aa658bf72..4c6cda2a8a89916492ca2eba892e830c00aad7f2 100644 (file)
@@ -555,8 +555,19 @@ xfs_lookup(
        if (error)
                goto out_free_name;
 
+       /*
+        * Fail if a directory entry in the regular directory tree points to
+        * a metadata file.
+        */
+       if (XFS_IS_CORRUPT(dp->i_mount, xfs_is_metadir_inode(*ipp))) {
+               error = -EFSCORRUPTED;
+               goto out_irele;
+       }
+
        return 0;
 
+out_irele:
+       xfs_irele(*ipp);
 out_free_name:
        if (ci_name)
                kfree(ci_name->name);