Files
fruix/tests/daemon/freebsd-build-user-helper.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;
}