/* * Copyright (c) 2026, Chloe M. * Provided under the BSD-3 clause. * * Description: CUMHOLE pre-boot image format * Author: Chloe M. */ #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define REF_MAGIC "CUMHOLE" #define REF_MAGIC_LEN 8 #define REF_PATH_LEN 256 /* Set to 1 for verbose */ #define DEBUG 0 #if DEBUG #define DTRACE(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define DTRACE(...) (void)0 #endif /* * A reference is an entry within the table that refers * to a block of data after the reference table. * * @magic: Reference magic * @path: File path * @size: File size * @data_off: Data offset * @real_path: Real path * @link: Queue link * * XXX: Do not reorder anything ABOVE 'link' */ struct hole_ref { char magic[REF_MAGIC_LEN]; char path[REF_PATH_LEN]; size_t size; off_t data_off; char *real_path; TAILQ_ENTRY(hole_ref) link; }; /* Globals */ static bool output_override = false; static char *output_path = "out.hole"; static char *fill_dirpath = NULL; static size_t file_count = 0; static TAILQ_HEAD(, hole_ref) hole_files; /* * Display the help options */ static void help(void) { printf("usage: ./hole [flags]\n"); printf("[-h] Display this help menu\n"); printf("[-f] Fill hole with directory\n"); printf("[-o] Output blob path\n"); } /* * Destroy the hole files table */ static void hole_files_destroy(void) { struct hole_ref *ref, *tmp; while ((ref = TAILQ_FIRST(&hole_files)) != NULL) { DTRACE("delete %s\n", ref->real_path); free(ref->real_path); tmp = ref; TAILQ_REMOVE(&hole_files, ref, link); free(tmp); } } /* * Used to fix up offsets after */ static void hole_table_fixup(void) { struct stat sb; struct hole_ref *ref; size_t table_end, offset; size_t hdr_size; hdr_size = sizeof(struct hole_ref); hdr_size -= sizeof(ref->real_path); hdr_size -= sizeof(ref->link); table_end = file_count * hdr_size; offset = table_end; printf("* fixing up offsets...\n"); printf("* table end @ %zx\n", table_end); TAILQ_FOREACH(ref, &hole_files, link) { if (stat(ref->real_path, &sb) < 0) { perror("stat"); continue; } ref->size = sb.st_size; ref->data_off = offset; DTRACE("data_off=%zx\n", ref->data_off); offset += ref->size; } } static void blob_write(void) { struct hole_ref *ref; int fd, ffd; void *data; size_t fsize, hdr_len; fd = open(output_path, O_RDWR | O_CREAT | O_TRUNC, 0777); if (fd < 0) { perror("open"); return; } /* * PASS 1): * We are to write the header of each file into the * top of the blob. */ TAILQ_FOREACH(ref, &hole_files, link) { hdr_len = sizeof(*ref); hdr_len -= sizeof(ref->real_path); hdr_len -= sizeof(ref->link); write(fd, ref, hdr_len); } /* * PASS 2): * Now we can write the contents of each file after */ TAILQ_FOREACH(ref, &hole_files, link) { ffd = open(ref->real_path, O_RDONLY); if (ffd < 0) { perror("open[ffd]"); continue; } /* Obtain the file size */ fsize = lseek(ffd, 0, SEEK_END); lseek(ffd, 0, SEEK_SET); data = mmap( NULL, fsize, PROT_READ, MAP_SHARED, ffd, 0 ); if (data == NULL) { close(ffd); perror("mmap"); continue; } lseek(fd, SEEK_SET, ref->data_off); write(fd, data, fsize); munmap(data, fsize); close(ffd); } close(fd); return; } /* * Strip a path of leading '.' and '/' characters * * @path: Path to strip */ static const char * path_strip(const char *path) { const char *p; if (path == NULL) { return NULL; } p = path; while (*p == '.' || *p == '/') { ++p; } return p; } /* * Push a file to the hole files table */ static int push_file(const char *path) { struct hole_ref *ref; const char *stripped; if (path == NULL) { return -1; } ref = malloc(sizeof(*ref)); if (ref == NULL) { printf("fatal: could not allocate ref\n"); hole_files_destroy(); return -1; } ref->real_path = strdup(path); stripped = path_strip(path); memcpy(ref->magic, REF_MAGIC, sizeof(REF_MAGIC)); memcpy(ref->path, stripped, strlen(stripped) + 1); ref->data_off = 0; TAILQ_INSERT_TAIL(&hole_files, ref, link); ++file_count; return 0; } /* * Scan a directory to fill the blob */ static void scan_dir(const char *path) { DIR *dir; struct dirent *dirent; char pathbuf[300]; if (path == NULL) { return; } dir = opendir(path); if (dir == NULL) { perror("opendir"); return; } while ((dirent = readdir(dir)) != NULL) { if (dirent->d_name[0] == '.') { continue; } snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, dirent->d_name); switch (dirent->d_type) { case DT_REG: printf("[f] %s\n", pathbuf); if (push_file(pathbuf) < 0) { free(fill_dirpath); exit(1); } break; case DT_DIR: printf("[d] %s\n", pathbuf); scan_dir(pathbuf); break; } } closedir(dir); } int main(int argc, char **argv) { int opt; if (argc < 2) { printf("fatal: too few arguments\n"); help(); return -1; } while ((opt = getopt(argc, argv, "hf:o:")) != -1) { switch (opt) { case 'h': help(); return -1; case 'f': fill_dirpath = strdup(optarg); if (fill_dirpath == NULL) { printf("fatal: out of memory\n"); return -1; } break; case 'o': output_override = true; output_path = strdup(optarg); if (output_path == NULL) { printf("fatal: out of memory\n"); return -1; } break; } } if (fill_dirpath == NULL) { printf("fatal: expected fill directory for the slutty blob...\n"); help(); return -1; } TAILQ_INIT(&hole_files); scan_dir(fill_dirpath); hole_table_fixup(); blob_write(); hole_files_destroy(); free(fill_dirpath); if (output_override) { free(output_path); } return 0; }