kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit ec0883756690e6e9c76dad2e5d3e36d77d5a44b2
parent 880e14bb778689d371ccb98f0846db43469a6c2b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 16 Jul 2026 10:12:19 -0700

strip: rewrite linked ELF Mach-O and PE images

Diffstat:
Mdriver/cmd/strip.c | 202++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Minclude/kit/object.h | 32++++++++++++++++++++++++++++++++
Asrc/api/object_rewrite.c | 27+++++++++++++++++++++++++++
Asrc/obj/coff/rewrite.c | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/obj/elf/rewrite.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/format.h | 8++++++++
Asrc/obj/macho/rewrite.c | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/obj/registry.c | 4++++
Asrc/obj/rewrite.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/obj/rewrite.h | 42++++++++++++++++++++++++++++++++++++++++++
Atest/api/object_rewrite_test.c | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 1480 insertions(+), 52 deletions(-)

diff --git a/driver/cmd/strip.c b/driver/cmd/strip.c @@ -7,10 +7,8 @@ #include "driver.h" #include "objedit.h" -/* `kit strip` — drop debug sections and / or unwanted symbols from a - * relocatable object or static archive, then write the result back. Scope - * for the first cut matches the CTOOLCHAIN.md plan: relocatable .o and - * .a inputs only — linked ELF (ET_EXEC / ET_DYN) is rejected. +/* `kit strip` — drop debug sections and / or unwanted symbols from an object, + * linked image, or static archive, then write the result back. * * Operations (the last one wins; default is --strip-all): * --strip-debug drop sections whose kind is KIT_SEC_DEBUG @@ -38,9 +36,9 @@ void driver_help_strip(void) { " kit strip [OPTIONS] FILE\n" "\n" "DESCRIPTION\n" - " Copies a relocatable object or static archive while removing\n" - " selected debug sections and symbols. By default FILE is replaced\n" - " in place; use -o PATH to preserve the original.\n" + " Copies an object, linked executable/shared library, or static\n" + " archive while removing selected debug sections and symbols. By\n" + " default FILE is replaced in place; use -o PATH to preserve it.\n" "\n" "OPTIONS\n" " -h, --help Show this help and exit\n" @@ -50,6 +48,8 @@ void driver_help_strip(void) { " --strip-unneeded remove debug + symbols not needed by " "relocs\n" " --strip-all remove debug + all non-essential symbols\n" + " --remove-signature allow a signed linked image to be changed;\n" + " the result must be signed again\n" "\n" "SYMBOL FILTERS (may repeat)\n" " --keep-symbol=NAME, -K NAME keep NAME even when the operation\n" @@ -60,14 +60,16 @@ void driver_help_strip(void) { " -o PATH write to PATH (default: rewrite FILE in " "place)\n" "\n" - "SUPPORTED INPUTS — RELOCATABLE .o OR STATIC .a ONLY\n" - " Do not pass an executable or shared library in this release. The\n" - " open linked-image case can return 0 while silently producing a\n" - " small, non-executable, unusable output. Use -o for safe inputs so\n" - " the original object/archive remains available for comparison.\n" + "LINKED IMAGES\n" + " ELF, Mach-O, and PE executables/shared libraries are rewritten in\n" + " place at the format layer without relinking. Loader-required\n" + " metadata and executable permissions are preserved. Signed images\n" + " fail unchanged unless --remove-signature is explicit. Symbol\n" + " keep/drop filters apply only to relocatable inputs.\n" "\n" "EXAMPLES\n" " kit strip --strip-debug -o foo.stripped.o foo.o\n" + " kit strip --strip-debug -o app.stripped app\n" " kit objdump -h foo.stripped.o\n" " kit strip --strip-unneeded -o libfoo.stripped.a libfoo.a\n" " kit nm -g libfoo.stripped.a\n" @@ -93,6 +95,7 @@ typedef struct StripOpts { uint32_t cap_strip; const char* output; const char* input; + int remove_signature; } StripOpts; static int push_name(DriverEnv* env, const char*** arr, uint32_t* n, @@ -148,7 +151,7 @@ static int parse_name_arg(int* i, int argc, char** argv, const char* flag, * lists. Mutations are issued against the builder; the emit-time sweep cleans * up cascades (orphan relocs against removed sections, etc.). */ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, - const StripOpts* opts) { + const StripOpts* opts, const char* input_name) { KitSlice* keep = NULL; int level = opts->op == STRIP_OP_DEBUG ? KIT_STRIP_DEBUG : opts->op == STRIP_OP_UNNEEDED ? KIT_STRIP_UNNEEDED @@ -161,14 +164,14 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, keep = (KitSlice*)driver_alloc_zeroed(env, (size_t)opts->nkeep * sizeof(*keep)); if (!keep) { - driver_errf(STRIP_TOOL, "out of memory"); + driver_errf(STRIP_TOOL, "%s: out of memory", input_name); return 1; } for (i = 0; i < opts->nkeep; ++i) keep[i] = kit_slice_cstr(opts->keep[i]); } if (kit_obj_builder_strip(b, level, keep, opts->nkeep) != KIT_OK) { - driver_errf(STRIP_TOOL, "strip failed"); + driver_errf(STRIP_TOOL, "%s: strip failed", input_name); goto done; } @@ -176,7 +179,7 @@ static int strip_one_builder(DriverEnv* env, KitObjFile* of, KitObjBuilder* b, if (opts->nstrip) { KitObjSymIter* sit = NULL; if (kit_obj_symiter_new(of, &sit) != KIT_OK) { - driver_errf(STRIP_TOOL, "out of memory"); + driver_errf(STRIP_TOOL, "%s: out of memory", input_name); goto done; } for (;;) { @@ -197,10 +200,12 @@ done: static int strip_object_bytes(DriverEnv* env, const KitContext* ctx, const KitSlice* input, const StripOpts* opts, - uint8_t** out_data, size_t* out_size) { + const char* input_name, uint8_t** out_data, + size_t* out_size) { KitObjFile* of = NULL; KitObjBuilder* b; KitWriter* w = NULL; + KitStatus st; size_t n = 0; const uint8_t* data; uint8_t* copy; @@ -209,37 +214,65 @@ static int strip_object_bytes(DriverEnv* env, const KitContext* ctx, *out_data = NULL; *out_size = 0; - if (kit_obj_open(ctx, KIT_SLICE_NULL, input, &of) != KIT_OK) { - driver_errf(STRIP_TOOL, "not a recognized object"); - return 1; - } - b = kit_obj_file_builder(of); - if (!b) { - driver_errf(STRIP_TOOL, "no builder for object"); - kit_obj_free(of); - return 1; - } - - if (strip_one_builder(env, of, b, opts) != 0) { - kit_obj_free(of); + if (kit_obj_open(ctx, kit_slice_cstr(input_name), input, &of) != KIT_OK) { + driver_errf(STRIP_TOOL, "%s: not a recognized object", input_name); return 1; } - if (kit_writer_mem(env->heap, &w) != KIT_OK || !w) { - driver_errf(STRIP_TOOL, "out of memory"); + driver_errf(STRIP_TOOL, "%s: out of memory", input_name); kit_obj_free(of); return 1; } - if (kit_obj_builder_emit(b, w) != KIT_OK) { - driver_errf(STRIP_TOOL, "emit failed"); - kit_writer_close(w); - kit_obj_free(of); - return 1; + if (kit_obj_kind(of) != KIT_OBJ_KIND_REL) { + KitLinkedRewriteOptions rewrite_opts = {0}; + if (opts->nkeep || opts->nstrip) { + driver_errf(STRIP_TOOL, + "%s: symbol keep/drop filters are unsupported for linked " + "images", + input_name); + kit_writer_close(w); + kit_obj_free(of); + return 1; + } + rewrite_opts.strip_level = opts->op == STRIP_OP_DEBUG + ? KIT_LINKED_STRIP_DEBUG + : KIT_LINKED_STRIP_ALL; + rewrite_opts.remove_signature = opts->remove_signature != 0; + st = kit_obj_rewrite_linked(ctx, kit_slice_cstr(input_name), input, + &rewrite_opts, w, NULL); + if (st != KIT_OK) { + if (st == KIT_NOMEM) + driver_errf(STRIP_TOOL, "%s: out of memory", input_name); + else if (st == KIT_IO) + driver_errf(STRIP_TOOL, "%s: rewrite output failed", input_name); + kit_writer_close(w); + kit_obj_free(of); + return 1; + } + } else { + b = kit_obj_file_builder(of); + if (!b) { + driver_errf(STRIP_TOOL, "%s: no builder for object", input_name); + kit_writer_close(w); + kit_obj_free(of); + return 1; + } + if (strip_one_builder(env, of, b, opts, input_name) != 0) { + kit_writer_close(w); + kit_obj_free(of); + return 1; + } + if (kit_obj_builder_emit(b, w) != KIT_OK) { + driver_errf(STRIP_TOOL, "%s: emit failed", input_name); + kit_writer_close(w); + kit_obj_free(of); + return 1; + } } data = kit_writer_mem_bytes(w, &n); copy = (uint8_t*)driver_alloc(env, n ? n : 1u); if (!copy) { - driver_errf(STRIP_TOOL, "out of memory"); + driver_errf(STRIP_TOOL, "%s: out of memory", input_name); kit_writer_close(w); kit_obj_free(of); return 1; @@ -276,7 +309,7 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, /* Pass 1: count members + total name bytes. */ if (kit_ar_iter_new(ctx, input, &it) != KIT_OK) { - driver_errf(STRIP_TOOL, "not an archive"); + driver_errf(STRIP_TOOL, "%s: not a recognized archive", opts->input); return 1; } for (;;) { @@ -322,6 +355,8 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, char* dst; KitBinFmt fmt; KitSlice mbytes; + char* member_name = NULL; + size_t member_name_size = 0; if (r != KIT_ITER_ITEM) break; dst = name_storage + cursor; for (j = 0; j < m.name.len; ++j) *dst++ = m.name.s[j]; @@ -337,11 +372,29 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, fmt == KIT_BIN_WASM) { uint8_t* sd = NULL; size_t ss = 0; - if (strip_object_bytes(env, ctx, &mbytes, opts, &sd, &ss) != 0) { + size_t archive_len = strlen(opts->input); + member_name_size = archive_len + m.name.len + 3u; + member_name = (char*)driver_alloc(env, member_name_size); + if (!member_name) { + driver_errf(STRIP_TOOL, "%s(%.*s): out of memory", opts->input, + (int)m.name.len, m.name.s); + kit_ar_iter_free(it); + it = NULL; + goto done; + } + memcpy(member_name, opts->input, archive_len); + member_name[archive_len] = '('; + memcpy(member_name + archive_len + 1u, m.name.s, m.name.len); + member_name[archive_len + 1u + m.name.len] = ')'; + member_name[member_name_size - 1u] = '\0'; + if (strip_object_bytes(env, ctx, &mbytes, opts, member_name, &sd, &ss) != + 0) { + driver_free(env, member_name, member_name_size); kit_ar_iter_free(it); it = NULL; goto done; } + driver_free(env, member_name, member_name_size); owned_data[k] = sd; owned_size[k] = ss; members[k].bytes.data = sd; @@ -389,7 +442,10 @@ static int strip_archive(DriverEnv* env, const KitContext* ctx, if (rc == 0 && kit_writer_status(out) != KIT_OK) rc = 1; done: - if (out) kit_writer_close(out); + if (out) { + if (rc != 0) driver_writer_abort(out); + kit_writer_close(out); + } if (it) kit_ar_iter_free(it); if (sym_allocs) { for (k = 0; k < nmembers; ++k) { @@ -421,10 +477,14 @@ int driver_strip(int argc, char** argv) { uint8_t* out_data = NULL; size_t out_size = 0; int have_input = 0; + int have_input_mode = 0; + int options_done = 0; int rc = 1; int i; + uint32_t input_mode = 0; + const char* final_output = NULL; - if (argc < 2 || driver_argv_wants_help(argc, argv, 1)) { + if (argc < 2) { driver_help_strip(); return 0; } @@ -438,19 +498,34 @@ int driver_strip(int argc, char** argv) { const char* a = argv[i]; const char* val = NULL; int matched; - if (driver_streq(a, "--strip-debug")) { + if (!options_done && driver_streq(a, "--")) { + options_done = 1; + continue; + } + if (!options_done && + (driver_streq(a, "-h") || driver_streq(a, "--help"))) { + driver_help_strip(); + rc = 0; + goto done; + } + if (!options_done && driver_streq(a, "--strip-debug")) { opts.op = STRIP_OP_DEBUG; continue; } - if (driver_streq(a, "--strip-unneeded")) { + if (!options_done && driver_streq(a, "--strip-unneeded")) { opts.op = STRIP_OP_UNNEEDED; continue; } - if (driver_streq(a, "--strip-all") || driver_streq(a, "-s")) { + if (!options_done && + (driver_streq(a, "--strip-all") || driver_streq(a, "-s"))) { opts.op = STRIP_OP_ALL; continue; } - if (driver_streq(a, "-o")) { + if (!options_done && driver_streq(a, "--remove-signature")) { + opts.remove_signature = 1; + continue; + } + if (!options_done && driver_streq(a, "-o")) { if (i + 1 >= argc) { driver_errf(STRIP_TOOL, "-o requires a path"); rc = 2; @@ -459,7 +534,10 @@ int driver_strip(int argc, char** argv) { opts.output = argv[++i]; continue; } - matched = parse_name_arg(&i, argc, argv, "--keep-symbol", "-K", &val); + matched = options_done + ? 0 + : parse_name_arg(&i, argc, argv, "--keep-symbol", "-K", + &val); if (matched < 0) { driver_errf(STRIP_TOOL, "%.*s requires a symbol name", KIT_SLICE_ARG(kit_slice_cstr(a))); @@ -473,7 +551,10 @@ int driver_strip(int argc, char** argv) { } continue; } - matched = parse_name_arg(&i, argc, argv, "--strip-symbol", "-N", &val); + matched = options_done + ? 0 + : parse_name_arg(&i, argc, argv, "--strip-symbol", "-N", + &val); if (matched < 0) { driver_errf(STRIP_TOOL, "%.*s requires a symbol name", KIT_SLICE_ARG(kit_slice_cstr(a))); @@ -488,7 +569,7 @@ int driver_strip(int argc, char** argv) { } continue; } - if (a[0] == '-' && a[1] != '\0') { + if (!options_done && a[0] == '-' && a[1] != '\0') { driver_errf(STRIP_TOOL, "unknown option: %.*s", KIT_SLICE_ARG(kit_slice_cstr(a))); rc = 2; @@ -515,18 +596,25 @@ int driver_strip(int argc, char** argv) { goto done; } have_input = 1; + if (driver_path_mode_get(opts.input, &input_mode) != 0) { + driver_errf(STRIP_TOOL, "failed to read input mode: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(opts.input))); + goto done; + } + have_input_mode = 1; input.data = input_fd.data; input.len = input_fd.size; { KitBinFmt fmt = kit_detect_fmt(input.data, input.len); const char* out_path = opts.output ? opts.output : opts.input; + final_output = out_path; if (fmt == KIT_BIN_AR) { rc = strip_archive(&env, &ctx, &input, &opts, out_path); goto done; } - if (strip_object_bytes(&env, &ctx, &input, &opts, &out_data, &out_size) != - 0) { + if (strip_object_bytes(&env, &ctx, &input, &opts, opts.input, &out_data, + &out_size) != 0) { goto done; } if (ctx.file_io->open_writer(ctx.file_io->user, out_path, &w) != KIT_OK) { @@ -544,7 +632,17 @@ int driver_strip(int argc, char** argv) { } done: - if (w) kit_writer_close(w); + if (w) { + if (rc != 0) driver_writer_abort(w); + kit_writer_close(w); + w = NULL; + } + if (rc == 0 && final_output && have_input_mode && + driver_path_mode_set(final_output, input_mode) != 0) { + driver_errf(STRIP_TOOL, "failed to preserve mode: %.*s", + KIT_SLICE_ARG(kit_slice_cstr(final_output))); + rc = 1; + } if (out_data) driver_free(&env, out_data, out_size); if (have_input) ctx.file_io->release(ctx.file_io->user, &input_fd); if (opts.keep) diff --git a/include/kit/object.h b/include/kit/object.h @@ -501,6 +501,38 @@ KIT_API KitIterResult kit_obj_image_rawiter_next(KitObjImageRawIter*, KitObjImageRaw* out); KIT_API void kit_obj_image_rawiter_free(KitObjImageRawIter*); +/* Format-layer rewrite of an already-linked executable or shared library. + * Unlike kit_obj_file_builder(), this path preserves the original loader + * image byte-for-byte except for file-only debug/symbol/signature metadata. + * It therefore retains program headers/load commands, mapped addresses and + * permissions, dynamic imports/exports/relocations, TLS, unwind tables, and + * init/fini state without relinking. + * + * Signed images are rejected before OUT is touched unless remove_signature is + * set. Removing a signature only drops its on-disk metadata; the result must + * be signed again before a platform that requires signing will execute it. */ +typedef enum KitLinkedStripLevel { + KIT_LINKED_STRIP_DEBUG = 1, + KIT_LINKED_STRIP_ALL = 2, +} KitLinkedStripLevel; + +typedef struct KitLinkedRewriteOptions { + uint32_t strip_level; /* KitLinkedStripLevel */ + bool remove_signature; +} KitLinkedRewriteOptions; + +typedef struct KitLinkedRewriteReport { + uint32_t removed_debug_sections; + uint32_t removed_symbols; + bool had_signature; + bool removed_signature; +} KitLinkedRewriteReport; + +KIT_API KitStatus kit_obj_rewrite_linked( + const KitContext*, KitSlice name, const KitSlice* input, + const KitLinkedRewriteOptions*, KitWriter* out, + KitLinkedRewriteReport* report_out); + /* Roundtrip: open an object via kit_obj_open, then hand its underlying * builder back. The builder is the same one the reader populated; it is * already finalized, so callers may inspect it (e.g. iterate sections via diff --git a/src/api/object_rewrite.c b/src/api/object_rewrite.c @@ -0,0 +1,27 @@ +#include <kit/object.h> + +#include "core/diag.h" +#include "obj/format.h" + +KitStatus kit_obj_rewrite_linked(const KitContext* ctx, KitSlice name, + const KitSlice* input, + const KitLinkedRewriteOptions* opts, + KitWriter* out, + KitLinkedRewriteReport* report_out) { + KitBinFmt bin; + const ObjFormatImpl* fmt; + if (!ctx || !input || !opts || !out || + (opts->strip_level != KIT_LINKED_STRIP_DEBUG && + opts->strip_level != KIT_LINKED_STRIP_ALL)) + return KIT_INVALID; + bin = kit_detect_fmt(input->data, input->len); + fmt = (bin == KIT_BIN_PE) ? obj_format_lookup(KIT_OBJ_COFF) + : obj_format_lookup_bin(bin); + if (!fmt || !fmt->rewrite_linked) { + kit_ctx_diagf(ctx, "%.*s: linked-image rewrite is unsupported for this " + "format", + KIT_SLICE_ARG(name)); + return KIT_UNSUPPORTED; + } + return fmt->rewrite_linked(ctx, name, input, opts, out, report_out); +} diff --git a/src/obj/coff/rewrite.c b/src/obj/coff/rewrite.c @@ -0,0 +1,253 @@ +#include "obj/rewrite.h" + +#include <limits.h> +#include <string.h> + +#include "obj/coff/coff.h" + +#define PE_DEBUG_DIRECTORY_SIZE 28u + +typedef struct CoffRewriteView { + ObjRewrite rw; + u8* file_header; + u8* optional_header; + u8* section_headers; + u16 section_count; + u16 optional_size; + u32 directory_count; + u32 size_of_headers; +} CoffRewriteView; + +static KitStatus coff_bad(CoffRewriteView* v, const char* detail) { + kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked PE image (%s)", + KIT_SLICE_ARG(v->rw.name), detail); + return KIT_MALFORMED; +} + +static u8* coff_directory(CoffRewriteView* v, u32 index) { + u64 off = 112u + (u64)index * COFF_DATA_DIRECTORY_SIZE; + if (index >= v->directory_count || off + COFF_DATA_DIRECTORY_SIZE > + v->optional_size) + return NULL; + return v->optional_header + (size_t)off; +} + +static u8* coff_section(CoffRewriteView* v, u32 index) { + if (index >= v->section_count) return NULL; + return v->section_headers + (size_t)index * COFF_SECTION_HEADER_SIZE; +} + +/* Translate a PE RVA range to its complete on-disk representation. Header + * RVAs are file offsets; section RVAs are bounded by SizeOfRawData because + * zero-fill tails have no bytes to rewrite. */ +static int coff_rva_file_range(CoffRewriteView* v, u32 rva, u32 size, + u64* off_out) { + u32 i; + if ((u64)rva + size > UINT32_MAX + 1ull) return 0; + if (rva < v->size_of_headers) { + if ((u64)rva + size > v->size_of_headers || + !obj_rw_range(v->rw.len, rva, size)) + return 0; + *off_out = rva; + return 1; + } + for (i = 0; i < v->section_count; ++i) { + const u8* sec = coff_section(v, i); + u32 va = obj_rw_u32(sec + 12, 0); + u32 raw_size = obj_rw_u32(sec + 16, 0); + u32 raw_off = obj_rw_u32(sec + 20, 0); + u64 delta; + if (rva < va) continue; + delta = (u64)rva - va; + if (delta > raw_size || size > (u64)raw_size - delta) continue; + if (!obj_rw_range(v->rw.len, (u64)raw_off + delta, size)) return 0; + *off_out = (u64)raw_off + delta; + return 1; + } + return 0; +} + +static int coff_fixed_prefix(const u8* name, const char* prefix) { + size_t n = strlen(prefix); + return n <= 8u && memcmp(name, prefix, n) == 0; +} + +static int coff_debug_section(const u8* sec) { + const u8* name = sec; + return coff_fixed_prefix(name, ".debug") || + coff_fixed_prefix(name, ".zdebug") || + coff_fixed_prefix(name, ".stab"); +} + +static KitStatus coff_validate(CoffRewriteView* v, const KitContext* ctx, + KitSlice name, const KitSlice* input) { + KitStatus st; + u32 peoff, i; + u64 sections_size; + memset(v, 0, sizeof *v); + st = obj_rewrite_init(&v->rw, ctx, name, input); + if (st != KIT_OK) return st; + if (v->rw.len < COFF_DOS_HEADER_SIZE || + obj_rw_u16(v->rw.bytes, 0) != IMAGE_DOS_SIGNATURE) + return coff_bad(v, "DOS header"); + peoff = obj_rw_u32(v->rw.bytes + 0x3cu, 0); + if (!obj_rw_range(v->rw.len, peoff, + 4u + COFF_FILE_HEADER_SIZE) || + obj_rw_u32(v->rw.bytes + peoff, 0) != IMAGE_NT_SIGNATURE) + return coff_bad(v, "PE header"); + v->file_header = v->rw.bytes + peoff + 4u; + v->section_count = obj_rw_u16(v->file_header + 2, 0); + v->optional_size = obj_rw_u16(v->file_header + 16, 0); + if (v->optional_size == 0) { + kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image", + KIT_SLICE_ARG(name)); + return KIT_INVALID; + } + if (v->optional_size < 112u || + !obj_rw_range(v->rw.len, (u64)peoff + 24u, v->optional_size)) + return coff_bad(v, "optional header"); + v->optional_header = v->file_header + COFF_FILE_HEADER_SIZE; + if (obj_rw_u16(v->optional_header, 0) != + IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return coff_bad(v, "PE32+ optional header"); + v->size_of_headers = obj_rw_u32(v->optional_header + 60, 0); + v->directory_count = obj_rw_u32(v->optional_header + 108, 0); + if (v->directory_count > COFF_NUM_DATA_DIRECTORIES) + v->directory_count = COFF_NUM_DATA_DIRECTORIES; + if ((u64)112u + (u64)v->directory_count * COFF_DATA_DIRECTORY_SIZE > + v->optional_size) + return coff_bad(v, "data directories"); + v->section_headers = v->optional_header + v->optional_size; + sections_size = (u64)v->section_count * COFF_SECTION_HEADER_SIZE; + if (!obj_rw_range(v->rw.len, + (u64)(v->section_headers - v->rw.bytes), sections_size)) + return coff_bad(v, "section table"); + for (i = 0; i < v->section_count; ++i) { + const u8* sec = coff_section(v, i); + u32 raw_size = obj_rw_u32(sec + 16, 0); + u32 raw_off = obj_rw_u32(sec + 20, 0); + if (raw_size && !obj_rw_range(v->rw.len, raw_off, raw_size)) + return coff_bad(v, "section contents"); + } + return KIT_OK; +} + +static KitStatus coff_check_signature(CoffRewriteView* v, + const KitLinkedRewriteOptions* opts) { + u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_SECURITY); + u32 off, size; + if (!dir) return KIT_OK; + off = obj_rw_u32(dir, 0); + size = obj_rw_u32(dir + 4, 0); + if (!off && !size) return KIT_OK; + if (!off || !size || !obj_rw_range(v->rw.len, off, size)) + return coff_bad(v, "certificate table"); + v->rw.report.had_signature = true; + if (!opts->remove_signature) { + kit_ctx_diagf(v->rw.ctx, + "%.*s: signed PE image is unchanged; pass " + "--remove-signature and sign the result again", + KIT_SLICE_ARG(v->rw.name)); + return KIT_UNSUPPORTED; + } + memset(v->rw.bytes + off, 0, size); + memset(dir, 0, COFF_DATA_DIRECTORY_SIZE); + v->rw.report.removed_signature = true; + return KIT_OK; +} + +static KitStatus coff_strip_debug_directory(CoffRewriteView* v) { + u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_DEBUG); + u32 rva, size, i; + u64 off; + if (!dir) return KIT_OK; + rva = obj_rw_u32(dir, 0); + size = obj_rw_u32(dir + 4, 0); + if (!rva && !size) return KIT_OK; + if (!rva || !size || size % PE_DEBUG_DIRECTORY_SIZE != 0 || + !coff_rva_file_range(v, rva, size, &off)) + return coff_bad(v, "debug directory"); + for (i = 0; i < size / PE_DEBUG_DIRECTORY_SIZE; ++i) { + const u8* entry = v->rw.input + (size_t)off + + (size_t)i * PE_DEBUG_DIRECTORY_SIZE; + u32 data_size = obj_rw_u32(entry + 16, 0); + u32 data_off = obj_rw_u32(entry + 24, 0); + if (data_size) { + if (!data_off || !obj_rw_range(v->rw.len, data_off, data_size)) + return coff_bad(v, "debug payload"); + memset(v->rw.bytes + data_off, 0, data_size); + } + } + memset(v->rw.bytes + (size_t)off, 0, size); + memset(dir, 0, COFF_DATA_DIRECTORY_SIZE); + ++v->rw.report.removed_debug_sections; + return KIT_OK; +} + +static KitStatus coff_strip_debug_sections(CoffRewriteView* v) { + u32 i; + for (i = 0; i < v->section_count; ++i) { + u8* sec = coff_section(v, i); + u32 raw_size, raw_off; + if (!coff_debug_section(sec)) continue; + raw_size = obj_rw_u32(sec + 16, 0); + raw_off = obj_rw_u32(sec + 20, 0); + if (raw_size) memset(v->rw.bytes + raw_off, 0, raw_size); + /* Leave the ordinal in place for any retained COFF symbol records, but + * make the section non-mapped and contentless. RVA-based loader metadata + * and every non-debug section retain their original layout. */ + memset(sec, 0, 8u); + obj_rw_put_u32(sec + 8, 0, 0); + obj_rw_put_u32(sec + 16, 0, 0); + obj_rw_put_u32(sec + 20, 0, 0); + obj_rw_put_u32(sec + 36, IMAGE_SCN_MEM_DISCARDABLE, 0); + ++v->rw.report.removed_debug_sections; + } + return KIT_OK; +} + +static KitStatus coff_strip_symbol_table(CoffRewriteView* v) { + u32 off = obj_rw_u32(v->file_header + 8, 0); + u32 count = obj_rw_u32(v->file_header + 12, 0); + u64 symbols_size, end, total; + u32 strings_size; + if (!off && !count) return KIT_OK; + if (!off || count > UINT32_MAX / COFF_SYMBOL_SIZE) + return coff_bad(v, "COFF symbol table"); + symbols_size = (u64)count * COFF_SYMBOL_SIZE; + end = (u64)off + symbols_size; + if (!obj_rw_range(v->rw.len, off, symbols_size) || + !obj_rw_range(v->rw.len, end, 4u)) + return coff_bad(v, "COFF symbol table"); + strings_size = obj_rw_u32(v->rw.bytes + (size_t)end, 0); + if (strings_size < 4u || !obj_rw_range(v->rw.len, end, strings_size)) + return coff_bad(v, "COFF string table"); + total = symbols_size + strings_size; + memset(v->rw.bytes + off, 0, (size_t)total); + obj_rw_put_u32(v->file_header + 8, 0, 0); + obj_rw_put_u32(v->file_header + 12, 0, 0); + v->rw.report.removed_symbols += count; + return KIT_OK; +} + +KitStatus obj_rewrite_coff(const KitContext* ctx, KitSlice name, + const KitSlice* input, + const KitLinkedRewriteOptions* opts, + KitWriter* out, + KitLinkedRewriteReport* report_out) { + CoffRewriteView v; + KitStatus st = coff_validate(&v, ctx, name, input); + if (st == KIT_OK) st = coff_check_signature(&v, opts); + if (st == KIT_OK) st = coff_strip_debug_directory(&v); + if (st == KIT_OK) st = coff_strip_debug_sections(&v); + if (st == KIT_OK && opts->strip_level == KIT_LINKED_STRIP_ALL) + st = coff_strip_symbol_table(&v); + if (st == KIT_OK) { + u16 characteristics = obj_rw_u16(v.file_header + 18, 0); + obj_rw_put_u16(v.file_header + 18, + (u16)(characteristics | IMAGE_FILE_DEBUG_STRIPPED), 0); + st = obj_rewrite_commit(&v.rw, out, report_out); + } + if (st != KIT_OK) obj_rewrite_discard(&v.rw); + return st; +} diff --git a/src/obj/elf/rewrite.c b/src/obj/elf/rewrite.c @@ -0,0 +1,300 @@ +#include "obj/rewrite.h" + +#include <limits.h> +#include <string.h> + +#include "obj/elf/elf.h" + +#define SHT_SYMTAB_SHNDX 18u +#define SHN_XINDEX 0xffffu + +typedef struct ElfRewriteView { + ObjRewrite rw; + int is32; + int be; + u64 shoff; + u32 shentsize; + u32 shnum; + u32 shstrndx; + u64 shstr_off; + u64 shstr_size; +} ElfRewriteView; + +typedef struct ElfRewriteSec { + u32 name; + u32 type; + u64 flags; + u64 off; + u64 size; + u32 link; + u32 info; + u64 entsize; + u8* hdr; +} ElfRewriteSec; + +static KitStatus elf_bad(ElfRewriteView* v, const char* detail) { + kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked ELF image (%s)", + KIT_SLICE_ARG(v->rw.name), detail); + return KIT_MALFORMED; +} + +static int elf_sec(ElfRewriteView* v, u32 index, ElfRewriteSec* out) { + u64 pos; + u8* p; + if (index >= v->shnum || + (u64)index > (UINT64_MAX - v->shoff) / v->shentsize) + return 0; + pos = v->shoff + (u64)index * v->shentsize; + if (!obj_rw_range(v->rw.len, pos, v->shentsize)) return 0; + p = v->rw.bytes + (size_t)pos; + memset(out, 0, sizeof *out); + out->name = obj_rw_u32(p, v->be); + out->type = obj_rw_u32(p + 4, v->be); + if (v->is32) { + out->flags = obj_rw_u32(p + 8, v->be); + out->off = obj_rw_u32(p + 16, v->be); + out->size = obj_rw_u32(p + 20, v->be); + out->link = obj_rw_u32(p + 24, v->be); + out->info = obj_rw_u32(p + 28, v->be); + out->entsize = obj_rw_u32(p + 36, v->be); + } else { + out->flags = obj_rw_u64(p + 8, v->be); + out->off = obj_rw_u64(p + 24, v->be); + out->size = obj_rw_u64(p + 32, v->be); + out->link = obj_rw_u32(p + 40, v->be); + out->info = obj_rw_u32(p + 44, v->be); + out->entsize = obj_rw_u64(p + 56, v->be); + } + out->hdr = p; + return 1; +} + +static int elf_name(ElfRewriteView* v, const ElfRewriteSec* s, + const char** name, size_t* len) { + u64 remain; + const char* p; + size_t n = 0; + if ((u64)s->name >= v->shstr_size) return 0; + remain = v->shstr_size - s->name; + p = (const char*)v->rw.bytes + (size_t)(v->shstr_off + s->name); + while ((u64)n < remain && p[n]) ++n; + if ((u64)n == remain) return 0; + *name = p; + *len = n; + return 1; +} + +static int name_eq(const char* name, size_t len, const char* want) { + size_t n = strlen(want); + return len == n && memcmp(name, want, n) == 0; +} + +static int name_prefix(const char* name, size_t len, const char* prefix) { + size_t n = strlen(prefix); + return len >= n && memcmp(name, prefix, n) == 0; +} + +static int elf_debug_name(const char* name, size_t len) { + return name_prefix(name, len, ".debug_") || + name_prefix(name, len, ".zdebug_") || + name_prefix(name, len, ".stab") || + name_eq(name, len, ".gdb_index") || + name_eq(name, len, ".gnu_debuglink") || + name_eq(name, len, ".gnu_debugaltlink") || + name_eq(name, len, ".ctf") || name_prefix(name, len, ".BTF"); +} + +static int elf_signature_name(const char* name, size_t len) { + return name_eq(name, len, ".signature") || name_eq(name, len, ".sig") || + name_eq(name, len, ".ima") || + name_eq(name, len, ".note.signature") || + name_eq(name, len, ".note.sigstore") || + name_eq(name, len, ".module_sig"); +} + +static KitStatus elf_view_init(ElfRewriteView* v, const KitContext* ctx, + KitSlice name, const KitSlice* input) { + KitStatus st; + u8* e; + u16 type, raw_shnum, raw_shstr; + ElfRewriteSec zero, str; + memset(v, 0, sizeof *v); + st = obj_rewrite_init(&v->rw, ctx, name, input); + if (st != KIT_OK) return st; + e = v->rw.bytes; + if (v->rw.len < ELF32_EHDR_SIZE || e[0] != 0x7f || e[1] != 'E' || + e[2] != 'L' || e[3] != 'F') + return elf_bad(v, "header"); + if (e[EI_CLASS] == ELFCLASS32) + v->is32 = 1; + else if (e[EI_CLASS] != ELFCLASS64) + return elf_bad(v, "class"); + if (e[EI_DATA] == ELFDATA2LSB) + v->be = 0; + else if (e[EI_DATA] == 2) + v->be = 1; + else + return elf_bad(v, "byte order"); + if ((!v->is32 && v->rw.len < ELF64_EHDR_SIZE)) + return elf_bad(v, "truncated header"); + type = obj_rw_u16(e + 16, v->be); + if (type == ET_REL) { + kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image", + KIT_SLICE_ARG(name)); + return KIT_INVALID; + } + if (type != ET_EXEC && type != ET_DYN) + return elf_bad(v, "unsupported e_type"); + if (v->is32) { + v->shoff = obj_rw_u32(e + 32, v->be); + v->shentsize = obj_rw_u16(e + 46, v->be); + raw_shnum = obj_rw_u16(e + 48, v->be); + raw_shstr = obj_rw_u16(e + 50, v->be); + if (v->shentsize < ELF32_SHDR_SIZE) return elf_bad(v, "section size"); + } else { + v->shoff = obj_rw_u64(e + 40, v->be); + v->shentsize = obj_rw_u16(e + 58, v->be); + raw_shnum = obj_rw_u16(e + 60, v->be); + raw_shstr = obj_rw_u16(e + 62, v->be); + if (v->shentsize < ELF64_SHDR_SIZE) return elf_bad(v, "section size"); + } + v->shnum = raw_shnum; + v->shstrndx = raw_shstr; + if (v->shoff == 0 || !obj_rw_range(v->rw.len, v->shoff, v->shentsize)) + return elf_bad(v, "section table"); + /* Read section zero with a temporary one-entry bound to recover the ELF + * extended section-count/string-index encodings. */ + if (v->shnum == 0 || v->shstrndx == SHN_XINDEX) { + v->shnum = 1; + if (!elf_sec(v, 0, &zero)) return elf_bad(v, "section zero"); + if (raw_shnum == 0) { + if (zero.size == 0 || zero.size > UINT32_MAX) + return elf_bad(v, "extended section count"); + v->shnum = (u32)zero.size; + } else + v->shnum = raw_shnum; + if (raw_shstr == SHN_XINDEX) v->shstrndx = zero.link; + } + if (v->shnum == 0 || v->shstrndx >= v->shnum || + !obj_rw_range(v->rw.len, v->shoff, + (u64)v->shnum * v->shentsize)) + return elf_bad(v, "section bounds"); + if (!elf_sec(v, v->shstrndx, &str) || str.type != SHT_STRTAB || + !obj_rw_range(v->rw.len, str.off, str.size)) + return elf_bad(v, "section-name table"); + v->shstr_off = str.off; + v->shstr_size = str.size; + return KIT_OK; +} + +KitStatus obj_rewrite_elf(const KitContext* ctx, KitSlice name, + const KitSlice* input, + const KitLinkedRewriteOptions* opts, KitWriter* out, + KitLinkedRewriteReport* report_out) { + ElfRewriteView v; + u8* remove = NULL; + size_t remove_size; + u32 i; + int changed; + KitStatus st = elf_view_init(&v, ctx, name, input); + if (st != KIT_OK) { + obj_rewrite_discard(&v.rw); + return st; + } + remove_size = v.shnum ? (size_t)v.shnum : 1u; + remove = (u8*)ctx->heap->alloc(ctx->heap, remove_size, 1u); + if (!remove) { + obj_rewrite_discard(&v.rw); + return KIT_NOMEM; + } + memset(remove, 0, remove_size); + + for (i = 1; i < v.shnum; ++i) { + ElfRewriteSec s; + const char* secname = "<unnamed>"; + size_t secname_len = sizeof("<unnamed>") - 1u; + if (!elf_sec(&v, i, &s) || !elf_name(&v, &s, &secname, &secname_len)) { + st = elf_bad(&v, "section record"); + goto done; + } + if (elf_signature_name(secname, secname_len)) { + v.rw.report.had_signature = true; + if (opts->remove_signature) remove[i] = 1; + } + if (elf_debug_name(secname, secname_len)) remove[i] = 1; + if (opts->strip_level == KIT_LINKED_STRIP_ALL && s.type == SHT_SYMTAB) { + remove[i] = 1; + if (s.link < v.shnum && s.link != v.shstrndx) remove[s.link] = 1; + if (s.entsize) + v.rw.report.removed_symbols += (u32)(s.size / s.entsize); + } + } + if (v.rw.report.had_signature && !opts->remove_signature) { + kit_ctx_diagf(ctx, + "%.*s: signed ELF image is unchanged; pass " + "--remove-signature and sign the result again", + KIT_SLICE_ARG(name)); + st = KIT_UNSUPPORTED; + goto done; + } + v.rw.report.removed_signature = + v.rw.report.had_signature && opts->remove_signature; + + /* Remove relocation/index companions whose target or symbol table is being + * removed. Iterate to a fixed point because a companion can precede its + * target in the section table. */ + do { + changed = 0; + for (i = 1; i < v.shnum; ++i) { + ElfRewriteSec s; + if (remove[i] || !elf_sec(&v, i, &s)) continue; + if ((s.type == SHT_RELA || s.type == SHT_REL) && + ((s.info < v.shnum && remove[s.info]) || + (s.link < v.shnum && remove[s.link]))) { + remove[i] = 1; + changed = 1; + } else if (s.type == SHT_SYMTAB_SHNDX && s.link < v.shnum && + remove[s.link]) { + remove[i] = 1; + changed = 1; + } + } + } while (changed); + + for (i = 1; i < v.shnum; ++i) { + ElfRewriteSec s; + const char* secname = "<unnamed>"; + size_t secname_len = sizeof("<unnamed>") - 1u; + int was_debug = 0; + if (!remove[i]) continue; + if (!elf_sec(&v, i, &s)) { + st = elf_bad(&v, "removed section"); + goto done; + } + if (elf_name(&v, &s, &secname, &secname_len)) + was_debug = elf_debug_name(secname, secname_len) || + s.type == SHT_RELA || s.type == SHT_REL; + if (s.flags & SHF_ALLOC) { + kit_ctx_diagf(ctx, + "%.*s: refusing to remove mapped ELF section %.*s", + KIT_SLICE_ARG(name), (int)secname_len, secname); + st = KIT_UNSUPPORTED; + goto done; + } + if (s.type != SHT_NOBITS && s.size) { + if (!obj_rw_range(v.rw.len, s.off, s.size)) { + st = elf_bad(&v, "section contents"); + goto done; + } + memset(v.rw.bytes + (size_t)s.off, 0, (size_t)s.size); + } + memset(s.hdr, 0, v.shentsize); + if (was_debug) ++v.rw.report.removed_debug_sections; + } + + st = obj_rewrite_commit(&v.rw, out, report_out); +done: + ctx->heap->free(ctx->heap, remove, remove_size); + if (st != KIT_OK) obj_rewrite_discard(&v.rw); + return st; +} diff --git a/src/obj/format.h b/src/obj/format.h @@ -41,6 +41,10 @@ typedef void (*ObjFormatCoffStubFn)(u8* dst, u64 stub_vaddr, * additionally handles PE images (the dispatcher routes KIT_BIN_PE to COFF). */ typedef KitStatus (*ObjFormatDetectTargetFn)(const u8* data, size_t len, KitTargetSpec* out); +typedef KitStatus (*ObjFormatRewriteLinkedFn)( + const KitContext*, KitSlice name, const KitSlice* input, + const KitLinkedRewriteOptions*, KitWriter* out, + KitLinkedRewriteReport* report_out); /* Synthetic-input hook: invoked before symbol resolution to inject a * synthetic input object (e.g. the COFF __CTOR_LIST__/__DTOR_LIST__ @@ -311,6 +315,10 @@ typedef struct ObjFormatImpl { /* Header-bytes -> KitTargetSpec detector (see ObjFormatDetectTargetFn). */ ObjFormatDetectTargetFn detect_target; + /* Raw linked-image metadata rewrite. This deliberately consumes the + * original bytes instead of an ObjBuilder so loader state is preserved. */ + ObjFormatRewriteLinkedFn rewrite_linked; + /* Optional format-specific linker ingestion policy. */ int (*classify_obj_input)(Compiler*, ObjBuilder*, Sym* soname_out); Sym (*archive_hint)(Compiler*, const char* archive_name); diff --git a/src/obj/macho/rewrite.c b/src/obj/macho/rewrite.c @@ -0,0 +1,355 @@ +#include "obj/rewrite.h" + +#include <limits.h> +#include <string.h> + +#include "obj/macho/macho.h" + +#define LC_CODE_SIGNATURE 0x1du +#define LC_DYLD_INFO 0x22u +#define LC_DYLD_INFO_ONLY (0x22u | LC_REQ_DYLD) +#define N_STAB 0xe0u +#define N_TYPE 0x0eu +#define N_EXT 0x01u +#define INDIRECT_SYMBOL_LOCAL 0x80000000u +#define INDIRECT_SYMBOL_ABS 0x40000000u + +typedef struct MachoRewriteView { + ObjRewrite rw; + u32 filetype; + u32 ncmds; + u32 sizeofcmds; + int has_export_trie; + u8 section_map[256]; +} MachoRewriteView; + +static KitStatus macho_bad(MachoRewriteView* v, const char* detail) { + kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked Mach-O image (%s)", + KIT_SLICE_ARG(v->rw.name), detail); + return KIT_MALFORMED; +} + +static int fixed_name_eq(const u8* name, size_t cap, const char* want) { + size_t n = strlen(want); + return n <= cap && memcmp(name, want, n) == 0 && + (n == cap || name[n] == 0); +} + +static int fixed_name_prefix(const u8* name, size_t cap, const char* prefix) { + size_t n = strlen(prefix); + return n <= cap && memcmp(name, prefix, n) == 0; +} + +static int macho_debug_section(const u8* sec) { + return fixed_name_eq(sec + 16, 16, "__DWARF") || + fixed_name_prefix(sec, 16, "__debug_") || + fixed_name_prefix(sec, 16, "__zdebug_") || + fixed_name_eq(sec, 16, "__apple_names") || + fixed_name_eq(sec, 16, "__apple_types") || + fixed_name_eq(sec, 16, "__apple_namespac") || + fixed_name_eq(sec, 16, "__apple_objc"); +} + +static KitStatus macho_validate(MachoRewriteView* v, const KitContext* ctx, + KitSlice name, const KitSlice* input) { + KitStatus st; + u64 pos, end; + u32 i; + memset(v, 0, sizeof *v); + st = obj_rewrite_init(&v->rw, ctx, name, input); + if (st != KIT_OK) return st; + if (v->rw.len < MACHO_HDR64_SIZE || + obj_rw_u32(v->rw.bytes, 0) != MH_MAGIC_64) + return macho_bad(v, "64-bit little-endian header"); + v->filetype = obj_rw_u32(v->rw.bytes + 12, 0); + if (v->filetype == MH_OBJECT) { + kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image", + KIT_SLICE_ARG(name)); + return KIT_INVALID; + } + if (v->filetype != MH_EXECUTE && v->filetype != MH_DYLIB && + v->filetype != MH_DYLINKER && v->filetype != MH_BUNDLE) + return macho_bad(v, "unsupported file type"); + v->ncmds = obj_rw_u32(v->rw.bytes + 16, 0); + v->sizeofcmds = obj_rw_u32(v->rw.bytes + 20, 0); + pos = MACHO_HDR64_SIZE; + end = pos + v->sizeofcmds; + if (end < pos || !obj_rw_range(v->rw.len, pos, v->sizeofcmds)) + return macho_bad(v, "load-command table"); + for (i = 0; i < v->ncmds; ++i) { + const u8* cmd; + u32 kind, size; + if (!obj_rw_range(v->rw.len, pos, 8)) + return macho_bad(v, "load command"); + cmd = v->rw.input + (size_t)pos; + kind = obj_rw_u32(cmd, 0); + size = obj_rw_u32(cmd + 4, 0); + if (size < 8 || pos + size > end) + return macho_bad(v, "load-command size"); + if (kind == LC_SEGMENT_64) { + u32 nsects; + if (size < MACHO_SEGCMD64_SIZE) + return macho_bad(v, "segment command"); + nsects = obj_rw_u32(cmd + 64, 0); + if ((u64)MACHO_SEGCMD64_SIZE + (u64)nsects * MACHO_SECT64_SIZE > size) + return macho_bad(v, "segment sections"); + } else if (kind == LC_SYMTAB) { + u32 symoff, nsyms, stroff, strsize; + if (size < MACHO_SYMTAB_CMD_SIZE) + return macho_bad(v, "symbol command"); + symoff = obj_rw_u32(cmd + 8, 0); + nsyms = obj_rw_u32(cmd + 12, 0); + stroff = obj_rw_u32(cmd + 16, 0); + strsize = obj_rw_u32(cmd + 20, 0); + if (!obj_rw_range(v->rw.len, symoff, + (u64)nsyms * MACHO_NLIST64_SIZE) || + !obj_rw_range(v->rw.len, stroff, strsize)) + return macho_bad(v, "symbol table"); + } else if (kind == LC_DYSYMTAB) { + u32 indirectoff, nindirect; + if (size < MACHO_DYSYMTAB_CMD_SIZE) + return macho_bad(v, "dynamic-symbol command"); + indirectoff = obj_rw_u32(cmd + 56, 0); + nindirect = obj_rw_u32(cmd + 60, 0); + if (nindirect && + !obj_rw_range(v->rw.len, indirectoff, (u64)nindirect * 4u)) + return macho_bad(v, "indirect symbols"); + } else if (kind == LC_CODE_SIGNATURE) { + u32 off, size_data; + if (size < 16) return macho_bad(v, "code signature command"); + off = obj_rw_u32(cmd + 8, 0); + size_data = obj_rw_u32(cmd + 12, 0); + if (!obj_rw_range(v->rw.len, off, size_data)) + return macho_bad(v, "code signature data"); + v->rw.report.had_signature = true; + } else if (kind == LC_DYLD_EXPORTS_TRIE || kind == LC_DYLD_INFO || + kind == LC_DYLD_INFO_ONLY) { + v->has_export_trie = 1; + } + pos += size; + } + if (pos != end) return macho_bad(v, "load-command count"); + return KIT_OK; +} + +static KitStatus macho_compact_commands(MachoRewriteView* v, + const KitLinkedRewriteOptions* opts) { + u64 src_pos = MACHO_HDR64_SIZE; + u64 dst_pos = MACHO_HDR64_SIZE; + u32 old_section = 1, new_section = 1; + u32 kept_cmds = 0; + u32 i; + memset(v->section_map, 0, sizeof v->section_map); + + for (i = 0; i < v->ncmds; ++i) { + const u8* src = v->rw.input + (size_t)src_pos; + u32 kind = obj_rw_u32(src, 0); + u32 size = obj_rw_u32(src + 4, 0); + if (kind == LC_CODE_SIGNATURE && opts->remove_signature) { + u32 off = obj_rw_u32(src + 8, 0); + u32 n = obj_rw_u32(src + 12, 0); + if (n) memset(v->rw.bytes + off, 0, n); + v->rw.report.removed_signature = true; + src_pos += size; + continue; + } + if (kind == LC_SEGMENT_64) { + u32 nsects = obj_rw_u32(src + 64, 0); + u32 kept = 0, j; + u8* dst = v->rw.bytes + (size_t)dst_pos; + int dwarf_segment = fixed_name_eq(src + 8, 16, "__DWARF"); + memmove(dst, src, MACHO_SEGCMD64_SIZE); + for (j = 0; j < nsects; ++j, ++old_section) { + const u8* sec = src + MACHO_SEGCMD64_SIZE + + (size_t)j * MACHO_SECT64_SIZE; + int debug = dwarf_segment || macho_debug_section(sec); + if (old_section >= sizeof v->section_map) { + return macho_bad(v, "too many sections"); + } + if (debug) { + u64 sec_size = obj_rw_u64(sec + 40, 0); + u32 sec_off = obj_rw_u32(sec + 48, 0); + if (sec_size && sec_off && + obj_rw_range(v->rw.len, sec_off, sec_size)) + memset(v->rw.bytes + sec_off, 0, (size_t)sec_size); + ++v->rw.report.removed_debug_sections; + continue; + } + v->section_map[old_section] = (u8)new_section++; + memmove(dst + MACHO_SEGCMD64_SIZE + + (size_t)kept * MACHO_SECT64_SIZE, + sec, MACHO_SECT64_SIZE); + ++kept; + } + if (dwarf_segment && kept == 0) { + src_pos += size; + continue; + } + size = MACHO_SEGCMD64_SIZE + kept * MACHO_SECT64_SIZE; + obj_rw_put_u32(dst + 4, size, 0); + obj_rw_put_u32(dst + 64, kept, 0); + dst_pos += size; + ++kept_cmds; + src_pos += obj_rw_u32(src + 4, 0); + continue; + } + memmove(v->rw.bytes + (size_t)dst_pos, src, size); + dst_pos += size; + src_pos += size; + ++kept_cmds; + } + if (dst_pos > MACHO_HDR64_SIZE + v->sizeofcmds) + return macho_bad(v, "compacted commands"); + memset(v->rw.bytes + (size_t)dst_pos, 0, + (size_t)(MACHO_HDR64_SIZE + v->sizeofcmds - dst_pos)); + obj_rw_put_u32(v->rw.bytes + 16, kept_cmds, 0); + obj_rw_put_u32(v->rw.bytes + 20, (u32)(dst_pos - MACHO_HDR64_SIZE), 0); + v->ncmds = kept_cmds; + v->sizeofcmds = (u32)(dst_pos - MACHO_HDR64_SIZE); + return KIT_OK; +} + +static u8* macho_find_command(MachoRewriteView* v, u32 want) { + u64 pos = MACHO_HDR64_SIZE; + u32 i; + for (i = 0; i < v->ncmds; ++i) { + u8* cmd = v->rw.bytes + (size_t)pos; + u32 size = obj_rw_u32(cmd + 4, 0); + if (obj_rw_u32(cmd, 0) == want) return cmd; + pos += size; + } + return NULL; +} + +static KitStatus macho_filter_symbols(MachoRewriteView* v, + const KitLinkedRewriteOptions* opts) { + u8* symcmd = macho_find_command(v, LC_SYMTAB); + u8* dysym = macho_find_command(v, LC_DYSYMTAB); + u8 *keep = NULL, *src_syms, *dst_syms; + u32* map = NULL; + u32 symoff, nsyms, newn = 0, i; + u32 indirectoff = 0, nindirect = 0; + size_t keep_size, map_size; + if (!symcmd) return KIT_OK; + symoff = obj_rw_u32(symcmd + 8, 0); + nsyms = obj_rw_u32(symcmd + 12, 0); + if (!nsyms) return KIT_OK; + keep_size = nsyms; + map_size = (size_t)nsyms * sizeof(*map); + keep = (u8*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, keep_size, 1u); + map = (u32*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, map_size, + _Alignof(u32)); + if (!keep || !map) { + if (keep) v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size); + if (map) v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size); + return KIT_NOMEM; + } + memset(keep, 0, keep_size); + for (i = 0; i < nsyms; ++i) map[i] = UINT32_MAX; + src_syms = (u8*)v->rw.input + symoff; + dst_syms = v->rw.bytes + symoff; + for (i = 0; i < nsyms; ++i) { + const u8* sym = src_syms + (size_t)i * MACHO_NLIST64_SIZE; + u8 type = sym[4]; + if (type & N_STAB) continue; + if (opts->strip_level == KIT_LINKED_STRIP_DEBUG) { + keep[i] = 1; + } else if ((type & N_TYPE) == N_UNDF || + ((type & N_EXT) && + (v->filetype == MH_DYLIB || !v->has_export_trie))) { + keep[i] = 1; + } + } + if (dysym) { + indirectoff = obj_rw_u32(dysym + 56, 0); + nindirect = obj_rw_u32(dysym + 60, 0); + for (i = 0; i < nindirect; ++i) { + u32 old = obj_rw_u32(v->rw.bytes + indirectoff + (size_t)i * 4u, 0); + if (!(old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) && old < nsyms) + keep[old] = 1; + } + } + for (i = 0; i < nsyms; ++i) { + u8* dst; + const u8* src; + u8 old_sect; + if (!keep[i]) continue; + src = src_syms + (size_t)i * MACHO_NLIST64_SIZE; + dst = dst_syms + (size_t)newn * MACHO_NLIST64_SIZE; + memmove(dst, src, MACHO_NLIST64_SIZE); + old_sect = dst[5]; + if (old_sect) { + if (!v->section_map[old_sect]) { + /* A non-STAB symbol cannot survive when its defining section was debug + * metadata. Drop it instead of creating an invalid n_sect index. */ + continue; + } + dst[5] = v->section_map[old_sect]; + } + map[i] = newn++; + } + if (newn < nsyms) + memset(dst_syms + (size_t)newn * MACHO_NLIST64_SIZE, 0, + (size_t)(nsyms - newn) * MACHO_NLIST64_SIZE); + obj_rw_put_u32(symcmd + 12, newn, 0); + v->rw.report.removed_symbols += nsyms - newn; + + if (dysym) { + u32 nlocal = 0, nextdef = 0, nundef = 0; + for (i = 0; i < newn; ++i) { + u8 type = dst_syms[(size_t)i * MACHO_NLIST64_SIZE + 4]; + if (!(type & N_EXT)) + ++nlocal; + else if ((type & N_TYPE) == N_UNDF) + ++nundef; + else + ++nextdef; + } + obj_rw_put_u32(dysym + 8, 0, 0); + obj_rw_put_u32(dysym + 12, nlocal, 0); + obj_rw_put_u32(dysym + 16, nlocal, 0); + obj_rw_put_u32(dysym + 20, nextdef, 0); + obj_rw_put_u32(dysym + 24, nlocal + nextdef, 0); + obj_rw_put_u32(dysym + 28, nundef, 0); + for (i = 0; i < nindirect; ++i) { + u8* entry = v->rw.bytes + indirectoff + (size_t)i * 4u; + u32 old = obj_rw_u32(entry, 0); + if (old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) continue; + if (old >= nsyms || map[old] == UINT32_MAX) { + v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size); + v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size); + return macho_bad(v, "indirect-symbol remap"); + } + obj_rw_put_u32(entry, map[old], 0); + } + } + v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size); + v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size); + return KIT_OK; +} + +KitStatus obj_rewrite_macho(const KitContext* ctx, KitSlice name, + const KitSlice* input, + const KitLinkedRewriteOptions* opts, + KitWriter* out, + KitLinkedRewriteReport* report_out) { + MachoRewriteView v; + KitStatus st = macho_validate(&v, ctx, name, input); + if (st != KIT_OK) { + obj_rewrite_discard(&v.rw); + return st; + } + if (v.rw.report.had_signature && !opts->remove_signature) { + kit_ctx_diagf(ctx, + "%.*s: signed Mach-O image is unchanged; pass " + "--remove-signature and sign the result again", + KIT_SLICE_ARG(name)); + obj_rewrite_discard(&v.rw); + return KIT_UNSUPPORTED; + } + st = macho_compact_commands(&v, opts); + if (st == KIT_OK) st = macho_filter_symbols(&v, opts); + if (st == KIT_OK) st = obj_rewrite_commit(&v.rw, out, report_out); + if (st != KIT_OK) obj_rewrite_discard(&v.rw); + return st; +} diff --git a/src/obj/registry.c b/src/obj/registry.c @@ -8,6 +8,7 @@ #include "obj/format.h" #include "obj/macho/macho.h" #include "obj/obj.h" +#include "obj/rewrite.h" #include "obj/wasm/wasm.h" #if KIT_LINK_ENABLED @@ -657,6 +658,7 @@ static const ObjFormatImpl obj_format_impl_elf = { .elf_arch = obj_elf_arch, .elf_machine = obj_elf_machine, .detect_target = detect_elf, + .rewrite_linked = obj_rewrite_elf, }; #endif @@ -691,6 +693,7 @@ static const ObjFormatImpl obj_format_impl_macho = { .macho_arch = obj_macho_arch, .macho_cputype = obj_macho_cputype, .detect_target = detect_macho, + .rewrite_linked = obj_rewrite_macho, }; #endif @@ -733,6 +736,7 @@ static const ObjFormatImpl obj_format_impl_coff = { .coff_arch = obj_coff_arch, .coff_machine = obj_coff_machine, .detect_target = detect_coff, + .rewrite_linked = obj_rewrite_coff, .classify_obj_input = coff_classify_obj_input, .archive_hint = coff_archive_hint, .archive_member = coff_archive_member, diff --git a/src/obj/rewrite.c b/src/obj/rewrite.c @@ -0,0 +1,92 @@ +#include "obj/rewrite.h" + +#include <string.h> + +KitStatus obj_rewrite_init(ObjRewrite* r, const KitContext* ctx, KitSlice name, + const KitSlice* input) { + if (!r || !ctx || !ctx->heap || !input || (!input->data && input->len)) + return KIT_INVALID; + memset(r, 0, sizeof *r); + r->ctx = ctx; + r->name = name; + r->input = input->data; + r->len = input->len; + r->bytes = (u8*)ctx->heap->alloc(ctx->heap, r->len ? r->len : 1u, 1u); + if (!r->bytes) return KIT_NOMEM; + if (r->len) memcpy(r->bytes, r->input, r->len); + return KIT_OK; +} + +void obj_rewrite_discard(ObjRewrite* r) { + if (!r || !r->bytes) return; + r->ctx->heap->free(r->ctx->heap, r->bytes, r->len ? r->len : 1u); + r->bytes = NULL; +} + +KitStatus obj_rewrite_commit(ObjRewrite* r, KitWriter* out, + KitLinkedRewriteReport* report_out) { + KitStatus st; + if (!r || !r->bytes || !out) return KIT_INVALID; + st = r->len ? kit_writer_write(out, r->bytes, r->len) : KIT_OK; + if (st == KIT_OK) st = kit_writer_status(out); + if (st == KIT_OK && report_out) *report_out = r->report; + obj_rewrite_discard(r); + return st == KIT_OK ? KIT_OK : KIT_IO; +} + +int obj_rw_range(size_t len, u64 off, u64 size) { + return off <= (u64)len && size <= (u64)len - off; +} + +u16 obj_rw_u16(const u8* p, int be) { + return be ? (u16)(((u16)p[0] << 8) | p[1]) + : (u16)((u16)p[0] | ((u16)p[1] << 8)); +} + +u32 obj_rw_u32(const u8* p, int be) { + if (be) + return ((u32)p[0] << 24) | ((u32)p[1] << 16) | ((u32)p[2] << 8) | + (u32)p[3]; + return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | + ((u32)p[3] << 24); +} + +u64 obj_rw_u64(const u8* p, int be) { + u32 a = obj_rw_u32(p, be); + u32 b = obj_rw_u32(p + 4, be); + return be ? ((u64)a << 32) | b : (u64)a | ((u64)b << 32); +} + +void obj_rw_put_u16(u8* p, u16 v, int be) { + if (be) { + p[0] = (u8)(v >> 8); + p[1] = (u8)v; + } else { + p[0] = (u8)v; + p[1] = (u8)(v >> 8); + } +} + +void obj_rw_put_u32(u8* p, u32 v, int be) { + if (be) { + p[0] = (u8)(v >> 24); + p[1] = (u8)(v >> 16); + p[2] = (u8)(v >> 8); + p[3] = (u8)v; + } else { + p[0] = (u8)v; + p[1] = (u8)(v >> 8); + p[2] = (u8)(v >> 16); + p[3] = (u8)(v >> 24); + } +} + +void obj_rw_put_u64(u8* p, u64 v, int be) { + if (be) { + obj_rw_put_u32(p, (u32)(v >> 32), 1); + obj_rw_put_u32(p + 4, (u32)v, 1); + } else { + obj_rw_put_u32(p, (u32)v, 0); + obj_rw_put_u32(p + 4, (u32)(v >> 32), 0); + } +} diff --git a/src/obj/rewrite.h b/src/obj/rewrite.h @@ -0,0 +1,42 @@ +#ifndef KIT_OBJ_REWRITE_H +#define KIT_OBJ_REWRITE_H + +#include <kit/object.h> + +#include "core/core.h" +#include "core/diag.h" + +typedef struct ObjRewrite { + const KitContext* ctx; + KitSlice name; + const u8* input; + size_t len; + u8* bytes; + KitLinkedRewriteReport report; +} ObjRewrite; + +KitStatus obj_rewrite_init(ObjRewrite*, const KitContext*, KitSlice name, + const KitSlice* input); +void obj_rewrite_discard(ObjRewrite*); +KitStatus obj_rewrite_commit(ObjRewrite*, KitWriter*, + KitLinkedRewriteReport* report_out); + +int obj_rw_range(size_t len, u64 off, u64 size); +u16 obj_rw_u16(const u8* p, int be); +u32 obj_rw_u32(const u8* p, int be); +u64 obj_rw_u64(const u8* p, int be); +void obj_rw_put_u16(u8* p, u16 v, int be); +void obj_rw_put_u32(u8* p, u32 v, int be); +void obj_rw_put_u64(u8* p, u64 v, int be); + +KitStatus obj_rewrite_elf(const KitContext*, KitSlice, const KitSlice*, + const KitLinkedRewriteOptions*, KitWriter*, + KitLinkedRewriteReport*); +KitStatus obj_rewrite_macho(const KitContext*, KitSlice, const KitSlice*, + const KitLinkedRewriteOptions*, KitWriter*, + KitLinkedRewriteReport*); +KitStatus obj_rewrite_coff(const KitContext*, KitSlice, const KitSlice*, + const KitLinkedRewriteOptions*, KitWriter*, + KitLinkedRewriteReport*); + +#endif diff --git a/test/api/object_rewrite_test.c b/test/api/object_rewrite_test.c @@ -0,0 +1,217 @@ +#include <kit/object.h> + +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "lib/kit_unit.h" + +#define PE_SIZE 0x900u +#define PE_OFF 0x80u +#define FILE_HDR (PE_OFF + 4u) +#define OPT_HDR (FILE_HDR + 20u) +#define SEC_HDR (OPT_HDR + 240u) +#define DEBUG_RAW 0x400u +#define RDATA_RAW 0x600u +#define CERT_RAW 0x800u +#define SYMTAB_RAW 0x880u + +static void put16(uint8_t* p, uint16_t v) { + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static void put32(uint8_t* p, uint32_t v) { + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static uint32_t get32(const uint8_t* p) { + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void put_dir(uint8_t* pe, uint32_t index, uint32_t addr, + uint32_t size) { + uint8_t* d = pe + OPT_HDR + 112u + index * 8u; + put32(d, addr); + put32(d + 4, size); +} + +static void put_section(uint8_t* pe, uint32_t index, const char* name, + uint32_t virtual_size, uint32_t va, + uint32_t raw_size, uint32_t raw_off, + uint32_t flags) { + uint8_t* s = pe + SEC_HDR + index * 40u; + size_t n = strlen(name); + if (n > 8u) n = 8u; + memcpy(s, name, n); + put32(s + 8, virtual_size); + put32(s + 12, va); + put32(s + 16, raw_size); + put32(s + 20, raw_off); + put32(s + 36, flags); +} + +static void build_signed_debug_pe(uint8_t pe[PE_SIZE]) { + uint8_t* debug_entry; + memset(pe, 0, PE_SIZE); + put16(pe, 0x5a4du); + put32(pe + 0x3c, PE_OFF); + put32(pe + PE_OFF, 0x00004550u); + put16(pe + FILE_HDR, 0x8664u); + put16(pe + FILE_HDR + 2, 3u); + put32(pe + FILE_HDR + 8, SYMTAB_RAW); + put32(pe + FILE_HDR + 12, 1u); + put16(pe + FILE_HDR + 16, 240u); + put16(pe + FILE_HDR + 18, 0x22u); + put16(pe + OPT_HDR, 0x020bu); + put32(pe + OPT_HDR + 16, 0x1000u); + put32(pe + OPT_HDR + 20, 0x1000u); + put32(pe + OPT_HDR + 60, 0x200u); + put32(pe + OPT_HDR + 108, 16u); + put_dir(pe, 1u, 0x33330000u, 0x44u); /* import metadata sentinel */ + put_dir(pe, 4u, CERT_RAW, 0x80u); + put_dir(pe, 6u, 0x3000u, 28u); + put_dir(pe, 9u, 0x55550000u, 0x66u); /* TLS metadata sentinel */ + put_section(pe, 0u, ".text", 0x80u, 0x1000u, 0x200u, 0x200u, + 0x60000020u); + put_section(pe, 1u, ".debug", 0x80u, 0x2000u, 0x200u, DEBUG_RAW, + 0x42000040u); + put_section(pe, 2u, ".rdata", 0x80u, 0x3000u, 0x200u, RDATA_RAW, + 0x40000040u); + memset(pe + 0x200u, 0xa5, 0x200u); + memset(pe + DEBUG_RAW, 0xdb, 0x200u); + debug_entry = pe + RDATA_RAW; + put32(debug_entry + 12, 2u); /* CodeView */ + put32(debug_entry + 16, 16u); + put32(debug_entry + 20, 0x2020u); + put32(debug_entry + 24, 0x420u); + memset(pe + CERT_RAW, 0xce, 0x80u); + memset(pe + SYMTAB_RAW, 0x5a, 18u); + put32(pe + SYMTAB_RAW + 18u, 4u); +} + +static int all_byte(const uint8_t* p, size_t n, uint8_t value) { + size_t i; + for (i = 0; i < n; ++i) + if (p[i] != value) return 0; + return 1; +} + +static void check_signed_transaction(KitUnit* u, const uint8_t* pe) { + uint8_t snapshot[PE_SIZE]; + KitSlice input; + KitLinkedRewriteOptions opts; + KitWriter* out = NULL; + size_t out_len = 99u; + KitStatus st; + memcpy(snapshot, pe, PE_SIZE); + input.data = pe; + input.len = PE_SIZE; + memset(&opts, 0, sizeof opts); + opts.strip_level = KIT_LINKED_STRIP_DEBUG; + CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out, + "signed transaction memory writer"); + st = kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("signed.exe"), &input, + &opts, out, NULL); + CU_EXPECT(u, st == KIT_UNSUPPORTED, + "signed PE without opt-in is unsupported (%d)", (int)st); + (void)kit_writer_mem_bytes(out, &out_len); + CU_EXPECT(u, out_len == 0u, "signed failure emits no bytes"); + CU_EXPECT(u, memcmp(pe, snapshot, PE_SIZE) == 0, + "signed failure leaves input unchanged"); + CU_EXPECT(u, strstr(u->last_diag, "--remove-signature") != NULL, + "signed failure explains explicit opt-in"); + kit_writer_close(out); +} + +static void check_strip_debug(KitUnit* u, const uint8_t* pe) { + KitSlice input; + KitLinkedRewriteOptions opts; + KitLinkedRewriteReport report; + KitWriter* out = NULL; + const uint8_t* bytes; + size_t len = 0; + memset(&opts, 0, sizeof opts); + input.data = pe; + input.len = PE_SIZE; + memset(&report, 0, sizeof report); + opts.strip_level = KIT_LINKED_STRIP_DEBUG; + opts.remove_signature = true; + CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out, + "debug rewrite memory writer"); + CU_EXPECT(u, + kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("debug.exe"), + &input, &opts, out, &report) == KIT_OK, + "debug rewrite succeeds"); + bytes = kit_writer_mem_bytes(out, &len); + CU_EXPECT(u, len == PE_SIZE, "PE rewrite preserves file extent"); + CU_EXPECT(u, get32(bytes + OPT_HDR + 16) == 0x1000u, + "entry point preserved"); + CU_EXPECT(u, get32(bytes + OPT_HDR + 112u + 8u) == 0x33330000u && + get32(bytes + OPT_HDR + 112u + 9u * 8u) == 0x55550000u, + "import and TLS directories preserved"); + CU_EXPECT(u, all_byte(bytes + 0x200u, 0x200u, 0xa5), + "mapped text bytes preserved"); + CU_EXPECT(u, all_byte(bytes + DEBUG_RAW, 0x200u, 0), + "debug section bytes removed"); + CU_EXPECT(u, all_byte(bytes + CERT_RAW, 0x80u, 0), + "certificate bytes removed"); + CU_EXPECT(u, get32(bytes + OPT_HDR + 112u + 4u * 8u) == 0u && + get32(bytes + OPT_HDR + 112u + 6u * 8u) == 0u, + "certificate and debug directories cleared"); + CU_EXPECT(u, get32(bytes + FILE_HDR + 8) == SYMTAB_RAW && + get32(bytes + FILE_HDR + 12) == 1u, + "--strip-debug retains COFF symbol table"); + CU_EXPECT(u, report.had_signature && report.removed_signature, + "signature removal reported"); + CU_EXPECT(u, report.removed_debug_sections == 2u, + "debug directory and section reported"); + kit_writer_close(out); +} + +static void check_strip_all(KitUnit* u, const uint8_t* pe) { + KitSlice input; + KitLinkedRewriteOptions opts; + KitLinkedRewriteReport report; + KitWriter* out = NULL; + const uint8_t* bytes; + size_t len = 0; + memset(&opts, 0, sizeof opts); + input.data = pe; + input.len = PE_SIZE; + memset(&report, 0, sizeof report); + opts.strip_level = KIT_LINKED_STRIP_ALL; + opts.remove_signature = true; + CU_EXPECT(u, kit_writer_mem(&u->heap, &out) == KIT_OK && out, + "all rewrite memory writer"); + CU_EXPECT(u, + kit_obj_rewrite_linked(&u->ctx, KIT_SLICE_LIT("all.exe"), &input, + &opts, out, &report) == KIT_OK, + "strip-all rewrite succeeds"); + bytes = kit_writer_mem_bytes(out, &len); + CU_EXPECT(u, len == PE_SIZE, "strip-all preserves PE extent"); + CU_EXPECT(u, get32(bytes + FILE_HDR + 8) == 0u && + get32(bytes + FILE_HDR + 12) == 0u, + "strip-all clears COFF symbol-table header"); + CU_EXPECT(u, all_byte(bytes + SYMTAB_RAW, 22u, 0), + "strip-all clears COFF symbol and string records"); + CU_EXPECT(u, report.removed_symbols == 1u, + "strip-all reports removed symbols"); + kit_writer_close(out); +} + +int main(void) { + KitUnit u; + uint8_t pe[PE_SIZE]; + kit_unit_init(&u); + build_signed_debug_pe(pe); + check_signed_transaction(&u, pe); + check_strip_debug(&u, pe); + check_strip_all(&u, pe); + kit_unit_summary(&u, "object_rewrite_test"); + return kit_unit_status(&u); +}