Code change:
consists mostly of syscalls (including ). There are even Ruby-specific workarounds. C/C++ would be a better language for the task. This should also reduce closure size and startup time of FHS-sandboxed apps (Ruby needs to load itself and its libraries from disk).
Can't we just use to setup the chroot?
I'm not sure if it's capable of doing all things that we need for this, but it should be worth checking. Can it run a container as a process (not as a systemd service), and can it be used not by a superuser?
needs root privileges. btw has C code to set them up, the rest is probably bash only?
D: A pity. Well, let's continue with the old plan then. Unfortunately we can't do much more in Bash than we already do because we need to do mounts in the same process we set the namespaces, in children necessary privileges are already gone. I'm not completely sure about this but I remember that when I tried to write this in Bash completely long time ago (using for setting up namespaces mounts and chroot were a problem.
How about using this one?
bind mounts, sysfs, tmpfs and procfs should be
I can confirm that these mounts are possible from using unprivileged lxc containers. But you cannot bind mount something from the host this way, because it needs to be done after . UPDATE: ok even that should be possible if the correct order is maintained: unshare -mount -chroot.
Oh, I should look at it! cc in case he's interested in this thread. I think that checks if the user is root -- that's why this doesn't work. This means that we need to call by ourselves... (I was going to hit "Comment" but you were faster ~_^) I expect that LXC containers do user maps such that you have UID 0 in new userns -- we, however, need to retain user uid.
why do we have to retain user id?
Multiple user apps use uid and username for their purposes -- we don't want to lie to them. I can't give a complete example (I remember that Steam breaks) but for example we have directories which apps can find based on uid.
I have another suggestion: the Go programming language. Bear in mind that I'm generally not a super big fan of Go (in fact, while I use Go extensively at work, I generally use Haskell for my personal projects); however, I think that a higher level, simple (in terms of specification) language would be conducive to maintenance and further development. The binaries should be pretty small, and the closure should be pretty small, too. I might give that a go (no pun intended) and see what we think.
Several possible issues: need native and then before any calls to pthreads (or you'll get or something close); I remember that Go has a runtime so there may be problems; need several syscalls which may not be readily available, namely , and others. In Ruby I import them from libc dynamically and redefine constants from C by hand, but doing this (especially redefining constants) is one of the reasons I want to move from Ruby in the first place. However these are only potential problems -- I don't know if they actually exist! I'm actually interested in how this turns out.
You cannot use clone() properly because of the language runtime. That's why docker uses a little C for that job.
Ah, you must be referring to this from : I'll need to give that a thorough reading before I understand why the runtime gets in the way (unless, perhaps, you have an explanation of why/how pthreads mucks things up).
Oh, I'm guessing that's probably stuff like this: Though, if there are other details, I'd love to hear them :smile:.
If you refer to my remark about fork and pthreads, it's actually 's limitation. You can look at how I workaround this in Ruby by calling imported instead of Ruby's builtin. You will know if you encounter it -- will just fail. I think the best way to find out is with an experiment. (notice that I don't know about Go runtime and haven't even used Go before, so, as I underline again, those might be non-issues)
Interesting. I referred to the , but I don't see any mention of failure when used with pthreads. I don't doubt you; I'd just like to know how all this stuff works.
No problem even if these were doubts ~^. From man page:
Ah, dunno why I didn't try reading the man page -- thanks for pointing that out :wink:.
Anybody done any more work on this? Also, this should probably be tagged with security, due to
a rewrite in a different language will not solve your grsecurity problems. I also do not see how the rewrite discussed here is related to security.
Anything related to grsecurity is security-related. A rewrite into a compiled language means that a setuid wrapper can be created, which means I don't have to run shit as root.
You can drop your privileges via right after you enter the environment as -- it's less convenient but you get roughly what you want.

Related discussion:
text: <ins> #define _GNU_SOURCE #include <errno.h> #include <error.h> #define errorf(status, fmt, ...)                                                error_at_line(status, errno, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #include <dirent.h> #include <ftw.h> #include <sched.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sysexits.h> #include <unistd.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/wait.h> char *env_whitelist[] = {"TERM", "DISPLAY", "XAUTHORITY", "HOME", "XDG_RUNTIME_DIR", "LANG", "SSL_CERT_FILE", "DBUS_SESSION_BUS_ADDRESS"}; char **env_build(char *names[], size_t len) { char *env, **ret = malloc((len + 1) * sizeof(char *)), **ptr = ret; for (size_t i = 0; i < len; i++) { if ((env = getenv(names[i]))) { if (asprintf(ptr++, "%s=%s", names[i], env) < 0) errorf(EX_OSERR, "asprintf"); } } *ptr = NULL; return ret; } struct bind { char *from; char *to; }; struct bind binds[] = {{"/", "host"},   {"/proc", "proc"}, {"/sys", "sys"}, {"/nix", "nix"}, {"/tmp", "tmp"},   {"/var", "var"}, {"/run", "run"}, {"/dev", "dev"},   {"/home", "home"}}; void bind(struct bind *bind) { DIR *src = opendir(bind->from); if (src) { if (closedir(src) < 0) errorf(EX_IOERR, "closedir"); if (mkdir(bind->to, 0755) < 0) errorf(EX_IOERR, "mkdir"); if (mount(bind->from, bind->to, "bind", MS_BIND | MS_REC, NULL) < 0) errorf(EX_OSERR, "mount"); } else { // https://github.com/NixOS/nixpkgs/issues/31104 if (errno != ENOENT) errorf(EX_OSERR, "opendir"); } } void spitf(char *path, char *fmt, ...) { va_list args; va_start(args, fmt); FILE *f = fopen(path, "w"); if (f == NULL) errorf(EX_IOERR, "spitf(%s): fopen", path); if (vfprintf(f, fmt, args) < 0) errorf(EX_IOERR, "spitf(%s): vfprintf", path); if (fclose(f) < 0) errorf(EX_IOERR, "spitf(%s): fclose", path); } int nftw_rm(const char *path, const struct stat *sb, int type, struct FTW *ftw) { if (remove(path) < 0) errorf(EX_IOERR, "nftw_rm"); return 0; } #define LEN(x) sizeof(x) / sizeof(*x) int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s command [arguments...]n" "Requires Linux kernel >= 3.19 with CONFIG_USER_NS.n", argv[0]); exit(EX_USAGE); } char tmpl[] = "/tmp/chrootenvXXXXXX"; char *root = mkdtemp(tmpl); if (root == NULL) errorf(EX_IOERR, "mkdtemp"); // Don't make root private so that privilege drops inside chroot are possible: if (chmod(root, 0755) < 0) errorf(EX_IOERR, "chmod"); pid_t cpid = fork(); if (cpid < 0) errorf(EX_OSERR, "fork"); if (cpid == 0) { uid_t uid = getuid(); gid_t gid = getgid(); // If we are root, no need to create new user namespace. if (uid == 0) { if (unshare(CLONE_NEWNS) < 0) errorf(EX_OSERR, "unshare() failed: You may have an old kernel or have CLONE_NEWUSER disabled by your distribution security settings."); // Mark all mounted filesystems as slave so changes // don't propagate to the parent mount namespace. if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) < 0) errorf(EX_OSERR, "mount"); } else { // Create new mount and user namespaces. CLONE_NEWUSER // requires a program to be non-threaded. if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) errorf(EX_OSERR, "unshare"); // Map users and groups to the parent namespace. // setgroups is only available since Linux 3.19: spitf("/proc/self/setgroups", "deny"); spitf("/proc/self/uid_map", "%d %d 1", uid, uid); spitf("/proc/self/gid_map", "%d %d 1", gid, gid); } if (chdir(root) < 0) errorf(EX_IOERR, "chdir"); for (size_t i = 0; i < LEN(binds); i++) bind(&binds[i]); if (chroot(root) < 0) errorf(EX_OSERR, "chroot"); if (chdir("/") < 0) errorf(EX_OSERR, "chdir"); argv++; if (execvpe(*argv, argv, env_build(env_whitelist, LEN(env_whitelist))) < 0) errorf(EX_OSERR, "execvpe"); } int status; if (waitpid(cpid, &status, 0) < 0) errorf(EX_OSERR, "waitpid"); if (nftw(root, nftw_rm, getdtablesize(), FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) errorf(EX_IOERR, "nftw"); if (WIFEXITED(status)) return WEXITSTATUS(status); else if (WIFSIGNALED(status)) kill(getpid(), WTERMSIG(status)); return EX_OSERR; } </ins>