Article posted on: 2023-02-21 21:53
Last edited on: 2023-02-21 23:15
Written by: Sylvain Gauthier
Here is a simple way to containerize your program into a directory, that is more
secure than chroot
because it uses pivot_root
and doesn’t require root
privileges thanks to Linux' namespaces.
See this great article for more details on how namespaces work.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <linux/sched.h>
/* see pivot_root(2) man page example for a detailed description of all the
* steps
*/
int dir_jail(const char* root) {
char* oldRoot = "/old_root";
char oldPath[1024];
int ok = 0;
if (strlen(root) + strlen(oldRoot) + 1 > sizeof(oldPath)) {
fprintf(stderr, "Error: path too long\n");
}
snprintf(oldPath, sizeof(oldPath), "%s%s", root, oldRoot);
if (syscall(SYS_unshare, CLONE_NEWUSER | CLONE_NEWNS) != 0) {
fprintf(stderr, "unshare: %s\n", strerror(errno));
} else if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) {
fprintf(stderr, "mount: %s\n", strerror(errno));
} else if (mount(root, root, NULL, MS_BIND, NULL) != 0) {
fprintf(stderr, "bind: %s\n", strerror(errno));
} else if (mkdir(oldPath, 0777) != 0) {
fprintf(stderr, "mkdir: failed to create %s: %s\n",
oldPath, strerror(errno));
} else if (syscall(SYS_pivot_root, root, oldPath) != 0) {
fprintf(stderr, "pivot_root: %s\n", strerror(errno));
} else if (chdir("/") != 0) {
fprintf(stderr, "chdir: %s\n", strerror(errno));
} else if (umount2(oldRoot, MNT_DETACH)) {
fprintf(stderr, "umount2: %s\n", strerror(errno));
} else if (rmdir(oldRoot) != 0) {
fprintf(stderr, "rmdir: %s\n", strerror(errno));
} else {
ok = 1;
}
return ok;
}
After calling dir_jail(dir)
, your program sees dir
as /
and cannot escape
it. It will have a full set of capabilities and can do pretty much anything
root
can with some major exceptions. As detailed in the article, unless it’s
started as root
, it will not be able to mount block devices. And it will not
be able to nest another container in itself, unless you go through the uid/gid
mapping process, which is quite annoying. See user_namespaces(7)
on how to do
this.
Nevertheless, it should provide some robust security for a server application or a CGI process, without having to run the calling process as root.