Commit 7297fd0b authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

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: default avatarDarrick J. Wong <djwong@kernel.org>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent c555dd9b
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -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;
}

+3 −0
Original line number Diff line number Diff line
@@ -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,
+11 −0
Original line number Diff line number Diff line
@@ -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,
+8 −2
Original line number Diff line number Diff line
@@ -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;

+24 −2
Original line number Diff line number Diff line
@@ -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;

Loading