kit

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

commit a18ab3c7bd9b50e2b745a31773909c118525492f
parent 1bb03d8f2ccde5f3d4f12ddc5391b4674b4000f5
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 27 May 2026 15:27:04 -0700

driver/link: default hosted targets to PIE; fix PE ASLR flags and static-PIE overflow

Hosted targets now default to position-independent executables, matching
modern gcc/clang. driver_default_pic() returns PIE for non-freestanding,
non-WASM targets (set in both target constructors so it survives -target);
driver_link_pie() derives the link-side -pie from the resolved pic model so
-no-pie/-static/-shared/-r suppress it. The -r-vs-pie error now fires only on
an explicit -pie. Mach-O was already unconditional MH_PIE; COFF/Mach-O codegen
is unaffected by pic, so the flip is safe for all three formats.

PE: gate DYNAMIC_BASE/HIGH_ENTROPY_VA on pie. A non-PIE image was advertising
ASLR while also setting RELOCS_STRIPPED, which is contradictory.

ELF: fix an off-by-one in count_dynamic_entries that omitted DT_FLAGS_1. It was
masked for dynamic PIEs (the extra entry spilled into the adjacent .got.plt of
the same allocation) but overflowed the tightly-sized .dynamic of a static PIE
(no imports) — surfaced by the default flip.

Verified on aarch64 under podman: default and explicit -pie both run (rc=42)
against musl's crt and a hand-rolled _start; -no-pie/-static/freestanding stay
ET_EXEC. make test-link: 122 pass, 0 fail.

