mkbox

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 0810f8bee06d6b88b3599fcfa1deb7d735701acf
Author: Brian Swetland <swetland@frotz.net>
Date:   Sat, 26 Apr 2014 21:44:46 -0700

initial commit

Diffstat:
AMakefile | 19+++++++++++++++++++
AREADME | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amkbox.c | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 234 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,19 @@ + +all: mkbox + +mkbox: mkbox.c + $(CC) -Wall -O1 -g -o mkbox mkbox.c + +clean: + rm -f mkbox + +test: mkbox + mkdir -p sandbox databox sandbox/bin + cp /bin/busybox sandbox/bin + chmod 755 sandbox/bin/busybox + ( cd sandbox/bin && for x in $$(busybox --list) ; do ln -fs busybox $$x ; done ) + ./mkbox sandbox `pwd`/databox + +clean-test:: + rm -rf sandbox databox + diff --git a/README b/README @@ -0,0 +1,56 @@ + +mkbox: an experiment in nonprivileged sandboxing in Linux +--------------------------------------------------------- + +Requires kernel 3.12 or newer with CONFIG_USER_NS. + +Tested on Ubuntu 14.04 LTS / Linux 3.13.7. + +Huge thanks to Andy Lutomirski, who provided the roadmap in the form of +a patch to Sandstorm (https://github.com/kentonv/sandstorm/pull/12) as +well as kindly assisting with some early debugging as I fell into various +pits he had previously explored. + +Disclaimer: This is a toy built to learn about these APIs and is +certainly incomplete, likely buggy, etc. Use at your own risk. + +"make test" will build mkbox and create sandbox (which will be r/o /), +and databox (which will be r/w /data), and copy /bin/busybox into +sandbox/bin and create busybox's symlinks in there as well, then +invoke: mkbox sandbox `pwd`/databox + +computer$ id +uid=1000(somebody) gid=1000(somebody) groups=1000(somebody) + +computer$ make test +cc -Wall -O1 -g -o mkbox mkbox.c +mkdir -p sandbox databox sandbox/bin +cp /bin/busybox sandbox/bin +chmod 755 sandbox/bin/busybox +( cd sandbox/bin && for x in $(busybox --list) ; do ln -fs busybox $x ; done ) +./mkbox sandbox `pwd`/databox +mkbox: pid=14259, child=14260 + + +BusyBox v1.21.1 (Ubuntu 1:1.21.0-1ubuntu1) built-in shell (ash) +Enter 'help' for a list of built-in commands. + +/ $ id +uid=3333 gid=3333 groups=65534,3333 + +/ $ ls -l +drwxrwxr-x 2 3333 3333 4096 Apr 27 04:34 bin +drwxrwxr-x 2 3333 3333 4096 Apr 27 04:33 data +drwxr-xr-x 2 3333 3333 80 Apr 27 04:34 dev + + +/* in another shell */ + +computer$ cat /proc/14259/mounts +rootfs / rootfs rw 0 0 +/dev/root / ext3 ro,nosuid,noatime,errors=remount-ro,barrier=0,data=writeback 0 0 +/dev/root /data ext3 rw,noatime,errors=remount-ro,barrier=0,data=writeback 0 0 +sandbox-dev /dev tmpfs ro,nosuid,noexec,noatime,size=64k,nr_inodes=16,mode=755,uid=1000,gid=1000 0 0 +devtmpfs /dev/null devtmpfs rw,relatime,size=1019296k,nr_inodes=254824,mode=755 0 0 +devtmpfs /dev/zero devtmpfs rw,relatime,size=1019296k,nr_inodes=254824,mode=755 0 0 + diff --git a/mkbox.c b/mkbox.c @@ -0,0 +1,159 @@ +/* mkbox.c + * + * Copyright 2011 Brian Swetland <swetland@frotz.net> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sched.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <sys/wait.h> +#include <fcntl.h> + +int pivot_root(const char *new_root, const char *put_old); /* header? */ + +static int checkreturn(int res, const char *name, int line) { + if (res >= 0) + return res; + fprintf(stderr, "mkbox.c:%d: error: %s() failed: r=%d errno=%d (%s)\n", + line, name, res, errno, strerror(errno)); + exit(-1); +} + +#define ok(fname, arg...) checkreturn(fname(arg), #fname, __LINE__) + +int main(int argc, char **argv) { + char buf[1024]; + int fd; + const char *sandbox; + const char *databox; + uid_t uid; + gid_t gid; + pid_t cpid; + + if (argc != 3) { + fprintf(stderr, + "usage: mkbox <sandbox-rootdir> <sandbox-datadir>\n"); + return -1; + } + sandbox = argv[1]; + databox = argv[2]; + + uid = getuid(); + gid = getgid(); + + ok(unshare, CLONE_NEWPID|CLONE_NEWNS|CLONE_NEWUTS| + CLONE_NEWIPC|CLONE_NEWUSER); + + mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL); + + /* mount the sandbox on top of itself in our new namespace */ + /* it will become our root filesystem */ + ok(mount, sandbox, sandbox, NULL, MS_BIND|MS_NOSUID, NULL); + + /* step inside the to-be-root-directory */ + ok(chdir, sandbox); + + /* setup needed subdirectories */ + rmdir("data"); + rmdir("dev"); + rmdir(".oldroot"); + ok(mkdir, "data", 0755); + ok(mkdir, "dev", 0755); + ok(mkdir, ".oldroot", 0755); + + /* mount read-write data volume */ + ok(mount, databox, "data", NULL, MS_BIND|MS_NOSUID, NULL); + + /* mount a tmpfs for dev */ + ok(mount, "sandbox-dev", "dev", "tmpfs", + MS_NOSUID|MS_NOEXEC|MS_NOATIME, + "size=64k,nr_inodes=16,mode=755"); + + /* populate bare minimum device nodes */ + /* create bind points */ + ok(close, ok(open, "dev/null", O_WRONLY|O_CREAT, 0666)); + ok(close, ok(open, "dev/zero", O_WRONLY|O_CREAT, 0666)); + + /* bind mount the device nodes we want */ + ok(mount, "/dev/null", "dev/null", NULL, MS_BIND, NULL); + ok(mount, "/dev/zero", "dev/zero", NULL, MS_BIND, NULL); + + /* note: MS_RDONLY does not work when doing the initial bind */ + ok(mount, "dev", "dev", NULL, + MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_REMOUNT|MS_NOATIME|MS_BIND, + NULL); + + /* map UID/GID 3333/3333 to outer UID/GID */ + sprintf(buf, "3333 %d 1\n", uid); + fd = ok(open, "/proc/self/uid_map", O_WRONLY); + ok(write, fd, buf, strlen(buf)); + ok(close, fd); + + sprintf(buf, "3333 %d 1\n", gid); + fd = ok(open, "/proc/self/gid_map", O_WRONLY); + ok(write, fd, buf, strlen(buf)); + ok(close, fd); + + /* initially we're nobody, change to 3333 */ + ok(setresgid, 3333, 3333, 3333); + ok(setresuid, 3333, 3333, 3333); + + /* sandbox becomes our new root, detach the old one */ + ok(pivot_root, ".", ".oldroot"); + ok(umount2, ".oldroot", MNT_DETACH); + unlink(".oldroot"); + + /* remount root to finalize permissions */ + ok(mount, "/", "/", NULL, + MS_RDONLY|MS_NOSUID|MS_REMOUNT|MS_NOATIME|MS_BIND|MS_RDONLY, + NULL); + + /* we must fork to become pid 1 in the new pid namespace */ + cpid = ok(fork); + + if (cpid == 0) { + if (getpid() != 1) { + fprintf(stderr, "mkbox child pid != 1?!\n"); + return -1; + } + ok(execl, "/bin/sh", "/bin/sh", NULL); + exit(0); + } + + fprintf(stderr, "mkbox: pid=%d, child=%d\n", getpid(), cpid); + for (;;) { + int status = 0; + pid_t pid = wait(&status); + if (pid < 0) { + fprintf(stderr, "mkbox: wait() errno=%d\n", errno); + continue; + } + fprintf(stderr, "mkbox: proc %d exited with status %d\n", + pid, status); + if (pid == cpid) + break; + } + + fprintf(stderr, "mkbox: exiting\n"); + return 0; +}