Skip to content

elf: report errors for some detected malformed object contents #18207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 103 additions & 83 deletions src/link/Elf.zig

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions src/link/Elf/Archive.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@ pub fn parse(self: *Archive, elf_file: *Elf) !void {
const hdr = try reader.readStruct(elf.ar_hdr);

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

const size = try hdr.size();
Expand Down
37 changes: 18 additions & 19 deletions src/link/Elf/LdScript.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
path: []const u8,
cpu_arch: ?std.Target.Cpu.Arch = null,
args: std.ArrayListUnmanaged(Elf.SystemLib) = .{},

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

pub const Error = error{
InvalidScript,
InvalidLdScript,
UnexpectedToken,
UnknownCpuArch,
OutOfMemory,
Expand All @@ -30,13 +31,12 @@ pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void {
try line_col.append(.{ .line = line, .column = column });
switch (tok.id) {
.invalid => {
// TODO errors
// elf_file.base.fatal("invalid token in ld script: '{s}' ({d}:{d})", .{
// tok.get(data),
// line,
// column,
// });
return error.InvalidScript;
try elf_file.reportParseError(scr.path, "invalid token in LD script: '{s}' ({d}:{d})", .{
std.fmt.fmtSliceEscapeLower(tok.get(data)),
line,
column,
});
return error.InvalidLdScript;
},
.new_line => {
line += 1;
Expand All @@ -55,17 +55,16 @@ pub fn parse(scr: *LdScript, data: []const u8, elf_file: *Elf) Error!void {
.args = &args,
}) catch |err| switch (err) {
error.UnexpectedToken => {
// const last_token_id = parser.it.pos - 1;
// const last_token = parser.it.get(last_token_id);
// const lcol = line_col.items[last_token_id];
// TODO errors
// elf_file.base.fatal("unexpected token in ld script: {s} : '{s}' ({d}:{d})", .{
// @tagName(last_token.id),
// last_token.get(data),
// lcol.line,
// lcol.column,
// });
return error.InvalidScript;
const last_token_id = parser.it.pos - 1;
const last_token = parser.it.get(last_token_id);
const lcol = line_col.items[last_token_id];
try elf_file.reportParseError(scr.path, "unexpected token in LD script: {s}: '{s}' ({d}:{d})", .{
@tagName(last_token.id),
last_token.get(data),
lcol.line,
lcol.column,
});
return error.InvalidLdScript;
},
else => |e| return e,
};
Expand Down
40 changes: 38 additions & 2 deletions src/link/Elf/Object.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,30 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {

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

if (elf_file.base.options.target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
try elf_file.reportParseError2(
self.index,
"invalid cpu architecture: {s}",
.{@tagName(self.header.?.e_machine.toTargetCpuArch().?)},
);
return error.InvalidCpuArch;
}

if (self.header.?.e_shnum == 0) return;

const gpa = elf_file.base.allocator;

if (self.data.len < self.header.?.e_shoff or
self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
{
try elf_file.reportParseError2(
self.index,
"corrupt header: section header table extends past the end of file",
.{},
);
return error.MalformedObject;
}

const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
const shdrs = @as(
[*]align(1) const elf.Elf64_Shdr,
Expand All @@ -66,10 +86,23 @@ pub fn parse(self: *Object, elf_file: *Elf) !void {
try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len);

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

try self.strtab.appendSlice(gpa, self.shdrContents(self.header.?.e_shstrndx));
const shstrtab = self.shdrContents(self.header.?.e_shstrndx);
for (shdrs) |shdr| {
if (shdr.sh_name >= shstrtab.len) {
try elf_file.reportParseError2(self.index, "corrupt section name offset", .{});
return error.MalformedObject;
}
}
try self.strtab.appendSlice(gpa, shstrtab);

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

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

const strtab_bias = @as(u32, @intCast(self.strtab.items.len));
Expand Down
25 changes: 25 additions & 0 deletions src/link/Elf/SharedObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ pub fn parse(self: *SharedObject, elf_file: *Elf) !void {
const reader = stream.reader();

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

if (elf_file.base.options.target.cpu.arch != self.header.?.e_machine.toTargetCpuArch().?) {
try elf_file.reportParseError2(
self.index,
"invalid cpu architecture: {s}",
.{@tagName(self.header.?.e_machine.toTargetCpuArch().?)},
);
return error.InvalidCpuArch;
}

if (self.data.len < self.header.?.e_shoff or
self.data.len < self.header.?.e_shoff + @as(u64, @intCast(self.header.?.e_shnum)) * @sizeOf(elf.Elf64_Shdr))
{
try elf_file.reportParseError2(
self.index,
"corrupted header: section header table extends past the end of file",
.{},
);
return error.MalformedObject;
}

const shoff = std.math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;

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

for (shdrs, 0..) |shdr, i| {
if (self.data.len < shdr.sh_offset or self.data.len < shdr.sh_offset + shdr.sh_size) {
try elf_file.reportParseError2(self.index, "corrupted section header", .{});
return error.MalformedObject;
}
self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr));
switch (shdr.sh_type) {
elf.SHT_DYNSYM => self.dynsym_sect_index = @as(u16, @intCast(i)),
Expand Down
46 changes: 28 additions & 18 deletions src/link/Elf/ZigObject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! and any relocations that may have been emitted.
//! Think about this as fake in-memory Object file for the Zig module.

data: std.ArrayListUnmanaged(u8) = .{},
path: []const u8,
index: File.Index,

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

pub fn deinit(self: *ZigObject, allocator: Allocator) void {
self.data.deinit(allocator);
allocator.free(self.path);
self.local_esyms.deinit(allocator);
self.global_esyms.deinit(allocator);
Expand Down Expand Up @@ -441,6 +443,27 @@ pub fn markLive(self: *ZigObject, elf_file: *Elf) void {
}
}

/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer.
/// We need this so that we can write to an archive.
/// TODO implement writing ZigObject data directly to a buffer instead.
pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
const gpa = elf_file.base.allocator;
const shsize: u64 = switch (elf_file.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
.p64 => @sizeOf(elf.Elf64_Shdr),
};
var end_pos: u64 = elf_file.shdr_table_offset.? + elf_file.shdrs.items.len * shsize;
for (elf_file.shdrs.items) |shdr| {
if (shdr.sh_type == elf.SHT_NOBITS) continue;
end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
}
const size = std.math.cast(usize, end_pos) orelse return error.Overflow;
try self.data.resize(gpa, size);

const amt = try elf_file.base.file.?.preadAll(self.data.items, 0);
if (amt != size) return error.InputOutput;
}

pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) error{OutOfMemory}!void {
const gpa = elf_file.base.allocator;

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

pub fn updateArSize(self: *ZigObject, elf_file: *Elf) void {
var end_pos: u64 = elf_file.shdr_table_offset.?;
for (elf_file.shdrs.items) |shdr| {
end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
}
self.output_ar_state.size = end_pos;
pub fn updateArSize(self: *ZigObject) void {
self.output_ar_state.size = self.data.items.len;
}

pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void {
const gpa = elf_file.base.allocator;

const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
const contents = try gpa.alloc(u8, size);
defer gpa.free(contents);

const amt = try elf_file.base.file.?.preadAll(contents, 0);
if (amt != self.output_ar_state.size) return error.InputOutput;

pub fn writeAr(self: ZigObject, writer: anytype) !void {
const name = self.path;
const hdr = Archive.setArHdr(.{
.name = if (name.len <= Archive.max_member_name_len)
.{ .name = name }
else
.{ .name_off = self.output_ar_state.name_off },
.size = @intCast(size),
.size = @intCast(self.data.items.len),
});
try writer.writeAll(mem.asBytes(&hdr));
try writer.writeAll(contents);
try writer.writeAll(self.data.items);
}

pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void {
Expand Down
8 changes: 4 additions & 4 deletions src/link/Elf/file.zig
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,17 @@ pub const File = union(enum) {
state.name_off = try ar_strtab.insert(allocator, path);
}

pub fn updateArSize(file: File, elf_file: *Elf) void {
pub fn updateArSize(file: File) void {
return switch (file) {
.zig_object => |x| x.updateArSize(elf_file),
.zig_object => |x| x.updateArSize(),
.object => |x| x.updateArSize(),
inline else => unreachable,
};
}

pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void {
pub fn writeAr(file: File, writer: anytype) !void {
return switch (file) {
.zig_object => |x| x.writeAr(elf_file, writer),
.zig_object => |x| x.writeAr(writer),
.object => |x| x.writeAr(writer),
inline else => unreachable,
};
Expand Down
45 changes: 41 additions & 4 deletions test/link/elf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn testAll(b: *Build) *Step {

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

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

fn testEmitStaticLibZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-static-lib-zig", opts);

const obj1 = addObject(b, "obj1", opts);
addZigSourceBytes(obj1,
\\export var foo: i32 = 42;
\\export var bar: i32 = 2;
);

const lib = addStaticLibrary(b, "lib", opts);
addZigSourceBytes(lib,
\\extern var foo: i32;
\\extern var bar: i32;
\\export fn fooBar() i32 {
\\ return foo + bar;
\\}
);
lib.addObject(obj1);

const exe = addExecutable(b, "test", opts);
addZigSourceBytes(exe,
\\const std = @import("std");
\\extern fn fooBar() i32;
\\pub fn main() void {
\\ std.debug.print("{d}", .{fooBar()});
\\}
);
exe.linkLibrary(lib);

const run = addRunArtifact(exe);
run.expectStdErrEqual("44");
test_step.dependOn(&run.step);

return test_step;
}

fn testEmptyObject(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-object", opts);

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

expectLinkErrors(exe, test_step, .{ .exact = &.{
"invalid cpu architecture: expected 'x86_64', but found 'aarch64'",
"invalid cpu architecture: aarch64",
"note: while parsing /?/a.o",
} });

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

expectLinkErrors(exe, test_step, .{ .exact = &.{
"unknown file type",
"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)",
"note: while parsing /?/liba.dylib",
"unexpected error: parsing input file failed with error InvalidLdScript",
"note: while parsing /?/liba.dylib",
"undefined symbol: foo",
"note: referenced by /?/a.o:.text",
} });

return test_step;
Expand Down