mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
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> Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
parent
c555dd9b8c
commit
7297fd0beb
@ -19,6 +19,7 @@
|
||||
#include "xfs_ialloc.h"
|
||||
#include "xfs_dir2.h"
|
||||
#include "xfs_health.h"
|
||||
#include "xfs_metafile.h"
|
||||
|
||||
#include <linux/iversion.h>
|
||||
|
||||
@ -489,6 +490,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,
|
||||
@ -673,6 +737,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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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_is_sb_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_is_sb_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;
|
||||
|
||||
|
@ -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_is_sb_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_is_sb_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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -834,7 +834,8 @@ out_error_or_again:
|
||||
/*
|
||||
* Get a metadata inode.
|
||||
*
|
||||
* The metafile type must match the file mode exactly.
|
||||
* The metafile type must match the file mode exactly, and for files in the
|
||||
* metadata directory tree, it must match the inode's metatype exactly.
|
||||
*/
|
||||
int
|
||||
xfs_trans_metafile_iget(
|
||||
@ -863,13 +864,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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user