summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2021-01-05 17:45:33 -0800
committerDarrick J. Wong <djwong@kernel.org>2021-03-25 17:08:35 -0700
commit15b9a9194ac8364474c84f2077b2a1ab2c549444 (patch)
tree339500d8ab6d3d10362de7a8a35a1b1d76b81bb2
parent889ecf29edfa4102eec38fcb3d6fd6be9e41f8fc (diff)
xfs: enforce metadata inode flag
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>
-rw-r--r--fs/xfs/libxfs/xfs_inode_buf.c69
-rw-r--r--fs/xfs/libxfs/xfs_inode_buf.h3
-rw-r--r--fs/xfs/scrub/common.c8
-rw-r--r--fs/xfs/scrub/inode_repair.c8
-rw-r--r--fs/xfs/xfs_icache.c2
-rw-r--r--fs/xfs/xfs_inode.c14
6 files changed, 103 insertions, 1 deletions
diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c
index b5c688a29635..adae76384b58 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.c
+++ b/fs/xfs/libxfs/xfs_inode_buf.c
@@ -421,6 +421,69 @@ xfs_dinode_verify_forkoff(
return NULL;
}
+/*
+ * Validate all the picky requirements we have for a file that claims to be
+ * filesystem metadata.
+ */
+xfs_failaddr_t
+xfs_dinode_verify_metaflag(
+ struct xfs_mount *mp,
+ struct xfs_dinode *dip,
+ uint16_t mode,
+ uint16_t flags,
+ uint64_t flags2)
+{
+ if (!xfs_sb_version_hasmetadir(&mp->m_sb))
+ return __this_address;
+
+ /* V5 filesystem only */
+ if (dip->di_version < 3)
+ return __this_address;
+
+ /* V3 inode fields that are always zero */
+ if (dip->di_flushiter || dip->di_onlink)
+ 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, group, and project IDs must be zero */
+ if (dip->di_uid || dip->di_gid ||
+ dip->di_projid_lo || dip->di_projid_hi)
+ return __this_address;
+
+ /* Immutable, sync, noatime, nodump, and nodefrag flags must be set */
+ if (!(flags & XFS_DIFLAG_IMMUTABLE))
+ return __this_address;
+ if (!(flags & XFS_DIFLAG_SYNC))
+ return __this_address;
+ if (!(flags & XFS_DIFLAG_NOATIME))
+ return __this_address;
+ if (!(flags & XFS_DIFLAG_NODUMP))
+ return __this_address;
+ if (!(flags & XFS_DIFLAG_NODEFRAG))
+ return __this_address;
+
+ /* Directories must have nosymlinks flags set */
+ if (S_ISDIR(mode) & !(flags & XFS_DIFLAG_NOSYMLINKS))
+ 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,
@@ -562,6 +625,12 @@ xfs_dinode_verify(
!xfs_sb_version_hasbigtime(&mp->m_sb))
return __this_address;
+ if (flags2 & XFS_DIFLAG2_METADATA) {
+ fa = xfs_dinode_verify_metaflag(mp, dip, mode, flags, flags2);
+ if (fa)
+ return fa;
+ }
+
return NULL;
}
diff --git a/fs/xfs/libxfs/xfs_inode_buf.h b/fs/xfs/libxfs/xfs_inode_buf.h
index ef5eaf33d146..e8ff8b9a7a77 100644
--- a/fs/xfs/libxfs/xfs_inode_buf.h
+++ b/fs/xfs/libxfs/xfs_inode_buf.h
@@ -57,6 +57,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_metaflag(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,
diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c
index 90733367a2bf..021afada3aa1 100644
--- a/fs/xfs/scrub/common.c
+++ b/fs/xfs/scrub/common.c
@@ -759,7 +759,13 @@ xchk_get_inode(
error, __return_address);
return error;
}
- if (VFS_I(ip)->i_generation != sc->sm->sm_gen) {
+
+ /*
+ * Scrubbing by handle requires the exact ino/gen pair, and is not
+ * allowed for non-directory metadata files.
+ */
+ if (VFS_I(ip)->i_generation != sc->sm->sm_gen ||
+ (xfs_is_metadata_inode(ip) && !S_ISDIR(VFS_I(ip)->i_mode))) {
xfs_irele(ip);
return -ENOENT;
}
diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
index 57aa98e95a59..279489b1937f 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -351,6 +351,14 @@ xrep_dinode_flags(
flags2 &= ~XFS_DIFLAG2_DAX;
if (!xfs_sb_version_hasbigtime(&mp->m_sb))
flags2 &= ~XFS_DIFLAG2_BIGTIME;
+ if (flags2 & XFS_DIFLAG2_METADATA) {
+ xfs_failaddr_t fa;
+
+ fa = xfs_dinode_verify_metaflag(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);
}
diff --git a/fs/xfs/xfs_icache.c b/fs/xfs/xfs_icache.c
index 35dde2ef517f..f99c4ec2f5de 100644
--- a/fs/xfs/xfs_icache.c
+++ b/fs/xfs/xfs_icache.c
@@ -871,6 +871,8 @@ xfs_imeta_iget(
goto bad_rele;
if (xfs_mode_to_ftype(VFS_I(ip)->i_mode) != ftype)
goto bad_rele;
+ if (xfs_sb_version_hasmetadir(&mp->m_sb) && !xfs_is_metadata_inode(ip))
+ goto bad_rele;
*ipp = ip;
return 0;
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 29fb4008cb14..67021d1a5054 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -587,8 +587,19 @@ xfs_lookup(
if (error)
goto out_free_name;
+ /*
+ * Make sure that a corrupt directory cannot accidentally link to a
+ * metadata file.
+ */
+ if (XFS_IS_CORRUPT(dp->i_mount, xfs_is_metadata_inode(*ipp))) {
+ error = -EFSCORRUPTED;
+ goto out_irele;
+ }
+
return 0;
+out_irele:
+ xfs_irele(*ipp);
out_free_name:
if (ci_name)
kmem_free(ci_name->name);
@@ -2622,6 +2633,9 @@ void
xfs_imeta_irele(
struct xfs_inode *ip)
{
+ ASSERT(!xfs_sb_version_hasmetadir(&ip->i_mount->m_sb) ||
+ xfs_is_metadata_inode(ip));
+
xfs_irele(ip);
}