/* SPDX-License-Identifier: GPL-2.0-or-later */
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/capability.h>
#include <sys/stat.h>
#include <glib.h>
static int add_mount(const char *from, const char *to)
{
int ret;
ret = mount("", from, "", MS_SLAVE | MS_REC, NULL);
if (ret < 0 && errno != EINVAL) {
fprintf(stderr, "cannot make mount propagation slave %s\n", from);
return ret;
}
ret = mount(from, to, "",
MS_BIND | MS_REC | MS_SLAVE | MS_NOSUID | MS_NODEV | MS_NOEXEC,
NULL);
if (ret < 0) {
fprintf(stderr, "cannot bind mount %s to %s\n", from, to);
return ret;
}
ret = mount("", to, "", MS_SLAVE | MS_REC, NULL);
if (ret < 0) {
fprintf(stderr, "cannot make mount propagation slave %s\n", to);
return ret;
}
ret = mount(from, to, "",
MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NODEV |
MS_NOEXEC,
NULL);
if (ret < 0) {
fprintf(stderr, "cannot remount ro %s\n", to);
return ret;
}
return 0;
}
/* Warn (not error) if /etc/resolv.conf is a symlink to a file outside /etc or
* /run. */
static void validate_etc_resolv_conf()
{
char *p = realpath("/etc/resolv.conf", NULL);
if (p == NULL) {
return;
}
if (!g_str_has_prefix(p, "/etc") && !g_str_has_prefix(p, "/run")) {
fprintf(stderr,
"sandbox: /etc/resolv.conf (-> %s) seems a symlink to a file "
"outside {/etc, /run}. DNS will not work.\n",
p);
}
free(p);
}
/* lock down the process doing the following:
- create a new mount namespace
- bind mount /etc and /run from the host
- pivot_root in the new tmpfs.
- drop all capabilities.
*/
int create_sandbox()
{
int ret, i;
struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
struct __user_cap_data_struct data[2] = { { 0 } };
validate_etc_resolv_conf();
ret = unshare(CLONE_NEWNS);
if (ret < 0) {
fprintf(stderr, "cannot unshare new mount namespace\n");
return ret;
}
ret = mount("", "/", "", MS_PRIVATE, NULL);
if (ret < 0) {
fprintf(stderr, "cannot remount / private\n");
return ret;
}
ret = mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC,
"size=1k");
if (ret < 0) {
fprintf(stderr, "cannot mount tmpfs on /tmp\n");
return ret;
}
ret = mkdir("/tmp/etc", 0755);
if (ret < 0) {
fprintf(stderr, "cannot mkdir /etc\n");
return ret;
}
ret = mkdir("/tmp/old", 0755);
if (ret < 0) {
fprintf(stderr, "cannot mkdir /old\n");
return ret;
}
ret = mkdir("/tmp/run", 0755);
if (ret < 0) {
fprintf(stderr, "cannot mkdir /run\n");
return ret;
}
ret = add_mount("/etc", "/tmp/etc");
if (ret < 0) {
return ret;
}
ret = add_mount("/run", "/tmp/run");
if (ret < 0) {
return ret;
}
ret = chdir("/tmp");
if (ret < 0) {
fprintf(stderr, "cannot chdir to /tmp\n");
return ret;
}
ret = syscall(__NR_pivot_root, ".", "old");
if (ret < 0) {
fprintf(stderr, "cannot pivot_root to /tmp\n");
return ret;
}
ret = chdir("/");
if (ret < 0) {
fprintf(stderr, "cannot chdir to /\n");
return ret;
}
ret = umount2("/old", MNT_DETACH);
if (ret < 0) {
fprintf(stderr, "cannot umount /old\n");
return ret;
}
ret = rmdir("/old");
if (ret < 0) {
fprintf(stderr, "cannot rmdir /old\n");
return ret;
}
ret = mount("tmpfs", "/", "tmpfs", MS_REMOUNT | MS_RDONLY, "size=0k");
if (ret < 0) {
fprintf(stderr, "cannot remount / as read-only\n");
/* error is negligible (#163) */
}
ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
if (ret < 0) {
fprintf(stderr, "prctl(PR_SET_NO_NEW_PRIVS)\n");
return ret;
}
ret = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
if (ret < 0) {
fprintf(stderr, "prctl(PR_CAP_AMBIENT_CLEAR_ALL)\n");
return ret;
}
for (i = 0;; i++) {
if (i == CAP_NET_BIND_SERVICE)
continue;
ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
if (ret < 0) {
if (errno == EINVAL)
break;
fprintf(stderr, "prctl(PR_CAPBSET_DROP)\n");
return ret;
}
}
memset(&data, 0, sizeof(data));
data[0].effective |= 1 << CAP_NET_BIND_SERVICE;
data[0].permitted |= 1 << CAP_NET_BIND_SERVICE;
data[0].inheritable |= 1 << CAP_NET_BIND_SERVICE;
ret = capset(&hdr, data);
if (ret < 0) {
fprintf(stderr, "capset(0)\n");
return ret;
}
return 0;
}