/* SPDX-License-Identifier: GPL-2.0-or-later */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include 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; }