kit

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

commit 693766af7c518ae065bcfabbbc9f22692bb47d79
parent ce01a09feccca1f0f7a2c1c0af1e5f37ada34534
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 15:29:33 -0700

ld: grow the -Wl owned-string pool instead of an argc+32 hard cap

ld sized owned_paths/owned_path_sizes at argc+32 and hard-errored "too many
sysroot-expanded paths" once full. But a single -Wl token can mint several
owned cstrings: --section-start consumed THREE slots (ld_record_section_start
dup'd the name, then ld_record_section_addr re-dup'd it) and --defsym up to
two — while adding only 1 to argc. A `-Wl,--section-start=...,...(16+)` token
therefore overran the fixed bound and failed valid input with a misleading
error.

Give the owned pool its own growable capacity (cap_owned_paths): ld_grow_owned
doubles the two parallel arrays when full and runs at the top of ld_own_path
in place of the hard-error; ld_options_release frees with cap_owned_paths.
Also drop the redundant double-dup: split the store core (ld_store_section_start,
taking an already-owned name) out of ld_record_section_addr, and have
ld_record_section_start own the name span once and store it directly, so each
--section-start now consumes two owned slots, not three.

Test (test/buildcmds/run.sh): 20 --section-start (and a --defsym burst) in one
-Wl token link successfully. Red-confirmed against HEAD: the section-start case
exits 2 "too many sysroot-expanded paths".

Diffstat:
Mdriver/cmd/ld.c | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mtest/buildcmds/run.sh | 25+++++++++++++++++++++++++
2 files changed, 95 insertions(+), 13 deletions(-)

diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -122,6 +122,9 @@ typedef struct LdOptions { char** owned_paths; /* sysroot-expanded argv/search paths */ size_t* owned_path_sizes; uint32_t nowned_paths; + size_t cap_owned_paths; /* grows independently of argv_bound: a single -Wl + * token (e.g. --section-start/--defsym) can mint + * several owned strings, so this is not argv-bounded */ /* Shared-library output state. */ int shared; /* -shared */ @@ -293,6 +296,7 @@ static int ld_alloc_arrays(LdOptions* o, int argc) { o->owned_paths = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_paths)); o->owned_path_sizes = driver_alloc_zeroed(o->env, bound * sizeof(*o->owned_path_sizes)); + o->cap_owned_paths = bound; o->rpaths = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpaths)); o->rpath_links = driver_alloc_zeroed(o->env, bound * sizeof(*o->rpath_links)); if (!o->object_files || !o->archives || !o->dsos || !o->order || @@ -516,15 +520,50 @@ static char* ld_join2(DriverEnv* env, const char* a, const char* b, return out; } +/* Ensure room for one more owned string. The owned-string pool is NOT bounded + * by argc: a single -Wl token (--section-start / --defsym) mints two or three + * owned cstrings, so a fixed argv+N cap would reject valid input. Double the + * two parallel arrays together when full. */ +static int ld_grow_owned(LdOptions* o) { + size_t nc; + char** np; + size_t* ns; + if (o->nowned_paths < o->cap_owned_paths) return 0; + nc = o->cap_owned_paths ? o->cap_owned_paths * 2u : 8u; + if (nc <= o->cap_owned_paths) { + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + np = driver_alloc_zeroed(o->env, nc * sizeof(*np)); + ns = driver_alloc_zeroed(o->env, nc * sizeof(*ns)); + if (!np || !ns) { + if (np) driver_free(o->env, np, nc * sizeof(*np)); + if (ns) driver_free(o->env, ns, nc * sizeof(*ns)); + driver_errf(LD_TOOL, "out of memory"); + return 1; + } + if (o->owned_paths) { + driver_memcpy(np, o->owned_paths, o->nowned_paths * sizeof(*np)); + driver_free(o->env, o->owned_paths, o->cap_owned_paths * sizeof(*np)); + } + if (o->owned_path_sizes) { + driver_memcpy(ns, o->owned_path_sizes, o->nowned_paths * sizeof(*ns)); + driver_free(o->env, o->owned_path_sizes, o->cap_owned_paths * sizeof(*ns)); + } + o->owned_paths = np; + o->owned_path_sizes = ns; + o->cap_owned_paths = nc; + return 0; +} + static int ld_own_path(LdOptions* o, char* path, size_t size, const char** out) { if (!path) { driver_errf(LD_TOOL, "out of memory"); return 1; } - if (o->nowned_paths >= o->argv_bound) { + if (ld_grow_owned(o) != 0) { driver_free(o->env, path, size); - driver_errf(LD_TOOL, "too many sysroot-expanded paths"); return 1; } o->owned_paths[o->nowned_paths] = path; @@ -878,14 +917,14 @@ static int ld_record_defsym(LdOptions* o, const char* val, size_t n) { return 0; } -/* --section-start: store a NAME -> ADDR override (NAME may be ".data"/".bss" - * synthesized from -Tdata/-Tbss). */ -static int ld_record_section_addr(LdOptions* o, const char* secname, +/* Store a NAME -> ADDR override where `name_owned` is ALREADY owned by + * o->owned_paths (so this neither dups nor frees it). Parses `addr` and grows + * the section_starts vector. */ +static int ld_store_section_start(LdOptions* o, const char* name_owned, const char* addr) { uint64_t v; - const char* name_owned; KitLinkSectionStart* ss; - if (!secname || !*secname || !addr || !*addr) { + if (!name_owned || !*name_owned || !addr || !*addr) { driver_errf(LD_TOOL, "--section-start requires .NAME=ADDR"); return 1; } @@ -908,9 +947,6 @@ static int ld_record_section_addr(LdOptions* o, const char* secname, o->section_starts = ns; o->cap_section_starts = nc; } - if (ld_span_to_owned_cstr(o, secname, driver_strlen(secname), &name_owned) != - 0) - return 1; ss = &o->section_starts[o->nsection_starts]; memset(ss, 0, sizeof(*ss)); ss->name = kit_slice_cstr(name_owned); @@ -919,6 +955,22 @@ static int ld_record_section_addr(LdOptions* o, const char* secname, return 0; } +/* --section-start: store a NAME -> ADDR override (NAME may be ".data"/".bss" + * synthesized from -Tdata/-Tbss, or a not-yet-owned argv span). Owns `secname` + * once, then stores it. */ +static int ld_record_section_addr(LdOptions* o, const char* secname, + const char* addr) { + const char* name_owned; + if (!secname || !*secname) { + driver_errf(LD_TOOL, "--section-start requires .NAME=ADDR"); + return 1; + } + if (ld_span_to_owned_cstr(o, secname, driver_strlen(secname), &name_owned) != + 0) + return 1; + return ld_store_section_start(o, name_owned, addr); +} + static int ld_record_section_start(LdOptions* o, const char* val, size_t n) { size_t eq = n; size_t i; @@ -931,10 +983,13 @@ static int ld_record_section_start(LdOptions* o, const char* val, size_t n) { val); return 1; } + /* Own the name span ONCE here and store it directly — going through + * ld_record_section_addr would re-dup it, costing a third owned slot per + * --section-start (the overflow this fixes). */ if (ld_span_to_owned_cstr(o, val, eq, &name_owned) != 0) return 1; if (ld_span_to_owned_cstr(o, val + eq + 1u, n - eq - 1u, &addr_owned) != 0) return 1; - return ld_record_section_addr(o, name_owned, addr_owned); + return ld_store_section_start(o, name_owned, addr_owned); } static int ld_record_orphan_handling(LdOptions* o, const char* val, size_t n) { @@ -2983,9 +3038,11 @@ static void ld_options_release(LdOptions* o) { driver_free(o->env, o->dsos, bound * sizeof(*o->dsos)); driver_free(o->env, o->order, bound * sizeof(*o->order)); driver_free(o->env, o->lib_dirs, bound * sizeof(*o->lib_dirs)); - driver_free(o->env, o->owned_paths, bound * sizeof(*o->owned_paths)); + /* owned_paths/owned_path_sizes grow independently of argv_bound. */ + driver_free(o->env, o->owned_paths, + o->cap_owned_paths * sizeof(*o->owned_paths)); driver_free(o->env, o->owned_path_sizes, - bound * sizeof(*o->owned_path_sizes)); + o->cap_owned_paths * sizeof(*o->owned_path_sizes)); driver_free(o->env, o->rpaths, bound * sizeof(*o->rpaths)); driver_free(o->env, o->rpath_links, bound * sizeof(*o->rpath_links)); if (o->defsyms) diff --git a/test/buildcmds/run.sh b/test/buildcmds/run.sh @@ -908,5 +908,30 @@ assert_file_exists cc-wl-symbols-file cc_sym.out contains cc-wl-symbols-start cc_sym.out "_start" # ==== END kernel-FZ Bug 11 ================================================== +# ==== BEGIN kernel-FZ Bug 12: ld -Wl owned-string pool grows ================ +# A single -Wl,--section-start=...,...(many) token adds only 1 to argc but each +# --section-start minted up to THREE owned strings (name dup'd twice + addr), +# overrunning ld's fixed argc+32 owned-paths bound and failing valid input with +# "too many sysroot-expanded paths". The pool now grows independently of argc, +# and each --section-start consumes exactly two slots. Reuses no_pie_start.o. +ld_b12_tok="--section-start=.s00=0x10000" +ld_b12_i=1 +while [ "$ld_b12_i" -le 19 ]; do + ld_b12_addr=$(printf '0x%x' $((0x10000 + ld_b12_i * 0x1000))) + ld_b12_tok="$ld_b12_tok,--section-start=.s$(printf '%02d' "$ld_b12_i")=$ld_b12_addr" + ld_b12_i=$((ld_b12_i + 1)) +done +# 20 --section-start entries in one -Wl token links successfully (was exit 2). +run_ok ld-wl-section-start-many "$KIT" ld -nostdlib -static -no-pie \ + -Ttext 0x500000 no_pie_start.o -Wl,"$ld_b12_tok" -o ld-secstart-many.elf +assert_file_exists ld-wl-section-start-many-file ld-secstart-many.elf +# A -Wl,--defsym burst (two owned strings per alias entry) likewise links. +run_ok ld-wl-defsym-many "$KIT" ld -nostdlib -static -no-pie \ + -Ttext 0x500000 no_pie_start.o \ + -Wl,--defsym=a0=_start,--defsym=a1=_start,--defsym=a2=_start,--defsym=a3=_start,--defsym=a4=_start,--defsym=a5=_start,--defsym=a6=_start,--defsym=a7=_start,--defsym=a8=_start,--defsym=a9=_start \ + -o ld-defsym-many.elf +assert_file_exists ld-wl-defsym-many-file ld-defsym-many.elf +# ==== END kernel-FZ Bug 12 ================================================== + kit_summary build-driver kit_exit