Skip to content

Commit

Permalink
Don't map ourselves to root in the user namespace.
Browse files Browse the repository at this point in the history
Doing this is the source of sandstorm-io#3584.

Instead, make sure we've fully entered a new user namespace before
we have to do anything that would require the capabilities that are
dropped on exec(). We also need to be in a new pid namespace, since
we try to mount /proc so it needs to be a procfs that we own. We use
clone() instead of unshare() for this so we don't have to disturb
the process hierarchy.
  • Loading branch information
zenhack committed Mar 13, 2022
1 parent 4849adc commit 2bb450f
Showing 1 changed file with 34 additions and 15 deletions.
49 changes: 34 additions & 15 deletions src/sandstorm/run-bundle.c++
Original file line number Diff line number Diff line change
Expand Up @@ -1396,23 +1396,21 @@ private:
}
}

void idMapUidNamespace(uid_t uid, gid_t gid) {
// Identity-map the given uid & gid in the current uid namespace.
writeSetgroupsIfPresent("deny\n");
writeUserNSMap("uid", kj::str(uid, " ", uid, " 1\n"));
writeUserNSMap("gid", kj::str(gid, " ", gid, " 1\n"));
}

void unshareUidNamespaceOnce() {
if (!unsharedUidNamespace) {
uid_t uid = getuid();
gid_t gid = getgid();

KJ_SYSCALL(unshare(CLONE_NEWUSER));

// Set up the UID namespace. We map ourselves as UID zero because this allows capabilities
// to be inherited through exec(), which we need to support update and restart. With any
// other UID, capabilities can only be inherited through exec() if the target exec'd file
// has its inheritable capabilities set filled. By default, the inheritable capability set
// for all files is empty, and only the filesystem's superuser (i.e. not us) can change them.
// But if our UID is zero, then the file's attributes are ignored and all capabilities are
// inherited.
writeSetgroupsIfPresent("deny\n");
writeUserNSMap("uid", kj::str("0 ", uid, " 1\n"));
writeUserNSMap("gid", kj::str("0 ", gid, " 1\n"));
idMapUidNamespace(uid, gid);

unsharedUidNamespace = true;
}
Expand Down Expand Up @@ -1951,10 +1949,23 @@ private:

pid_t updaterPid = startUpdater(config, fdBundle, false);

pid_t sandstormPid = fork();
if (sandstormPid == 0) {
runServerMonitor(config, fdBundle);
KJ_UNREACHABLE;
// Start the server monitor.
pid_t sandstormPid;
{
int cloneFlags = CLONE_PARENT_SETTID | SIGCHLD;
if(!runningAsRoot) {
cloneFlags |= CLONE_NEWUSER|CLONE_NEWPID;
unsharedUidNamespace = true;
}

uid_t uid = getuid();
gid_t gid = getgid();

KJ_SYSCALL(syscall(SYS_clone, cloneFlags, nullptr, &sandstormPid, 0));
if(sandstormPid == 0) {
runServerMonitor(config, fdBundle, uid, gid);
KJ_UNREACHABLE;
}
}

for (;;) {
Expand Down Expand Up @@ -2019,11 +2030,19 @@ private:
}
}

[[noreturn]] void runServerMonitor(const Config& config, FdBundle& fdBundle) {
[[noreturn]] void runServerMonitor(const Config& config, FdBundle& fdBundle,
uid_t uid, gid_t gid) {
// Run the server monitor, which runs node and mongo and deals with them dying.
// The uid and gid should be the uid and gid for the parent process in its own
// user namespace (if user namespaces are in use).

setProcessName("montr", "(server monitor)");

if(!runningAsRoot) {
// We were cloned into a new user namespace, so we need to set up a mapping:
idMapUidNamespace(uid, gid);
}

enterChroot(config.uids, true);

// Make sure socket directory exists (since the installer doesn't create it).
Expand Down

0 comments on commit 2bb450f

Please sign in to comment.