Skip to content

Commit 559e216

Browse files
authored
Merge pull request #18207 from ziglang/elf-error-handler
elf: report errors for some detected malformed object contents
2 parents 72568c1 + da417b8 commit 559e216

File tree

8 files changed

+261
-136
lines changed

8 files changed

+261
-136
lines changed

src/link/Elf.zig

Lines changed: 103 additions & 83 deletions
Large diffs are not rendered by default.

src/link/Elf/Archive.zig

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,10 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void {
3333
const hdr = try reader.readStruct(elf.ar_hdr);
3434

3535
if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) {
36-
// TODO convert into an error
37-
log.debug(
38-
"{s}: invalid header delimiter: expected '{s}', found '{s}'",
39-
.{ self.path, std.fmt.fmtSliceEscapeLower(elf.ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag) },
40-
);
41-
return;
36+
try elf_file.reportParseError(self.path, "invalid archive header delimiter: {s}", .{
37+
std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag),
38+
});
39+
return error.MalformedArchive;
4240
}
4341

4442
const size = try hdr.size();

src/link/Elf/LdScript.zig

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
path: []const u8,
12
cpu_arch: ?std.Target.Cpu.Arch = null,
23
args: std.ArrayListUnmanaged(Elf.SystemLib) = .{},
34

@@ -6,7 +7,7 @@ pub fn deinit(scr: *LdScript, allocator: Allocator) void {
67
}
78