Diffstat:
Mdriver/cc.c | 2+-
Mdriver/driver.h | 12++++++++++++
Mdriver/env.c | 2+-
Mdriver/ld.c | 7+++++--
Mdriver/target.c | 18+++++++++++++++++-
Msrc/obj/coff/link.c | 15++++++++++-----
Msrc/obj/elf/link_dyn.c | 2+-
7 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/driver/cc.c b/driver/cc.c @@ -2632,7 +2632,7 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, lopts.build_id_len = o->build_id_len; lopts.gc_sections = o->gc_sections; lopts.strip_debug = o->strip_debug; - lopts.pie = o->pie; + lopts.pie = driver_link_pie(o->target, o->pie, o->shared, /*reloc=*/0); lopts.pe_subsystem = o->pe_subsystem; lopts.interp_path = cfree_slice_cstr(o->interp_path); lopts.soname = cfree_slice_cstr(o->soname); diff --git a/driver/driver.h b/driver/driver.h @@ -107,4 +107,16 @@ int driver_target_from_triple(const char* triple, CfreeTarget* out); * nonzero when `buf` is too small. */ int driver_target_to_triple(CfreeTarget target, char* buf, size_t cap); +/* Default PIC/PIE model for a target. Hosted targets default to PIE, + * matching modern gcc/clang and platform norms (ELF -> ET_DYN, Mach-O -> + * MH_PIE, PE/COFF -> .reloc + DYNAMIC_BASE). Freestanding targets (no + * dynamic loader) and WASM stay non-PIE. */ +CfreePic driver_default_pic(CfreeObjFmt obj, CfreeOSKind os); + +/* Resolve whether the link step should emit a position-independent + * executable. An explicit -pie always wins; -shared and -r (relocatable) + * always suppress it; otherwise it follows the target's PIC model. */ +int driver_link_pie(CfreeTarget target, int explicit_pie, int shared, + int relocatable); + #endif diff --git a/driver/env.c b/driver/env.c @@ -1842,7 +1842,7 @@ CfreeTarget driver_host_target(void) { t.ptr_size = (uint8_t)sizeof(void*); t.ptr_align = (uint8_t)sizeof(void*); t.big_endian = 0; - t.pic = CFREE_PIC_NONE; + t.pic = driver_default_pic(t.obj, t.os); t.code_model = CFREE_CM_DEFAULT; return t; } diff --git a/driver/ld.c b/driver/ld.c @@ -956,7 +956,9 @@ static int ld_parse(int argc, char** argv, LdOptions* o) { driver_errf(LD_TOOL, "-r and -shared are incompatible"); return 1; } - if (o->pie || o->target.pic == CFREE_PIC_PIE) { + /* Only an explicit -pie conflicts; a default PIE target.pic is simply + * overridden by -r (driver_link_pie suppresses pie when relocatable). */ + if (o->pie) { driver_errf(LD_TOOL, "-r and -pie are incompatible"); return 1; } @@ -1208,7 +1210,8 @@ static int ld_run_link(LdOptions* o) { lopts.build_id_len = o->build_id_len; lopts.gc_sections = o->gc_sections; lopts.strip_debug = o->strip_debug; - lopts.pie = o->pie; + lopts.pie = + driver_link_pie(o->target, o->pie, o->shared, o->relocatable); lopts.pe_subsystem = o->pe_subsystem; lopts.interp_path = cfree_slice_cstr(o->interp_path); lopts.soname = cfree_slice_cstr(o->soname); diff --git a/driver/target.c b/driver/target.c @@ -14,6 +14,22 @@ static int triple_tok_eq(const char* s, size_t n, const char* lit) { return n == l && memcmp(s, lit, n) == 0; } +CfreePic driver_default_pic(CfreeObjFmt obj, CfreeOSKind os) { + /* WASM has no PIC/PIE concept; freestanding targets have no dynamic + * loader to apply load-time relocations. Everything else is hosted and + * defaults to PIE. */ + if (obj == CFREE_OBJ_WASM) return CFREE_PIC_NONE; + if (os == CFREE_OS_FREESTANDING) return CFREE_PIC_NONE; + return CFREE_PIC_PIE; +} + +int driver_link_pie(CfreeTarget target, int explicit_pie, int shared, + int relocatable) { + if (explicit_pie) return 1; + if (shared || relocatable) return 0; + return target.pic == CFREE_PIC_PIE; +} + int driver_target_from_triple(const char* triple, CfreeTarget* out) { const char* parts[4]; size_t plen[4]; @@ -114,7 +130,7 @@ int driver_target_from_triple(const char* triple, CfreeTarget* out) { t.ptr_align = t.ptr_size; t.big_endian = 0; - t.pic = CFREE_PIC_NONE; + t.pic = driver_default_pic(t.obj, t.os); t.code_model = CFREE_CM_DEFAULT; *out = t; diff --git a/src/obj/coff/link.c b/src/obj/coff/link.c @@ -95,11 +95,16 @@ static SrcLoc no_loc(void) { #define PE_STACK_COMMIT 0x1000ULL #define PE_HEAP_RESERVE 0x100000ULL #define PE_HEAP_COMMIT 0x1000ULL -#define PE_DLL_CHARS \ - (IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | \ - IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | \ - IMAGE_DLLCHARACTERISTICS_NX_COMPAT | \ +/* DllCharacteristics bits that apply regardless of relocatability. */ +#define PE_DLL_CHARS_BASE \ + (IMAGE_DLLCHARACTERISTICS_NX_COMPAT | \ IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE) +/* ASLR bits — only valid when the image carries base relocations (PIE). + * Advertising DYNAMIC_BASE / HIGH_ENTROPY_VA alongside RELOCS_STRIPPED is + * contradictory: the loader has nothing to fix up, so it can't relocate. */ +#define PE_DLL_CHARS_ASLR \ + (IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | \ + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) /* PE32+ DOS-stub-to-PE-signature offsets (manual, since we marshal * field-by-field rather than memcpy'ing the packed struct). */ @@ -1424,7 +1429,7 @@ static void coff_write_optional_header(Writer* w, u32 entry_rva, coff_wr_u32(w, headers_size_padded); coff_wr_u32(w, 0u); /* CheckSum */ coff_wr_u16(w, subsystem ? subsystem : IMAGE_SUBSYSTEM_WINDOWS_CUI); - coff_wr_u16(w, PE_DLL_CHARS); + coff_wr_u16(w, (u16)(PE_DLL_CHARS_BASE | (pie ? PE_DLL_CHARS_ASLR : 0))); coff_wr_u64(w, PE_STACK_RESERVE); coff_wr_u64(w, PE_STACK_COMMIT); coff_wr_u64(w, PE_HEAP_RESERVE); diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c @@ -493,7 +493,7 @@ static u32 count_dynamic_entries(const LinkDynState* dyn) { * DT_PLTGOT DT_PLTRELSZ DT_PLTREL DT_JMPREL * Plus DT_NEEDED per dependency. */ u32 n = dyn->nneeded; - n += 6; /* 5 fixed + DT_NULL */ + n += 7; /* 5 fixed + DT_FLAGS_1 + DT_NULL */ if (dyn->cap_rela_dyn) n += 3; /* DT_RELA + DT_RELASZ + DT_RELAENT */ if (dyn->nrela_plt) n += 4; /* PLT-only entries */ return n;