authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-05-28 12:14:19+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-05-28 12:16:58+01:00
logee13aaeb8ddb8d00d4186c300b616b21da121963
tree9935c9764c2c1a60e8edd09ac6d7ef49a4e217e4
parenta7531ba928938029f2381b8ec5845c2797528b12
signaturelock-open Commit is signed but in an unrecognized format.

Elf2: prevent bogus relocations into debug sections

If a `.debug_*` section---or any non-alloc section for that matter!---references a symbol which is not resolved, we must not emit a dynamic relocation for it. Since a non-alloc section's base virtual address is 0, this could potentially cause crashes in rtld. I'm not sure what the correct behavior is here, but for now, this prevents a possible crash.

1 files changed, 21 insertions(+), 10 deletions(-)

src/link/Elf2.zig+21-10
......@@ -128,7 +128,7 @@ section_by_name: std.array_hash_map.Auto(String(.shstrtab), void),
128128changed_symtab_index: std.array_hash_map.Auto(String(.strtab), void),
129129/// Counts how many relocations are currently in `.rela.dyn` which would require a `DT_TEXTREL`
130130/// entry in the `.dynamic` section. This allows adding `DT_TEXTREL` to the output `.dynamic`
131/// section in `flush` only when it is actually necessary. See also `nodeRequiresTextrel`.
131/// section in `flush` only when it is actually necessary. See also `nodeWantsDsoRelocation`.
132132textrel_count: u32,
133133
134134const_prog_node: std.Progress.Node,
......@@ -1128,8 +1128,10 @@ const SymbolReloc = struct {
11281128 }
11291129 if (reloc.rela_index.unwrap()) |rela_index| {
11301130 reloc.relaSection(elf).relaDeleteOne(elf, rela_index);
1131 if (elf.nodeRequiresTextrel(reloc.node)) {
1132 elf.textrel_count -= 1;
1131 switch (elf.nodeWantsDsoRelocation(reloc.node)) {
1132 .no => unreachable, // there *was* a dynamic relocation!
1133 .yes => {},
1134 .yes_textrel => elf.textrel_count -= 1,
11331135 }
11341136 }
11351137 if (reloc.type.dependsOnTlsSize()) {
......@@ -1672,8 +1674,10 @@ fn setGlobalSymbolValue(
16721674 assert(reloc.target == Symbol.Id.global(global_name));
16731675 if (reloc.rela_index.unwrap()) |rela_index| {
16741676 reloc.relaSection(elf).relaDeleteOne(elf, rela_index);
1675 if (elf.nodeRequiresTextrel(reloc.node)) {
1676 elf.textrel_count -= 1;
1677 switch (elf.nodeWantsDsoRelocation(reloc.node)) {
1678 .no => unreachable, // there *was* a dynamic relocation!
1679 .yes => {},
1680 .yes_textrel => elf.textrel_count -= 1,
16771681 }
16781682 reloc.rela_index = .none;
16791683 }
......@@ -5108,8 +5112,10 @@ fn addSymbolRelocAssumeCapacity(
51085112 } else elf.globalByName(name).?.dynsym_index,
51095113 };
51105114
5111 if (elf.nodeRequiresTextrel(node)) {
5112 elf.textrel_count += 1;
5115 switch (elf.nodeWantsDsoRelocation(node)) {
5116 .no => break :r .none,
5117 .yes => {},
5118 .yes_textrel => elf.textrel_count += 1,
51135119 }
51145120
51155121 // It currently looks like we need a runtime relocation for this.
......@@ -5383,13 +5389,18 @@ fn updateGotEntry(elf: *Elf, got_index: usize) void {
53835389 };
53845390}
53855391
5386/// Returns whether a `DT_TEXTREL` dynamic entry is needed to have a runtime relocation in `node`.
5387fn nodeRequiresTextrel(elf: *Elf, node: MappedFile.Node.Index) bool {
5392/// If `node` cannot contain runtime relocations, returns `.no`.
5393///
5394/// If `node` can contain runtime relocations, `returns `.yes_textrel` if such a relocation requires
5395/// the presence of a `DT_TEXTREL` dynamic entry, or `.yes` otherwise.
5396fn nodeWantsDsoRelocation(elf: *Elf, node: MappedFile.Node.Index) enum { yes, yes_textrel, no } {
53885397 const shndx = elf.getNodeShndx(node);
53895398 const shf: std.elf.SHF = switch (elf.shdrPtr(shndx)) {
53905399 inline else => |shdr| elf.targetLoad(&shdr.flags).shf,
53915400 };
5392 return shf.ALLOC and !shf.WRITE;
5401 if (!shf.ALLOC) return .no;
5402 if (!shf.WRITE) return .yes_textrel;
5403 return .yes;
53935404}
53945405
53955406pub fn updateNav(elf: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {