#include "build_db.h" #include "cfg.h" #include "util.h" #include "source_db.h" #include #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 extern char** environment; const char slash[] = "/"; 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->desc_dir, &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 | LANDLOCK_ACCESS_FS_EXECUTE; 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) { uint8_t errorcode = 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"); } // should make this more general currently very rigid int num_envvars = 0; for (; environment[num_envvars]; num_envvars++); num_envvars += 2; char** newenvp = calloc(sizeof(char*), num_envvars + 1); int i = 0; for (; environment[i]; i++) { newenvp[i] = environment[i]; } char* descdir = vastrcat("descdir=", build->package->desc_dir); if (!descdir) { errorcode = 1; goto error; } newenvp[i++] = descdir; char* root = vastrcat("root=", build->fakeroot); if (!descdir) { errorcode = 2; goto error; } newenvp[i++] = root; newenvp[i] = NULL; char* argv[] = { cfg.build_package_script, buf1, buf2, build->package->script_path, NULL }; execve(cfg.build_package_script, argv, newenvp); error: int finished = API_MESSAGE_FINISHED; write(api_from_sh[1], &finished, sizeof(finished)); // what are you still doing here? switch (errorcode) { case 0: die("CHILD - execve to %s failed: %s", cfg.build_package_script, strerror(errno)); case 1: die("CHILD - allocating descdir envvar failed: %s", strerror(errno)); case 2: die("CHILD - allocating root envvar failed: %s", 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_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 do_dir_traversal(char* path, const char* absolute_path, char** flist) { size_t path_len = strlen(path); if (path_len >= PATH_MAX) { return -1; } struct stat st; if (lstat(path, &st) < 0) { fprintf(stderr, "Failed to stat path '%s': %s", path, strerror(errno)); return -1; } bool copy_allowed = true; bool exists = false; struct stat absolute_st; if (lstat(absolute_path, &absolute_st) < 0) { if (errno != ENOENT) { print("lstat %s failed: %s", absolute_path, strerror(errno)); return -1; } } else { //fprintf(stderr, "%s exists\n", absolute_path); exists = true; copy_allowed = false; if (S_ISDIR(st.st_mode)) { copy_allowed = true; } else if (flist != NULL) { for (int i = 0; flist[i]; i++) { if (!strcmp(flist[i], absolute_path)) { copy_allowed = true; break; } } } } if (exists && !copy_allowed) { print("Path %s exists and is not in flist: stopping\n", absolute_path); return -1; } if (S_ISDIR(st.st_mode)) { if (exists && copy_allowed) { print("Directory %s exists doing nothing\n", absolute_path); } else { fprintf(stderr, "Creating directory %s -> %s\n", path, absolute_path); } DIR* d = opendir(path); if (!d) return -1; struct dirent* de = NULL; while((de = readdir(d))) { if (de->d_name[0] == '.' && (!de->d_name[1] || (de->d_name[1]=='.' && !de->d_name[2]))) continue; if (path_len + strlen(de->d_name) >= PATH_MAX) return -1; path[path_len] = '/'; if (absolute_path == slash) absolute_path = strstr(path, "fakeroot") + strlen("fakeroot"); strcpy(path + path_len + 1, de->d_name); if (do_dir_traversal(path, absolute_path, flist)) { closedir(d); return -1; } } closedir(d); } else if (S_ISLNK(st.st_mode)) { if (exists && copy_allowed) { fprintf(stderr, "Overwriting symlink %s\n", absolute_path); } else { fprintf(stderr, "Creating symlink %s -> %s\n", path, absolute_path); } } else { if (exists && copy_allowed) { fprintf(stderr, "Overwriting file %s\n", absolute_path); } else { fprintf(stderr, "Copying file %s -> %s\n", path, absolute_path); } } return 0; } void list_fakeroot(const package_build_t* build, char** flist) { char path[PATH_MAX] = {0}; strcpy(path, build->fakeroot); const char* absolute_path = slash; do_dir_traversal(path, absolute_path, flist); } void read_flist(const package_info_t* info, char*** flist) { char* flist_file = vastrcat(cfg.install_db, "/", info->attrs.id, "/flist"); if (!flist_file) { die("flist path allocation failed: %s", strerror(errno)); } FILE* f = fopen(flist_file, "r"); if (!f) { print("There is no flist file for: %s\n", info->attrs.id); goto error; } if (fseek(f, 0, SEEK_END) < 0) { print("failed to find end of flist: %s\n", info->attrs.id); goto error; } long file_size = ftell(f); if (fseek(f, 0, SEEK_SET) < 0) { print("failed to find begining of flist: %s\n", info->attrs.id); goto error; } if (file_size == 0) { print("flist is empty for: %s\n", info->attrs.id); goto error; } char* flist_buffer = emalloc(file_size + 1, "flist buffer"); if (fread(flist_buffer, 1, file_size, f) < file_size) { die("Failed to read entire flist"); } flist_buffer[file_size] = 0; size_t lines = 0; for (size_t i = 0; i < file_size; i++) { if (flist_buffer[i] == '\n') lines++; } if (flist_buffer[file_size - 1] != '\n') lines++; char** flist_array = emalloc((lines + 1) * sizeof(char*), "flist array"); flist_array[0] = strtok(flist_buffer, "\n"); for (size_t i = 1; i < lines; i++) { flist_array[i] = strtok(NULL, "\n"); } flist_array[lines] = NULL; *flist = flist_array; fclose(f); free(flist_file); return; error: free(flist_file); *flist = NULL; } void free_flist(char*** flist) { if (!*flist) return; free(**flist); free(*flist); *flist = NULL; }