262 lines
6.7 KiB
C
262 lines
6.7 KiB
C
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|