#include #include #include #include #include #include #include #include #include #include struct options { const char *job_name; uid_t uid; gid_t gid; const char *allowed_file; const char *own_output; const char *peer_file; const char *hidden_file; const char *protected_file; unsigned int hold_seconds; }; static int failures = 0; static void usage(const char *argv0) { fprintf(stderr, "usage: %s --job-name NAME --uid UID --gid GID --allowed-file PATH " "--own-output PATH --peer-file PATH --hidden-file PATH " "--protected-file PATH [--hold-seconds N]\n", argv0); } static void print_value(const char *key, const char *value) { printf("%s=%s\n", key, value); } static void print_number(const char *key, long value) { printf("%s=%ld\n", key, value); } static void report_failure(const char *key, const char *detail) { failures++; printf("%s=fail:%s\n", key, detail); } static bool parse_ulong(const char *text, unsigned long *value) { char *end = NULL; unsigned long parsed; errno = 0; parsed = strtoul(text, &end, 10); if (errno != 0 || end == text || *end != '\0') return false; *value = parsed; return true; } static int parse_options(int argc, char **argv, struct options *opts) { int i; memset(opts, 0, sizeof(*opts)); opts->hold_seconds = 0; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--job-name") == 0 && i + 1 < argc) { opts->job_name = argv[++i]; } else if (strcmp(argv[i], "--uid") == 0 && i + 1 < argc) { unsigned long value; if (!parse_ulong(argv[++i], &value)) return -1; opts->uid = (uid_t)value; } else if (strcmp(argv[i], "--gid") == 0 && i + 1 < argc) { unsigned long value; if (!parse_ulong(argv[++i], &value)) return -1; opts->gid = (gid_t)value; } else if (strcmp(argv[i], "--allowed-file") == 0 && i + 1 < argc) { opts->allowed_file = argv[++i]; } else if (strcmp(argv[i], "--own-output") == 0 && i + 1 < argc) { opts->own_output = argv[++i]; } else if (strcmp(argv[i], "--peer-file") == 0 && i + 1 < argc) { opts->peer_file = argv[++i]; } else if (strcmp(argv[i], "--hidden-file") == 0 && i + 1 < argc) { opts->hidden_file = argv[++i]; } else if (strcmp(argv[i], "--protected-file") == 0 && i + 1 < argc) { opts->protected_file = argv[++i]; } else if (strcmp(argv[i], "--hold-seconds") == 0 && i + 1 < argc) { unsigned long value; if (!parse_ulong(argv[++i], &value)) return -1; opts->hold_seconds = (unsigned int)value; } else { return -1; } } if (opts->job_name == NULL || opts->allowed_file == NULL || opts->own_output == NULL || opts->peer_file == NULL || opts->hidden_file == NULL || opts->protected_file == NULL) return -1; return 0; } static void require_drop(uid_t uid, gid_t gid) { gid_t groups[1]; groups[0] = gid; if (setgroups(1, groups) != 0) { report_failure("drop.setgroups", strerror(errno)); return; } if (setgid(gid) != 0) { report_failure("drop.setgid", strerror(errno)); return; } if (setuid(uid) != 0) { report_failure("drop.setuid", strerror(errno)); return; } print_number("post_drop.euid", (long)geteuid()); print_number("post_drop.egid", (long)getegid()); } static void check_setuid_regain_root(void) { if (setuid(0) == 0) { report_failure("drop.regain_root", "unexpected-success"); return; } print_value("drop.regain_root", strerror(errno)); } static void check_read_allowed(const char *path) { int fd; char buffer[256]; ssize_t nread; fd = open(path, O_RDONLY); if (fd < 0) { report_failure("access.allowed_read", strerror(errno)); return; } nread = read(fd, buffer, sizeof(buffer) - 1); close(fd); if (nread < 0) { report_failure("access.allowed_read", strerror(errno)); return; } buffer[nread] = '\0'; print_value("access.allowed_read", buffer); } static void check_write_own_output(const char *path, const char *job_name) { int fd; char buffer[256]; struct stat st; fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd < 0) { report_failure("access.own_output", strerror(errno)); return; } snprintf(buffer, sizeof(buffer), "%s-ran-as-build-user\n", job_name); if (write(fd, buffer, strlen(buffer)) < 0) { close(fd); report_failure("access.own_output", strerror(errno)); return; } close(fd); if (stat(path, &st) != 0) { report_failure("access.own_output_stat", strerror(errno)); return; } print_number("access.own_output_uid", (long)st.st_uid); print_number("access.own_output_gid", (long)st.st_gid); } static void expect_denied(const char *key, const char *path) { int fd; errno = 0; fd = open(path, O_RDONLY); if (fd >= 0) { close(fd); report_failure(key, "unexpected-success"); return; } print_value(key, strerror(errno)); } static void expect_protected_create_denied(const char *path) { int fd; errno = 0; fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); if (fd >= 0) { close(fd); unlink(path); report_failure("access.protected_create", "unexpected-success"); return; } print_value("access.protected_create", strerror(errno)); } static void expect_chown_denied(const char *path) { if (chown(path, 0, 0) == 0) { report_failure("drop.chown_root", "unexpected-success"); return; } print_value("drop.chown_root", strerror(errno)); } int main(int argc, char **argv) { struct options opts; if (parse_options(argc, argv, &opts) != 0) { usage(argv[0]); return 2; } print_value("job", opts.job_name); print_number("target.uid", (long)opts.uid); print_number("target.gid", (long)opts.gid); require_drop(opts.uid, opts.gid); check_setuid_regain_root(); check_read_allowed(opts.allowed_file); check_write_own_output(opts.own_output, opts.job_name); expect_denied("access.peer_file", opts.peer_file); expect_denied("access.hidden_file", opts.hidden_file); expect_protected_create_denied(opts.protected_file); expect_chown_denied(opts.own_output); if (opts.hold_seconds > 0) sleep(opts.hold_seconds); return failures == 0 ? 0 : 1; }