#include "build_db.h" #include "cfg.h" #include "util.h" #include "source_db.h" #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include #include "sandboxing.h" #define API_MESSAGE_NONE 0 #define API_MESSAGE_FINISHED 1 #define API_MESSAGE_TEST 2 #define API_MESSAGE_GET_VAR 3 bool set_child_sandbox(const package_build_t* build) { struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV | LANDLOCK_ACCESS_FS_RESOLVE_UNIX, .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL, }; int landlock_fd = create_new_landlock_ruleset(&ruleset_attr); if (landlock_fd < 0) { return false; } struct landlock_path_beneath_attr path_beneath = { .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV | LANDLOCK_ACCESS_FS_RESOLVE_UNIX, }; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, build->directory, &path_beneath)) { return false; } path_beneath.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, cfg.build_package_script, &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, build->package->script_path, &path_beneath)) { return false; } path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/dev/null", &path_beneath)) { return false; } path_beneath.allowed_access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/bin", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/usr/bin", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/lib64", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/lib", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/usr/lib64", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/usr/lib", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/usr/libexec", &path_beneath)) { return false; } if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/usr", &path_beneath)) { return false; } // this is just specific to my system - (rjb) if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/opt/gnome/", &path_beneath)) { } path_beneath.allowed_access = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | LANDLOCK_ACCESS_FS_TRUNCATE; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/tmp", &path_beneath)) { return false; } path_beneath.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; if (!landlock_add_path_rule(landlock_fd, &ruleset_attr, "/etc", &path_beneath)) { return false; } landlock_enforce(landlock_fd); return true; } bool package_setup_build(const package_info_t* info, package_build_t* build) { // get timestamp string char timestamp_str[32]; { // get time in ms struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) != 0) die("failed to get time: %s", strerror(errno)); unsigned long long total_ms = ((unsigned long long)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); snprintf(timestamp_str, sizeof(timestamp_str), "%llu", total_ms); } // create build info memset(build, 0, sizeof(*build)); build->package = info; build->directory = vastrcat(cfg.build_db, "/", info->attrs.id, "-", timestamp_str); // create build directory if (mkdir(build->directory, 0777) == -1) die("failed to create build directory: %s", strerror(errno)); // (re)create symlink to build directory char* symlink_name = vastrcat(cfg.build_db, "/", info->attrs.id); if (unlink(symlink_name) == -1 && errno != ENOENT) die("failed to remove build symlink '%s': %s", symlink_name, strerror(errno)); if (symlink(build->directory + cfg.build_db_len + 1, symlink_name) == -1) die("failed to create build symlink '%s': %s", symlink_name, strerror(errno)); free(symlink_name); build->fakeroot = join_path(build->directory, "fakeroot"); if (mkdir(build->fakeroot, 0777) == -1) die("failed to create fakeroot directory: %s", strerror(errno)); // unpack package source (eventually this will be done by swenu api) build->source_directory = join_path(build->directory, "source"); unpack_source(&info->attrs.source, build->source_directory); // todo - create paths for log files // todo - create rebuild script return true; } void build_run(const package_build_t* build) { int api_from_sh[2] = {0}; if (pipe(api_from_sh) < 0) die("failed to open api endpoint pipe: %s", strerror(errno)); int api_to_sh[2] = {0}; if (pipe(api_to_sh) < 0) die("failed to open api response pipe: %s", strerror(errno)); // PIPE and FORK to run install script int child_output[2] = {0}; if (pipe(child_output) < 0) die("failed to pipe: %s", strerror(errno)); pid_t pid = fork(); if (pid < 0) die("failed to fork: %s", strerror(errno)); if (pid == 0) { // child proc // connect my stdout and stderr to the pipe close(child_output[0]); dup2(child_output[1], STDOUT_FILENO); dup2(child_output[1], STDERR_FILENO); close(child_output[1]); // close unused api pipes close(api_from_sh[0]); close(api_to_sh[1]); char buf1[12]; snprintf(buf1, sizeof(buf1), "%d", api_from_sh[1]); char buf2[12]; snprintf(buf2, sizeof(buf2), "%d", api_to_sh[0]); // exec into install script chdir(build->source_directory); if (!set_child_sandbox(build)) { int finished = API_MESSAGE_FINISHED; write(api_from_sh[1], &finished, sizeof(finished)); die("CHILD - landlock sandbox failed not running script"); } char* argv[] = { cfg.build_package_script, buf1, buf2, build->package->script_path, NULL }; execv(cfg.build_package_script, argv); int finished = API_MESSAGE_FINISHED; write(api_from_sh[1], &finished, sizeof(finished)); // what are you still doing here? die("CHILD - execve to %s failed: %s", cfg.build_package_script, strerror(errno)); } else { // parent proc, close write end of pipe close(child_output[1]); // close unused api pipes close(api_from_sh[1]); close(api_to_sh[0]); } // all of this should be rewritten char buf[4096]; ssize_t bytes_read = 0; struct pollfd pollfds[] = { { .fd = api_from_sh[0], .events = POLLIN, .revents = 0 }, { .fd = child_output[0], .events = POLLIN, .revents = 0 } }; bool finished = false; while (!finished) { if (poll(pollfds, sizeof(pollfds) / sizeof(*pollfds), 0) < 0) { die("polling child process fds failed %s", strerror(errno)); } if (pollfds[1].revents == POLLIN) { bytes_read = read(child_output[0], buf, sizeof(buf)); if (bytes_read < 0) die("failed to read from child output: %s", strerror(errno)); // print build out, can log and stuff in future printf("%.*s", (int)bytes_read, buf); } if (pollfds[0].revents == POLLIN) { int api_call = 0; bytes_read = read(api_from_sh[0], &api_call, sizeof(api_call)); if (bytes_read < 0) { print("reading from api pipe failed: %s\n", strerror(errno)); } switch (api_call) { case API_MESSAGE_FINISHED: finished = true; break; case API_MESSAGE_TEST: printf("api test\n"); write(api_to_sh[1], "api test\n", strlen("api test\n")); break; case API_MESSAGE_GET_VAR: bytes_read = read(api_from_sh[0], buf, sizeof(buf)); if (strncmp(buf, "root", sizeof("root") - 1) == 0) { char* tmp = vastrcat(build->fakeroot, "\n"); write(api_to_sh[1], tmp, strlen(tmp)); free(tmp); } break; case API_MESSAGE_NONE: break; } } } while ((bytes_read = read(child_output[0], buf, sizeof(buf))) > 0) { // print build out, can log and stuff in future printf("%.*s", (int)bytes_read, buf); } if (bytes_read < 0) die("failed to read from pipe: %s", strerror(errno)); close(child_output[0]); // close api pipes close(api_from_sh[0]); close(api_to_sh[1]); // reap the child to finish int wstatus; if (waitpid(pid, &wstatus, 0) < 0) die("waitpid failed: %s", strerror(errno)); if (WEXITSTATUS(wstatus) != 0) die("the child failed to execute install script"); } int nftw_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { const char* path = strstr(fpath, "fakeroot") + strlen("fakeroot"); if (!path[0]) return 0; print("%s\n", path); return 0; } void list_fakeroot(const package_build_t* build) { nftw(build->fakeroot, nftw_callback, 100, FTW_PHYS); }