#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include const char *var_name = "VOIDNSRUN_DIR"; const char *prog_version = "1.0"; #define USERMOUNTS_MAX 8 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) bool isdir(const char *s) { struct stat st; int result = stat(s, &st); if (result == -1) ERROR("stat: %s\n", strerror(errno)); return result == 0 && S_ISDIR(st.st_mode); } void usage(const char *progname) { printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); printf("\n" "Options:\n" " -m : add bind mount\n" " -r : altroot path. If this option is not present,\n" " %s environment variable is used.\n" " -h: print this help\n" " -v: print version\n", var_name); } bool mountlist( const char *dirptr, size_t dirlen, const char **mountpoints, size_t len, bool ignore_missing) { char buf[PATH_MAX]; for (size_t i = 0; i < len; i++) { /* Check if it's safe to proceed. */ if (dirlen + strlen(mountpoints[i]) >= PATH_MAX) { ERROR("error: path %s%s is too large.\n", dirptr, mountpoints[i]); return false; } strcpy(buf, dirptr); strcat(buf, mountpoints[i]); if (!isdir(buf)) { if (ignore_missing) continue; ERROR("error: source mount dir %s does not exists.\n", buf); return false; } if (!isdir(mountpoints[i])) { ERROR("error: mountpoint %s does not exists.\n", mountpoints[i]); return false; } if (mount(buf, mountpoints[i], NULL, MS_BIND|MS_REC, NULL) == -1) { ERROR("mount: failed to mount %s: %s\n", mountpoints[i], strerror(errno)); return false; } } return true; } bool isxbpscommand(const char *s) { const char *commands[] = { "/xbps-install", "/xbps-remove", "/xbps-reconfigure" }; for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { const char *command = commands[i]; if (!strcmp(s, command+1)) return true; char *slash = strrchr(s, '/'); if (slash && !strcmp(slash, command)) return true; } return false; } int main(int argc, char **argv) { if (argc < 2) { usage(argv[0]); return 0; } int result; char *dir = NULL; size_t dirlen; const char *usermounts[USERMOUNTS_MAX] = {0}; int usermounts_num = 0; int c; while ((c = getopt(argc, argv, "vhm:r:")) != -1) { switch (c) { case 'v': printf("%s\n", prog_version); return 0; case 'h': usage(argv[0]); return 0; case 'r': dir = optarg; break; case 'm': if (usermounts_num < USERMOUNTS_MAX) { usermounts[usermounts_num++] = optarg; break; } else { ERROR("error: only up to %d user mounts allowed.\n", USERMOUNTS_MAX); return 1; } case '?': return 1; } } /* Get alternative root dir. */ if (!dir) dir = getenv(var_name); if (!dir) { ERROR("error: environment variable %s not found.\n", var_name); return 1; } /* Validate it. */ if (!isdir(dir)) { ERROR("error: %s is not a directory.\n", dir); return 1; } dirlen = strlen(dir); /* Do the unshare magic. */ result = unshare(CLONE_NEWNS); if (result == -1) { ERROR("unshare: %s\n", strerror(errno)); return 1; } /* Mount stuff from altroot to our private namespace. */ const char *mountpoints[3] = {"/usr", NULL, NULL}; if (isxbpscommand(argv[optind])) { mountpoints[1] = "/var"; mountpoints[2] = "/etc"; } else { mountpoints[1] = "/var/db/xbps"; mountpoints[2] = "/etc/xbps.d"; } if (!mountlist(dir, dirlen, mountpoints, ARRAY_SIZE(mountpoints), true)) return 1; if (usermounts_num > 0 && !mountlist(dir, dirlen, usermounts, usermounts_num, false)) return 1; /* Drop root. */ uid_t uid = getuid(); gid_t gid = getgid(); result = setreuid(uid, uid); if (result == -1) { ERROR("setreuid: %s\n", strerror(errno)); return 1; } result = setregid(gid, gid); if (result == -1) { ERROR("setregid: %s\n", strerror(errno)); return 1; } /* Launch program. */ result = execvp(argv[optind], (char *const *)argv+optind); if (result == -1) { ERROR("execvp(%s): %s\n", argv[optind], strerror(errno)); return 1; } return 0; }