diff --git a/build.py b/build.py index 9b82104b..85f10db5 100644 --- a/build.py +++ b/build.py @@ -720,7 +720,7 @@ def main(): print(f"\n {color('⚠ Some tools missing - will try anyway:', Colors.YELLOW)}") for m in missing: print(f" {m}") - print(f" {color('Not all modules will build. That\'s fine.', Colors.GRAY)}") + print(" " + color("Not all modules will build. That's fine.", Colors.GRAY)) else: print(f" {color('✓ All prerequisites found', Colors.GREEN)}") diff --git a/diagnostic/build-2b54872c.json b/diagnostic/build-2b54872c.json new file mode 100644 index 00000000..36488b31 --- /dev/null +++ b/diagnostic/build-2b54872c.json @@ -0,0 +1,23 @@ +{ + "generated_at": "2026-06-23T04:05:10.650191+00:00", + "commit": "2b54872c", + "diagnostic_logd": null, + "diagnostic_logd_error": "191814fb92eb3c934cfb", + "chunked": false, + "chunk_size_bytes": null, + "password": null, + "decrypt_command": null, + "total_modules": 1, + "passed": 1, + "failed": 0, + "modules": [ + { + "name": "frailbox", + "status": "PASS", + "elapsed_seconds": 0.557, + "artifact": "/Volumes/Soengkit\u786c\u76d8/Codex\u65b0\u9879\u76ee\u4e0e\u8f93\u51fa/2026-06-19_Agent Bounty Workbench/02_candidate_bounties/gautam-tentoftrials-buddy-4/repo/frailbox/frailbox", + "output": "gcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/arena.c -o build/src/arena.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/buddy.c -o build/src/buddy.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/logger.c -o build/src/logger.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/sandbox.c -o build/src/sandbox.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c main.c -o build/main.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude build/src/arena.o build/src/buddy.o build/src/logger.o build/src/sandbox.o build/main.o -o frailbox" + } + ], + "pr_note": "Encrypted diagnostic logd artifact was not created; include this JSON report showing why. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/frailbox/Makefile b/frailbox/Makefile index d4383d85..ebd4c922 100644 --- a/frailbox/Makefile +++ b/frailbox/Makefile @@ -1,9 +1,13 @@ CC = gcc +UNAME_S := $(shell uname -s) CFLAGS = -Wall -Wextra -Wpedantic -std=c2x -O2 -g CFLAGS += -D_FORTIFY_SOURCE=3 CFLAGS += -fstack-protector-strong CFLAGS += -fPIE -LDFLAGS = -pie -z relro -z now +LDFLAGS = +ifeq ($(UNAME_S),Linux) +LDFLAGS += -pie -z relro -z now +endif SRCDIR = src INCDIR = include @@ -14,8 +18,9 @@ OBJS = $(patsubst %.c, $(BUILDDIR)/%.o, $(SRCS)) DEPS = $(OBJS:.o=.d) TARGET = frailbox +BUDDY_TEST = $(BUILDDIR)/test_buddy -.PHONY: all clean +.PHONY: all clean test-buddy all: $(TARGET) @@ -26,6 +31,10 @@ $(BUILDDIR)/%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) -I$(INCDIR) -MMD -MP -c $< -o $@ +$(BUDDY_TEST): tests/test_buddy.c $(SRCDIR)/buddy.c $(INCDIR)/buddy.h + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -I$(INCDIR) tests/test_buddy.c $(SRCDIR)/buddy.c -o $@ $(LDFLAGS) + -include $(DEPS) clean: @@ -37,7 +46,10 @@ distclean: clean test: $(TARGET) ./$(TARGET) --sandbox-type seccomp --memory-limit 64 --verbose +test-buddy: $(BUDDY_TEST) + ./$(BUDDY_TEST) + valgrind: $(TARGET) valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET) -.PHONY: all clean distclean test valgrind +.PHONY: all clean distclean test test-buddy valgrind diff --git a/frailbox/include/buddy.h b/frailbox/include/buddy.h new file mode 100644 index 00000000..464bf4fc --- /dev/null +++ b/frailbox/include/buddy.h @@ -0,0 +1,41 @@ +#ifndef FRAILBOX_BUDDY_H +#define FRAILBOX_BUDDY_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUDDY_MIN_BLOCK_SIZE 64u + +typedef struct buddy_allocator buddy_allocator_t; + +typedef struct buddy_stats { + size_t capacity_bytes; + size_t allocated_bytes; + size_t reserved_bytes; + size_t free_bytes; + size_t largest_free_block; + uint64_t allocation_count; + uint64_t deallocation_count; + uint64_t split_count; + uint64_t coalesce_count; + double fragmentation_ratio; +} buddy_stats_t; + +buddy_allocator_t *buddy_create(size_t capacity_bytes); +void buddy_destroy(buddy_allocator_t *allocator); + +void *buddy_alloc(buddy_allocator_t *allocator, size_t size); +void buddy_free(buddy_allocator_t *allocator, void *ptr); + +buddy_stats_t buddy_stats(const buddy_allocator_t *allocator); +int buddy_contains(const buddy_allocator_t *allocator, const void *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/frailbox/src/arena.c b/frailbox/src/arena.c index d4753b53..24fd12d7 100644 --- a/frailbox/src/arena.c +++ b/frailbox/src/arena.c @@ -14,7 +14,11 @@ static arena_region_t *region_alloc(size_t size, uint32_t flags) { int mmap_prot = PROT_READ | PROT_WRITE; if (flags & ARENA_HUGE_PAGES) { +#ifdef MAP_HUGETLB mmap_flags |= MAP_HUGETLB; +#else + return NULL; +#endif } void *addr = mmap(NULL, size, mmap_prot, mmap_flags, -1, 0); @@ -175,8 +179,9 @@ size_t arena_total_capacity(const arena_t *arena) { int arena_contains(const arena_t *arena, const void *ptr) { arena_region_t *region = arena->regions; while (region) { - if (ptr >= region->start && - ptr < (char *)region->start + region->size) { + const char *byte_ptr = ptr; + if (byte_ptr >= (const char *)region->start && + byte_ptr < (const char *)region->start + region->size) { return 1; } region = region->next; diff --git a/frailbox/src/buddy.c b/frailbox/src/buddy.c new file mode 100644 index 00000000..788a1fea --- /dev/null +++ b/frailbox/src/buddy.c @@ -0,0 +1,297 @@ +#include "buddy.h" + +#include +#include +#include +#include + +typedef struct buddy_block { + uint8_t order; + uint8_t free; + size_t requested_size; + struct buddy_block *next; + struct buddy_block *prev; +} buddy_block_t; + +struct buddy_allocator { + unsigned char *base; + size_t capacity; + uint8_t min_order; + uint8_t max_order; + buddy_block_t **free_lists; + buddy_stats_t stats; +}; + +static int is_power_of_two(size_t value) { + return value != 0 && (value & (value - 1u)) == 0; +} + +static size_t next_power_of_two(size_t value) { + if (value <= 1u) { + return 1u; + } + + value--; + for (size_t shift = 1u; shift < sizeof(size_t) * 8u; shift <<= 1u) { + value |= value >> shift; + } + return value + 1u; +} + +static uint8_t order_for_size(size_t size) { + uint8_t order = 0; + size_t block_size = 1u; + + while (block_size < size) { + block_size <<= 1u; + order++; + } + + return order; +} + +static size_t size_for_order(uint8_t order) { + return (size_t)1u << order; +} + +static void list_push(buddy_allocator_t *allocator, buddy_block_t *block) { + buddy_block_t **head = &allocator->free_lists[block->order]; + + block->free = 1u; + block->requested_size = 0u; + block->prev = NULL; + block->next = *head; + if (*head) { + (*head)->prev = block; + } + *head = block; +} + +static void list_remove(buddy_allocator_t *allocator, buddy_block_t *block) { + buddy_block_t **head = &allocator->free_lists[block->order]; + + if (block->prev) { + block->prev->next = block->next; + } else if (*head == block) { + *head = block->next; + } + + if (block->next) { + block->next->prev = block->prev; + } + + block->next = NULL; + block->prev = NULL; + block->free = 0u; +} + +static buddy_block_t *block_at(const buddy_allocator_t *allocator, size_t offset) { + return (buddy_block_t *)(void *)(allocator->base + offset); +} + +static size_t block_offset(const buddy_allocator_t *allocator, const buddy_block_t *block) { + return (size_t)((const unsigned char *)(const void *)block - allocator->base); +} + +static void refresh_free_stats(buddy_allocator_t *allocator) { + size_t free_bytes = 0u; + size_t largest = 0u; + + for (uint8_t order = allocator->min_order; order <= allocator->max_order; order++) { + size_t block_size = size_for_order(order); + for (buddy_block_t *block = allocator->free_lists[order]; block; block = block->next) { + free_bytes += block_size; + if (block_size > largest) { + largest = block_size; + } + } + } + + allocator->stats.free_bytes = free_bytes; + allocator->stats.largest_free_block = largest; + allocator->stats.fragmentation_ratio = + free_bytes == 0u ? 0.0 : 1.0 - ((double)largest / (double)free_bytes); +} + +buddy_allocator_t *buddy_create(size_t capacity_bytes) { + size_t min_capacity = BUDDY_MIN_BLOCK_SIZE; + + if (capacity_bytes < min_capacity) { + capacity_bytes = min_capacity; + } + + capacity_bytes = next_power_of_two(capacity_bytes); + if (!is_power_of_two(capacity_bytes)) { + errno = EOVERFLOW; + return NULL; + } + + buddy_allocator_t *allocator = calloc(1u, sizeof(*allocator)); + if (!allocator) { + return NULL; + } + + allocator->capacity = capacity_bytes; + allocator->min_order = order_for_size(BUDDY_MIN_BLOCK_SIZE); + allocator->max_order = order_for_size(capacity_bytes); + + allocator->free_lists = calloc((size_t)allocator->max_order + 1u, sizeof(*allocator->free_lists)); + if (!allocator->free_lists) { + free(allocator); + return NULL; + } + + int rc = posix_memalign((void **)&allocator->base, BUDDY_MIN_BLOCK_SIZE, allocator->capacity); + if (rc != 0) { + free(allocator->free_lists); + free(allocator); + errno = rc; + return NULL; + } + memset(allocator->base, 0, allocator->capacity); + + buddy_block_t *root = block_at(allocator, 0u); + root->order = allocator->max_order; + list_push(allocator, root); + + allocator->stats.capacity_bytes = allocator->capacity; + refresh_free_stats(allocator); + + return allocator; +} + +void buddy_destroy(buddy_allocator_t *allocator) { + if (!allocator) { + return; + } + + free(allocator->base); + free(allocator->free_lists); + memset(&allocator->stats, 0, sizeof(allocator->stats)); + free(allocator); +} + +void *buddy_alloc(buddy_allocator_t *allocator, size_t size) { + if (!allocator || size == 0u) { + return NULL; + } + + if (size > SIZE_MAX - sizeof(buddy_block_t)) { + return NULL; + } + + size_t required = size + sizeof(buddy_block_t); + if (required < BUDDY_MIN_BLOCK_SIZE) { + required = BUDDY_MIN_BLOCK_SIZE; + } + + size_t rounded_required = next_power_of_two(required); + if (rounded_required == 0u || rounded_required > allocator->capacity) { + return NULL; + } + + uint8_t needed_order = order_for_size(rounded_required); + if (needed_order < allocator->min_order) { + needed_order = allocator->min_order; + } + + uint8_t order = needed_order; + while (order <= allocator->max_order && allocator->free_lists[order] == NULL) { + order++; + } + + if (order > allocator->max_order) { + return NULL; + } + + buddy_block_t *block = allocator->free_lists[order]; + list_remove(allocator, block); + + while (order > needed_order) { + order--; + size_t half_size = size_for_order(order); + buddy_block_t *right = block_at(allocator, block_offset(allocator, block) + half_size); + right->order = order; + list_push(allocator, right); + block->order = order; + allocator->stats.split_count++; + } + + block->free = 0u; + block->requested_size = size; + block->next = NULL; + block->prev = NULL; + + allocator->stats.allocated_bytes += size; + allocator->stats.reserved_bytes += size_for_order(block->order); + allocator->stats.allocation_count++; + refresh_free_stats(allocator); + + return (unsigned char *)(void *)block + sizeof(*block); +} + +void buddy_free(buddy_allocator_t *allocator, void *ptr) { + if (!allocator || !ptr) { + return; + } + + buddy_block_t *block = (buddy_block_t *)(void *)((unsigned char *)ptr - sizeof(*block)); + if (!buddy_contains(allocator, block) || block->free) { + return; + } + + allocator->stats.allocated_bytes -= block->requested_size; + allocator->stats.reserved_bytes -= size_for_order(block->order); + allocator->stats.deallocation_count++; + + block->requested_size = 0u; + block->free = 1u; + + while (block->order < allocator->max_order) { + size_t block_size = size_for_order(block->order); + size_t offset = block_offset(allocator, block); + size_t buddy_offset = offset ^ block_size; + + if (buddy_offset >= allocator->capacity) { + break; + } + + buddy_block_t *buddy = block_at(allocator, buddy_offset); + if (!buddy->free || buddy->order != block->order) { + break; + } + + list_remove(allocator, buddy); + if (buddy_offset < offset) { + block = buddy; + } + + block->order++; + block->free = 1u; + block->requested_size = 0u; + block->next = NULL; + block->prev = NULL; + allocator->stats.coalesce_count++; + } + + list_push(allocator, block); + refresh_free_stats(allocator); +} + +buddy_stats_t buddy_stats(const buddy_allocator_t *allocator) { + if (!allocator) { + buddy_stats_t empty = {0}; + return empty; + } + + return allocator->stats; +} + +int buddy_contains(const buddy_allocator_t *allocator, const void *ptr) { + if (!allocator || !ptr) { + return 0; + } + + const unsigned char *byte_ptr = ptr; + return byte_ptr >= allocator->base && byte_ptr < allocator->base + allocator->capacity; +} diff --git a/frailbox/src/sandbox.c b/frailbox/src/sandbox.c index d9ceb645..14760944 100644 --- a/frailbox/src/sandbox.c +++ b/frailbox/src/sandbox.c @@ -8,12 +8,16 @@ #include #include #include +#ifdef __linux__ #include +#endif #include +#ifdef __linux__ #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #endif +#endif sandbox_t *sandbox_create(const sandbox_config_t *config) { if (!config) return NULL; @@ -42,10 +46,12 @@ int sandbox_apply(sandbox_t *sandbox) { return 0; } +#ifdef __linux__ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { fprintf(stderr, "warning: PR_SET_NO_NEW_PRIVS failed: %s\n", strerror(errno)); } +#endif if (sandbox->config.memory_limit_bytes > 0) { struct rlimit rl = { diff --git a/frailbox/tests/test_buddy.c b/frailbox/tests/test_buddy.c new file mode 100644 index 00000000..4450b8c1 --- /dev/null +++ b/frailbox/tests/test_buddy.c @@ -0,0 +1,105 @@ +#include "buddy.h" + +#include +#include +#include +#include +#include + +static void test_minimum_capacity_and_stats(void) { + buddy_allocator_t *allocator = buddy_create(1u); + assert(allocator != NULL); + + buddy_stats_t stats = buddy_stats(allocator); + assert(stats.capacity_bytes == BUDDY_MIN_BLOCK_SIZE); + assert(stats.free_bytes == BUDDY_MIN_BLOCK_SIZE); + assert(stats.largest_free_block == BUDDY_MIN_BLOCK_SIZE); + assert(stats.fragmentation_ratio == 0.0); + + buddy_destroy(allocator); +} + +static void test_alloc_write_and_free(void) { + buddy_allocator_t *allocator = buddy_create(1024u); + assert(allocator != NULL); + + char *payload = buddy_alloc(allocator, 20u); + assert(payload != NULL); + assert(buddy_contains(allocator, payload)); + + strcpy(payload, "frailbox buddy"); + assert(strcmp(payload, "frailbox buddy") == 0); + + buddy_stats_t stats = buddy_stats(allocator); + assert(stats.allocated_bytes == 20u); + assert(stats.reserved_bytes >= BUDDY_MIN_BLOCK_SIZE); + assert(stats.allocation_count == 1u); + assert(stats.free_bytes < stats.capacity_bytes); + + buddy_free(allocator, payload); + + stats = buddy_stats(allocator); + assert(stats.allocated_bytes == 0u); + assert(stats.reserved_bytes == 0u); + assert(stats.deallocation_count == 1u); + assert(stats.free_bytes == stats.capacity_bytes); + assert(stats.largest_free_block == stats.capacity_bytes); + assert(stats.coalesce_count > 0u); + + buddy_destroy(allocator); +} + +static void test_fragmentation_and_reuse(void) { + buddy_allocator_t *allocator = buddy_create(2048u); + assert(allocator != NULL); + + void *a = buddy_alloc(allocator, 128u); + void *b = buddy_alloc(allocator, 128u); + void *c = buddy_alloc(allocator, 128u); + assert(a && b && c); + + buddy_free(allocator, b); + buddy_stats_t fragmented = buddy_stats(allocator); + assert(fragmented.fragmentation_ratio >= 0.0); + assert(fragmented.fragmentation_ratio <= 1.0); + + void *d = buddy_alloc(allocator, 96u); + assert(d == b); + + buddy_free(allocator, a); + buddy_free(allocator, c); + buddy_free(allocator, d); + + buddy_stats_t final = buddy_stats(allocator); + assert(final.free_bytes == final.capacity_bytes); + assert(final.largest_free_block == final.capacity_bytes); + + buddy_destroy(allocator); +} + +static void test_oversized_allocation_fails(void) { + buddy_allocator_t *allocator = buddy_create(512u); + assert(allocator != NULL); + + void *payload = buddy_alloc(allocator, 1024u); + assert(payload == NULL); + + payload = buddy_alloc(allocator, SIZE_MAX); + assert(payload == NULL); + + buddy_stats_t stats = buddy_stats(allocator); + assert(stats.allocation_count == 0u); + assert(stats.allocated_bytes == 0u); + + buddy_destroy(allocator); +} + +int main(void) { + test_minimum_capacity_and_stats(); + test_alloc_write_and_free(); + test_fragmentation_and_reuse(); + test_oversized_allocation_fails(); + + puts("buddy allocator tests passed"); + return 0; +}