#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include static int symlink_clone_dir(const char *pathname) { int ret = -1; char tmp[] = "/tmp/cloneXXXXXX"; char buf[sizeof tmp + 100]; if (!mkdtemp(tmp)) return -1; if (mount("none", tmp, "tmpfs", 0, "mode=0755") < 0) goto out1; int fd = open(tmp, O_RDONLY|O_DIRECTORY|O_CLOEXEC); if (fd < 0) goto out2; if (mkdirat(fd, ".orig", 0700) < 0) goto out3; snprintf(buf, sizeof buf, "%s/%s", tmp, ".orig"); if (mount(pathname, buf, 0, MS_BIND, 0) < 0) goto out3; DIR *dir = opendir(buf); if (!dir) goto out3; struct dirent *de; while ((de = readdir(dir))) { char lnk[sizeof ".orig/" + strlen(de->d_name)]; snprintf(lnk, sizeof lnk, ".orig/%s", de->d_name); symlinkat(lnk, fd, de->d_name); } closedir(dir); if (mount(tmp, pathname, 0, MS_MOVE, 0) < 0) goto out3; ret = 0; out3: close(fd); out2: if (ret) umount2(tmp, MNT_DETACH); out1: rmdir(tmp); return ret; } static int make_empty(const char *pathname) { int fd = open(pathname, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0644); if (fd < 0) return -1; close(fd); return 0; } static int bindover(const char *pathname) { char tmp[] = "/tmp/bindoverXXXXXX"; int fd = mkostemp(tmp, O_CLOEXEC); if (fd < 0) return -1; int r = mount(tmp, pathname, 0, MS_BIND, 0); unlink(tmp); close(fd); return r; } static int writeproc(const char *pathname, const char *contents) { int fd = open(pathname, O_RDWR|O_CLOEXEC); if (fd < 0) return -1; if (write(fd, contents, strlen(contents)) != strlen(contents)) return -1; close(fd); return 0; } static int new_user_ns(int flags) { uid_t uid = getuid(); gid_t gid = getgid(); if (unshare(CLONE_NEWUSER|flags) < 0) return -1; char buf[3*sizeof(uintmax_t) + 10]; snprintf(buf, sizeof buf, "0 %ju 1", (uintmax_t)uid); if (writeproc("/proc/self/uid_map", buf) < 0) return -1; if (writeproc("/proc/self/setgroups", "deny") < 0) return -1; snprintf(buf, sizeof buf, "0 %ju 1", (uintmax_t)gid); if (writeproc("/proc/self/gid_map", buf) < 0) return -1; return 0; } int enter_dns_test_ns() { if (new_user_ns(CLONE_NEWNS|CLONE_NEWNET) < 0) return -1; int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (s < 0) return -1; struct ifreq ifr = { .ifr_name = "lo", }; if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) return -1; ifr.ifr_flags |= IFF_UP; if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) return -1; close(s); if (access("/etc/resolv.conf", F_OK) || access("/etc/hosts", F_OK)) { if (symlink_clone_dir("/etc") < 0) return -1; unlink("/etc/resolv.conf"); unlink("/etc/hosts"); if (make_empty("/etc/resolv.conf") || make_empty("/etc/hosts")) return -1; } else { if (bindover("/etc/resolv.conf")) return -1; if (bindover("/etc/hosts")) return -1; } return 0; } int main(int argc, char **argv) { if (enter_dns_test_ns()) { perror(0); return 1; } if (argc>1) execv(argv[1], argv+1); }