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