diff options
-rw-r--r-- | fs/xfs/libxfs/xfs_inode_buf.c | 69 | ||||
-rw-r--r-- | fs/xfs/libxfs/xfs_inode_buf.h | 3 | ||||
-rw-r--r-- | fs/xfs/scrub/common.c | 8 | ||||
-rw-r--r-- | fs/xfs/scrub/inode_repair.c | 8 | ||||
-rw-r--r-- | fs/xfs/xfs_icache.c | 2 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.c | 14 |
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); } |