89
pub const Error = error{
9-
InvalidScript,
10+
InvalidLdScript,
1011
UnexpectedToken,
1112
UnknownCpuArch,
1213
OutOfMemory,
@@ -30,13 +31,12 @@ pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void {
3031
try line_col.append(.{ .line = line, .column = column });
3132
switch (tok.id) {
3233
.invalid => {
33-
// TODO errors
34-
// elf_file.base.fatal("invalid token in ld script: '{s}' ({d}:{d})", .{
35-
// tok.get(data),
36-
// line,
37-
// column,
38-
// });
39-
return error.InvalidScript;
34+
try elf_file.reportParseError(scr.path, "invalid token in LD script: '{s}' ({d}:{d})", .{
35+
std.fmt.fmtSliceEscapeLower(tok.get(data)),
36+
line,
37+
column,
38+
});
39+
return error.InvalidLdScript;
4040
},
4141
.new_line => {
4242
line += 1;
@@ -55,17 +55,16 @@ pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void {
5555
.args = &args,
5656
}) catch |err| switch (err) {
5757
error.UnexpectedToken => {
58-
// const last_token_id = parser.it.pos - 1;
59-
// const last_token = parser.it.get(last_token_id);
60-
// const lcol = line_col.items[last_token_id];
61-
// TODO errors
62-
// elf_file.base.fatal("unexpected token in ld script: {s} : '{s}' ({d}:{d})", .{
63-
// @tagName(last_token.id),
64-
// last_token.get(data),
65-
// lcol.line,
66-
// lcol.column,
67-
// });
68-
return error.InvalidScript;
58+
const last_token_id = parser.it.pos - 1;
59+
const last_token = parser.it.get(last_token_id);
60+
const lcol = line_col.items[last_token_id];
61+
try elf_file.reportParseError(scr.path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{
62+
@tagName(last_token.id),
63+
last_token.get(data),
64+
lcol.line,
65+
lcol.column,
66+
});
67+
return error.InvalidLdScript;
6968
},
7069
else => |e| return e,
7170
};

src/link/Elf/Object.zig

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,30 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
5454

5555
self.header = try reader.readStruct(elf.Elf64_Ehdr);
5656

57+
if (elf_file.base.options.target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
58+
try elf_file.reportParseError2(
59+
self.index,
60+
"invalid cpu architecture: {s}",
61+
.{@tagName(self.header.?.e_machine.toTargetCpuArch().?)},
62+
);
63+
return error.InvalidCpuArch;
64+
}
65+
5766
if (self.header.?.e_shnum == 0) return;
5867

5968
const gpa = elf_file.base.allocator;
6069

70+
if (self.data.len < self.header.?.e_shoff or
71+
self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
72+
{
73+
try elf_file.reportParseError2(
74+
self.index,
75+
"corrupt header: section header table extends past the end of file",
76+
.{},
77+
);
78+
return error.MalformedObject;
79+
}
80+
6181
const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
6282
const shdrs = @as(
6383
[*]align(1) const elf.Elf64_Shdr,
@@ -66,10 +86,23 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
6686
try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
6787

6888
for (shdrs) |shdr| {
89+
if (shdr.sh_type != elf.SHT_NOBITS) {
90+
if (self.data.len < shdr.sh_offset or self.data.len < shdr.sh_offset + shdr.sh_size) {
91+
try elf_file.reportParseError2(self.index, "corrupt section: extends past the end of file", .{});
92+
return error.MalformedObject;
93+
}
94+
}
6995
self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
7096
}
7197

72-
try self.strtab.appendSlice(gpa, self.shdrContents(self.header.?.e_shstrndx));
98+
const shstrtab = self.shdrContents(self.header.?.e_shstrndx);
99+
for (shdrs) |shdr| {
100+
if (shdr.sh_name >= shstrtab.len) {
101+
try elf_file.reportParseError2(self.index, "corrupt section name offset", .{});
102+
return error.MalformedObject;
103+
}
104+
}
105+
try self.strtab.appendSlice(gpa, shstrtab);
73106

74107
const symtab_index = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
75108
elf.SHT_SYMTAB => break @as(u16, @intCast(i)),
@@ -81,7 +114,10 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
81114
self.first_global = shdr.sh_info;
82115

83116
const raw_symtab = self.shdrContents(index);
84-
const nsyms = @divExact(raw_symtab.len, @sizeOf(elf.Elf64_Sym));
117+
const nsyms = math.divExact(usize, raw_symtab.len, @sizeOf(elf.Elf64_Sym)) catch {
118+
try elf_file.reportParseError2(self.index, "symbol table not evenly divisible", .{});
119+
return error.MalformedObject;
120+
};
85121
const symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(raw_symtab.ptr))[0..nsyms];
86122

87123
const strtab_bias = @as(u32, @intCast(self.strtab.items.len));

src/link/Elf/SharedObject.zig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
5252
const reader = stream.reader();
5353

5454
self.header = try reader.readStruct(elf.Elf64_Ehdr);
55+
56+
if (elf_file.base.options.target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
57+
try elf_file.reportParseError2(
58+
self.index,
59+
"invalid cpu architecture: {s}",
60+
.{@tagName(self.header.?.e_machine.toTargetCpuArch().?)},
61+
);
62+
return error.InvalidCpuArch;
63+
}
64+
65+
if (self.data.len < self.header.?.e_shoff or
66+
self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
67+
{
68+
try elf_file.reportParseError2(
69+
self.index,
70+
"corrupted header: section header table extends past the end of file",
71+
.{},
72+
);
73+
return error.MalformedObject;
74+
}
75+
5576
const shoff = std.math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
5677

5778
const shdrs = @as(
@@ -61,6 +82,10 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
6182
try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);
6283

6384
for (shdrs, 0..) |shdr, i| {
85+
if (self.data.len < shdr.sh_offset or self.data.len < shdr.sh_offset + shdr.sh_size) {
86+
try elf_file.reportParseError2(self.index, "corrupted section header", .{});
87+
return error.MalformedObject;
88+
}
6489
self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
6590
switch (shdr.sh_type) {
6691
elf.SHT_DYNSYM => self.dynsym_sect_index = @as(u16, @intCast(i)),

src/link/Elf/ZigObject.zig

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! and any relocations that may have been emitted.
44
//! Think about this as fake in-memory Object file for the Zig module.
55

6+
data: std.ArrayListUnmanaged(u8) = .{},
67
path: []const u8,
78
index: File.Index,
89

@@ -101,6 +102,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void {
101102
}
102103

103104
pub fn deinit(self: *ZigObject, allocator: Allocator) void {
105+
self.data.deinit(allocator);
104106
allocator.free(self.path);
105107
self.local_esyms.deinit(allocator);
106108
self.global_esyms.deinit(allocator);
@@ -441,6 +443,27 @@ pub fn markLive(self: *ZigObject, elf_file: *Elf) void {
441443
}
442444
}
443445

446+
/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer.
447+
/// We need this so that we can write to an archive.
448+
/// TODO implement writing ZigObject data directly to a buffer instead.
449+
pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
450+
const gpa = elf_file.base.allocator;
451+
const shsize: u64 = switch (elf_file.ptr_width) {
452+
.p32 => @sizeOf(elf.Elf32_Shdr),
453+
.p64 => @sizeOf(elf.Elf64_Shdr),
454+
};
455+
var end_pos: u64 = elf_file.shdr_table_offset.? + elf_file.shdrs.items.len * shsize;
456+
for (elf_file.shdrs.items) |shdr| {
457+
if (shdr.sh_type == elf.SHT_NOBITS) continue;
458+
end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
459+
}
460+
const size = std.math.cast(usize, end_pos) orelse return error.Overflow;
461+
try self.data.resize(gpa, size);
462+
463+
const amt = try elf_file.base.file.?.preadAll(self.data.items, 0);
464+
if (amt != size) return error.InputOutput;
465+
}
466+
444467
pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) error{OutOfMemory}!void {
445468
const gpa = elf_file.base.allocator;
446469

@@ -457,34 +480,21 @@ pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *
457480
}
458481
}
459482

460-
pub fn updateArSize(self: *ZigObject, elf_file: *Elf) void {
461-
var end_pos: u64 = elf_file.shdr_table_offset.?;
462-
for (elf_file.shdrs.items) |shdr| {
463-
end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
464-
}
465-
self.output_ar_state.size = end_pos;
483+
pub fn updateArSize(self: *ZigObject) void {
484+
self.output_ar_state.size = self.data.items.len;
466485
}
467486

468-
pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void {
469-
const gpa = elf_file.base.allocator;
470-
471-
const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
472-
const contents = try gpa.alloc(u8, size);
473-
defer gpa.free(contents);
474-
475-
const amt = try elf_file.base.file.?.preadAll(contents, 0);
476-
if (amt != self.output_ar_state.size) return error.InputOutput;
477-
487+
pub fn writeAr(self: ZigObject, writer: anytype) !void {
478488
const name = self.path;
479489
const hdr = Archive.setArHdr(.{
480490
.name = if (name.len <= Archive.max_member_name_len)
481491
.{ .name = name }
482492
else
483493
.{ .name_off = self.output_ar_state.name_off },
484-
.size = @intCast(size),
494+
.size = @intCast(self.data.items.len),
485495
});
486496
try writer.writeAll(mem.asBytes(&hdr));
487-
try writer.writeAll(contents);
497+
try writer.writeAll(self.data.items);
488498
}
489499

490500
pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void {

src/link/Elf/file.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,17 @@ pub const File = union(enum) {
162162
state.name_off = try ar_strtab.insert(allocator, path);
163163
}
164164

165-
pub fn updateArSize(file: File, elf_file: *Elf) void {
165+
pub fn updateArSize(file: File) void {
166166
return switch (file) {
167-
.zig_object => |x| x.updateArSize(elf_file),
167+
.zig_object => |x| x.updateArSize(),
168168
.object => |x| x.updateArSize(),
169169
inline else => unreachable,
170170
};
171171
}
172172

173-
pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void {
173+
pub fn writeAr(file: File, writer: anytype) !void {
174174
return switch (file) {
175-
.zig_object => |x| x.writeAr(elf_file, writer),
175+
.zig_object => |x| x.writeAr(writer),
176176
.object => |x| x.writeAr(writer),
177177
inline else => unreachable,
178178
};

test/link/elf.zig

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub fn testAll(b: *Build) *Step {
2929

3030
// Exercise linker in ar mode
3131
elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target }));
32+
elf_step.dependOn(testEmitStaticLibZig(b, .{ .use_llvm = false, .target = musl_target }));
3233

3334
// Exercise linker with self-hosted backend (no LLVM)
3435
elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target }));
@@ -743,6 +744,42 @@ fn testEmitStaticLib(b: *Build, opts: Options) *Step {
743744
return test_step;
744745
}
745746

747+
fn testEmitStaticLibZig(b: *Build, opts: Options) *Step {
748+
const test_step = addTestStep(b, "emit-static-lib-zig", opts);
749+
750+
const obj1 = addObject(b, "obj1", opts);
751+
addZigSourceBytes(obj1,
752+
\\export var foo: i32 = 42;
753+
\\export var bar: i32 = 2;
754+
);
755+
756+
const lib = addStaticLibrary(b, "lib", opts);
757+
addZigSourceBytes(lib,
758+
\\extern var foo: i32;
759+
\\extern var bar: i32;
760+
\\export fn fooBar() i32 {
761+
\\ return foo + bar;
762+
\\}
763+
);
764+
lib.addObject(obj1);
765+
766+
const exe = addExecutable(b, "test", opts);
767+
addZigSourceBytes(exe,
768+
\\const std = @import("std");
769+
\\extern fn fooBar() i32;
770+
\\pub fn main() void {
771+
\\ std.debug.print("{d}", .{fooBar()});
772+
\\}
773+
);
774+
exe.linkLibrary(lib);
775+
776+
const run = addRunArtifact(exe);
777+
run.expectStdErrEqual("44");
778+
test_step.dependOn(&run.step);
779+
780+
return test_step;
781+
}
782+
746783
fn testEmptyObject(b: *Build, opts: Options) *Step {
747784
const test_step = addTestStep(b, "empty-object", opts);
748785

@@ -1875,7 +1912,7 @@ fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
18751912
exe.linkLibC();
18761913

18771914
expectLinkErrors(exe, test_step, .{ .exact = &.{
1878-
"invalid cpu architecture: expected 'x86_64', but found 'aarch64'",
1915+
"invalid cpu architecture: aarch64",
18791916
"note: while parsing /?/a.o",
18801917
} });
18811918

@@ -3305,10 +3342,10 @@ fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
33053342
exe.linkLibC();
33063343

33073344
expectLinkErrors(exe, test_step, .{ .exact = &.{
3308-
"unknown file type",
3345+
"invalid token in LD script: '\\x00\\x00\\x00\\x0c\\x00\\x00\\x00/usr/lib/dyld\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d' (0:829)",
3346+
"note: while parsing /?/liba.dylib",
3347+
"unexpected error: parsing input file failed with error InvalidLdScript",
33093348
"note: while parsing /?/liba.dylib",
3310-
"undefined symbol: foo",
3311-
"note: referenced by /?/a.o:.text",
33123349
} });
33133350

33143351
return test_step;

0 commit comments

Comments
 (0)