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:
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