authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-06-20 20:01:39-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-06-20 17:03:50-07:00
logd0575310dce6d8592576951fb2a21640e00141f9
tree104ad0d1ad8f585971a97ebde0435e002d1f8d9f
parentb72d55ea5fa1de0c55e2cfb9aa65f4ec80986bc9

Merge pull request #9168 from LemonBoy/fix-pie

std: Fix PIE startup sequence

9 files changed, 106 insertions(+), 115 deletions(-)

lib/std/os/linux.zig+1
...@@ -34,6 +34,7 @@ pub usingnamespace switch (native_arch) {...@@ -34,6 +34,7 @@ pub usingnamespace switch (native_arch) {
34};34};
35pub usingnamespace @import("bits.zig");35pub usingnamespace @import("bits.zig");
36pub const tls = @import("linux/tls.zig");36pub const tls = @import("linux/tls.zig");
37pub const pie = @import("linux/start_pie.zig");
37pub const BPF = @import("linux/bpf.zig");38pub const BPF = @import("linux/bpf.zig");
38pub usingnamespace @import("linux/io_uring.zig");39pub usingnamespace @import("linux/io_uring.zig");
3940
lib/std/os/linux/start_pie.zig+26-46
...@@ -8,33 +8,35 @@ const R_386_RELATIVE = 8;...@@ -8,33 +8,35 @@ const R_386_RELATIVE = 8;
8const R_ARM_RELATIVE = 23;8const R_ARM_RELATIVE = 23;
9const R_AARCH64_RELATIVE = 1027;9const R_AARCH64_RELATIVE = 1027;
10const R_RISCV_RELATIVE = 3;10const R_RISCV_RELATIVE = 3;
11const R_SPARC_RELATIVE = 22;
1112
12const ARCH_RELATIVE_RELOC = switch (builtin.cpu.arch) {13const R_RELATIVE = switch (builtin.cpu.arch) {
13 .i386 => R_386_RELATIVE,14 .i386 => R_386_RELATIVE,
14 .x86_64 => R_AMD64_RELATIVE,15 .x86_64 => R_AMD64_RELATIVE,
15 .arm => R_ARM_RELATIVE,16 .arm => R_ARM_RELATIVE,
16 .aarch64 => R_AARCH64_RELATIVE,17 .aarch64 => R_AARCH64_RELATIVE,
17 .riscv64 => R_RISCV_RELATIVE,18 .riscv64 => R_RISCV_RELATIVE,
18 else => @compileError("unsupported architecture"),19 else => @compileError("Missing R_RELATIVE definition for this target"),
19};20};
2021
21// Just a convoluted (but necessary) way to obtain the address of the _DYNAMIC[]22// Obtain a pointer to the _DYNAMIC array.
22// vector as PC-relative so that we can use it before any relocation is applied23// We have to compute its address as a PC-relative quantity not to require a
24// relocation that, at this point, is not yet applied.
23fn getDynamicSymbol() [*]elf.Dyn {25fn getDynamicSymbol() [*]elf.Dyn {
24 const addr = switch (builtin.cpu.arch) {26 return switch (builtin.cpu.arch) {
25 .i386 => asm volatile (27 .i386 => asm volatile (
26 \\ .weak _DYNAMIC28 \\ .weak _DYNAMIC
27 \\ .hidden _DYNAMIC29 \\ .hidden _DYNAMIC
28 \\ call 1f30 \\ call 1f
29 \\ 1: pop %[ret]31 \\ 1: pop %[ret]
30 \\ lea _DYNAMIC-1b(%[ret]), %[ret]32 \\ lea _DYNAMIC-1b(%[ret]), %[ret]
31 : [ret] "=r" (-> usize)33 : [ret] "=r" (-> [*]elf.Dyn)
32 ),34 ),
33 .x86_64 => asm volatile (35 .x86_64 => asm volatile (
34 \\ .weak _DYNAMIC36 \\ .weak _DYNAMIC
35 \\ .hidden _DYNAMIC37 \\ .hidden _DYNAMIC
36 \\ lea _DYNAMIC(%%rip), %[ret]38 \\ lea _DYNAMIC(%%rip), %[ret]
37 : [ret] "=r" (-> usize)39 : [ret] "=r" (-> [*]elf.Dyn)
38 ),40 ),
39 // Work around the limited offset range of `ldr`41 // Work around the limited offset range of `ldr`
40 .arm => asm volatile (42 .arm => asm volatile (
...@@ -45,7 +47,7 @@ fn getDynamicSymbol() [*]elf.Dyn {...@@ -45,7 +47,7 @@ fn getDynamicSymbol() [*]elf.Dyn {
45 \\ b 2f47 \\ b 2f
46 \\ 1: .word _DYNAMIC-1b48 \\ 1: .word _DYNAMIC-1b
47 \\ 2:49 \\ 2:
48 : [ret] "=r" (-> usize)50 : [ret] "=r" (-> [*]elf.Dyn)
49 ),51 ),
50 // A simple `adr` is not enough as it has a limited offset range52 // A simple `adr` is not enough as it has a limited offset range
51 .aarch64 => asm volatile (53 .aarch64 => asm volatile (
...@@ -53,61 +55,39 @@ fn getDynamicSymbol() [*]elf.Dyn {...@@ -53,61 +55,39 @@ fn getDynamicSymbol() [*]elf.Dyn {
53 \\ .hidden _DYNAMIC55 \\ .hidden _DYNAMIC
54 \\ adrp %[ret], _DYNAMIC56 \\ adrp %[ret], _DYNAMIC
55 \\ add %[ret], %[ret], #:lo12:_DYNAMIC57 \\ add %[ret], %[ret], #:lo12:_DYNAMIC
56 : [ret] "=r" (-> usize)58 : [ret] "=r" (-> [*]elf.Dyn)
57 ),59 ),
58 .riscv64 => asm volatile (60 .riscv64 => asm volatile (
59 \\ .weak _DYNAMIC61 \\ .weak _DYNAMIC
60 \\ .hidden _DYNAMIC62 \\ .hidden _DYNAMIC
61 \\ lla %[ret], _DYNAMIC63 \\ lla %[ret], _DYNAMIC
62 : [ret] "=r" (-> usize)64 : [ret] "=r" (-> [*]elf.Dyn)
63 ),65 ),
64 else => @compileError("???"),66 else => {
67 @compileError("PIE startup is not yet supported for this target!");
68 },
65 };69 };
66 return @intToPtr([*]elf.Dyn, addr);
67}70}
6871
69pub fn apply_relocations() void {72pub fn relocate(phdrs: []elf.Phdr) void {
70 @setRuntimeSafety(false);73 @setRuntimeSafety(false);
7174
72 const dynv = getDynamicSymbol();75 const dynv = getDynamicSymbol();
73 const auxv = std.os.linux.elf_aux_maybe.?;76 // Recover the delta applied by the loader by comparing the effective and
74 var at_phent: usize = undefined;77 // the theoretical load addresses for the `_DYNAMIC` symbol.
75 var at_phnum: usize = undefined;78 const base_addr = base: {
76 var at_phdr: usize = undefined;
77 var at_hwcap: usize = undefined;
78
79 {
80 var i: usize = 0;
81 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
82 switch (auxv[i].a_type) {
83 elf.AT_PHENT => at_phent = auxv[i].a_un.a_val,
84 elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
85 elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
86 else => continue,
87 }
88 }
89 }
90
91 // Sanity check
92 assert(at_phent == @sizeOf(elf.Phdr));
93
94 // Search the TLS section
95 const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum];
96
97 const base_addr = blk: {
98 for (phdrs) |*phdr| {79 for (phdrs) |*phdr| {
99 if (phdr.p_type == elf.PT_DYNAMIC) {80 if (phdr.p_type != elf.PT_DYNAMIC) continue;
100 break :blk @ptrToInt(&dynv[0]) - phdr.p_vaddr;81 break :base @ptrToInt(dynv) - phdr.p_vaddr;
101 }
102 }82 }
103 unreachable;83 // This is not supposed to happen for well-formed binaries.
84 std.os.abort();
104 };85 };
10586
106 var rel_addr: usize = 0;87 var rel_addr: usize = 0;
107 var rela_addr: usize = 0;88 var rela_addr: usize = 0;
108 var rel_size: usize = 0;89 var rel_size: usize = 0;
109 var rela_size: usize = 0;90 var rela_size: usize = 0;
110
111 {91 {
112 var i: usize = 0;92 var i: usize = 0;
113 while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) {93 while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) {
...@@ -121,18 +101,18 @@ pub fn apply_relocations() void {...@@ -121,18 +101,18 @@ pub fn apply_relocations() void {
121 }101 }
122 }102 }
123103
124 // Perform the relocations104 // Apply the relocations.
125 if (rel_addr != 0) {105 if (rel_addr != 0) {
126 const rel = std.mem.bytesAsSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]);106 const rel = std.mem.bytesAsSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]);
127 for (rel) |r| {107 for (rel) |r| {
128 if (r.r_type() != ARCH_RELATIVE_RELOC) continue;108 if (r.r_type() != R_RELATIVE) continue;
129 @intToPtr(*usize, base_addr + r.r_offset).* += base_addr;109 @intToPtr(*usize, base_addr + r.r_offset).* += base_addr;
130 }110 }
131 }111 }
132 if (rela_addr != 0) {112 if (rela_addr != 0) {
133 const rela = std.mem.bytesAsSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]);113 const rela = std.mem.bytesAsSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]);
134 for (rela) |r| {114 for (rela) |r| {
135 if (r.r_type() != ARCH_RELATIVE_RELOC) continue;115 if (r.r_type() != R_RELATIVE) continue;
136 @intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend);116 @intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend);
137 }117 }
138 }118 }
lib/std/os/linux/tls.zig+4-39
...@@ -190,53 +190,18 @@ pub fn setThreadPointer(addr: usize) void {...@@ -190,53 +190,18 @@ pub fn setThreadPointer(addr: usize) void {
190 }190 }
191}191}
192192
193fn initTLS() void {193fn initTLS(phdrs: []elf.Phdr) void {
194 var tls_phdr: ?*elf.Phdr = null;194 var tls_phdr: ?*elf.Phdr = null;
195 var img_base: usize = 0;195 var img_base: usize = 0;
196196
197 const auxv = std.os.linux.elf_aux_maybe.?;
198 var at_phent: usize = undefined;
199 var at_phnum: usize = undefined;
200 var at_phdr: usize = undefined;
201 var at_hwcap: usize = undefined;
202
203 var i: usize = 0;
204 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
205 switch (auxv[i].a_type) {
206 elf.AT_PHENT => at_phent = auxv[i].a_un.a_val,
207 elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
208 elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
209 elf.AT_HWCAP => at_hwcap = auxv[i].a_un.a_val,
210 else => continue,
211 }
212 }
213
214 // Sanity check
215 assert(at_phent == @sizeOf(elf.Phdr));
216
217 // Find the TLS section
218 const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum];
219
220 for (phdrs) |*phdr| {197 for (phdrs) |*phdr| {
221 switch (phdr.p_type) {198 switch (phdr.p_type) {
222 elf.PT_PHDR => img_base = at_phdr - phdr.p_vaddr,199 elf.PT_PHDR => img_base = @ptrToInt(phdrs.ptr) - phdr.p_vaddr,
223 elf.PT_TLS => tls_phdr = phdr,200 elf.PT_TLS => tls_phdr = phdr,
224 else => {},201 else => {},
225 }202 }
226 }203 }
227204
228 // ARMv6 targets (and earlier) have no support for TLS in hardware
229 // FIXME: Elide the check for targets >= ARMv7 when the target feature API
230 // becomes less verbose (and more usable).
231 if (comptime native_arch.isARM()) {
232 if (at_hwcap & std.os.linux.HWCAP_TLS == 0) {
233 // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls
234 // For the time being use a simple abort instead of a @panic call to
235 // keep the binary bloat under control.
236 std.os.abort();
237 }
238 }
239
240 var tls_align_factor: usize = undefined;205 var tls_align_factor: usize = undefined;
241 var tls_data: []const u8 = undefined;206 var tls_data: []const u8 = undefined;
242 var tls_data_alloc_size: usize = undefined;207 var tls_data_alloc_size: usize = undefined;
...@@ -344,8 +309,8 @@ pub fn prepareTLS(area: []u8) usize {...@@ -344,8 +309,8 @@ pub fn prepareTLS(area: []u8) usize {
344// overhead.309// overhead.
345var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined;310var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined;
346311
347pub fn initStaticTLS() void {312pub fn initStaticTLS(phdrs: []elf.Phdr) void {
348 initTLS();313 initTLS(phdrs);
349314
350 const tls_area = blk: {315 const tls_area = blk: {
351 // Fast path for the common case where the TLS data is really small,316 // Fast path for the common case where the TLS data is really small,
lib/std/start.zig+41-28
...@@ -10,6 +10,7 @@ const std = @import("std.zig");...@@ -10,6 +10,7 @@ const std = @import("std.zig");
10const builtin = @import("builtin");10const builtin = @import("builtin");
11const assert = std.debug.assert;11const assert = std.debug.assert;
12const uefi = std.os.uefi;12const uefi = std.os.uefi;
13const elf = std.elf;
13const tlcsprng = @import("crypto/tlcsprng.zig");14const tlcsprng = @import("crypto/tlcsprng.zig");
14const native_arch = builtin.cpu.arch;15const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;16const native_os = builtin.os.tag;
...@@ -281,49 +282,60 @@ fn posixCallMainAndExit() noreturn {...@@ -281,49 +282,60 @@ fn posixCallMainAndExit() noreturn {
281282
282 if (native_os == .linux) {283 if (native_os == .linux) {
283 // Find the beginning of the auxiliary vector284 // Find the beginning of the auxiliary vector
284 const auxv = @ptrCast([*]std.elf.Auxv, @alignCast(@alignOf(usize), envp.ptr + envp_count + 1));285 const auxv = @ptrCast([*]elf.Auxv, @alignCast(@alignOf(usize), envp.ptr + envp_count + 1));
285 std.os.linux.elf_aux_maybe = auxv;286 std.os.linux.elf_aux_maybe = auxv;
286287
287 // Do this as early as possible, the aux vector is needed288 var at_hwcap: usize = 0;
289 const phdrs = init: {
290 var i: usize = 0;
291 var at_phdr: usize = 0;
292 var at_phnum: usize = 0;
293 while (auxv[i].a_type != elf.AT_NULL) : (i += 1) {
294 switch (auxv[i].a_type) {
295 elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
296 elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
297 elf.AT_HWCAP => at_hwcap = auxv[i].a_un.a_val,
298 else => continue,
299 }
300 }
301 break :init @intToPtr([*]elf.Phdr, at_phdr)[0..at_phnum];
302 };
303
304 // Apply the initial relocations as early as possible in the startup
305 // process.
288 if (builtin.position_independent_executable) {306 if (builtin.position_independent_executable) {
289 @import("os/linux/start_pie.zig").apply_relocations();307 std.os.linux.pie.relocate(phdrs);
290 }308 }
291309
292 // Initialize the TLS area. We do a runtime check here to make sure310 // ARMv6 targets (and earlier) have no support for TLS in hardware.
293 // this code is truly being statically executed and not inside a dynamic311 // FIXME: Elide the check for targets >= ARMv7 when the target feature API
294 // loader, otherwise this would clobber the thread ID register.312 // becomes less verbose (and more usable).
295 const is_dynamic = @import("dynamic_library.zig").get_DYNAMIC() != null;313 if (comptime native_arch.isARM()) {
296 if (!is_dynamic) {314 if (at_hwcap & std.os.linux.HWCAP_TLS == 0) {
297 std.os.linux.tls.initStaticTLS();315 // FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls
316 // For the time being use a simple abort instead of a @panic call to
317 // keep the binary bloat under control.
318 std.os.abort();
319 }
298 }320 }
299321
322 // Initialize the TLS area.
323 std.os.linux.tls.initStaticTLS(phdrs);
324
300 // The way Linux executables represent stack size is via the PT_GNU_STACK325 // The way Linux executables represent stack size is via the PT_GNU_STACK
301 // program header. However the kernel does not recognize it; it always gives 8 MiB.326 // program header. However the kernel does not recognize it; it always gives 8 MiB.
302 // Here we look for the stack size in our program headers and use setrlimit327 // Here we look for the stack size in our program headers and use setrlimit
303 // to ask for more stack space.328 // to ask for more stack space.
304 {329 expandStackSize(phdrs);
305 var i: usize = 0;
306 var at_phdr: usize = undefined;
307 var at_phnum: usize = undefined;
308 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
309 switch (auxv[i].a_type) {
310 std.elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
311 std.elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
312 else => continue,
313 }
314 }
315 expandStackSize(at_phdr, at_phnum);
316 }
317 }330 }
318331
319 std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));332 std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
320}333}
321334
322fn expandStackSize(at_phdr: usize, at_phnum: usize) void {335fn expandStackSize(phdrs: []elf.Phdr) void {
323 const phdrs = (@intToPtr([*]std.elf.Phdr, at_phdr))[0..at_phnum];
324 for (phdrs) |*phdr| {336 for (phdrs) |*phdr| {
325 switch (phdr.p_type) {337 switch (phdr.p_type) {
326 std.elf.PT_GNU_STACK => {338 elf.PT_GNU_STACK => {
327 const wanted_stack_size = phdr.p_memsz;339 const wanted_stack_size = phdr.p_memsz;
328 assert(wanted_stack_size % std.mem.page_size == 0);340 assert(wanted_stack_size % std.mem.page_size == 0);
329341
...@@ -362,9 +374,10 @@ fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C)...@@ -362,9 +374,10 @@ fn main(c_argc: i32, c_argv: [*][*:0]u8, c_envp: [*:null]?[*:0]u8) callconv(.C)
362 const envp = @ptrCast([*][*:0]u8, c_envp)[0..env_count];374 const envp = @ptrCast([*][*:0]u8, c_envp)[0..env_count];
363375
364 if (builtin.os.tag == .linux) {376 if (builtin.os.tag == .linux) {
365 const at_phdr = std.c.getauxval(std.elf.AT_PHDR);377 const at_phdr = std.c.getauxval(elf.AT_PHDR);
366 const at_phnum = std.c.getauxval(std.elf.AT_PHNUM);378 const at_phnum = std.c.getauxval(elf.AT_PHNUM);
367 expandStackSize(at_phdr, at_phnum);379 const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum];
380 expandStackSize(phdrs);
368 }381 }
369382
370 return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });383 return @call(.{ .modifier = .always_inline }, callMainWithArgs, .{ @intCast(usize, c_argc), c_argv, envp });
test/stage2/darwin.zig+1-1
...@@ -14,7 +14,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -14,7 +14,7 @@ pub fn addCases(ctx: *TestContext) !void {
14 {14 {
15 var case = ctx.exe("hello world with updates", target);15 var case = ctx.exe("hello world with updates", target);
16 case.addError("", &[_][]const u8{16 case.addError("", &[_][]const u8{
17 ":84:9: error: struct 'test_case.test_case' has no member named 'main'",17 ":85:9: error: struct 'test_case.test_case' has no member named 'main'",
18 });18 });
1919
20 // Incorrect return type20 // Incorrect return type
test/stage2/test.zig+1-1
...@@ -24,7 +24,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -24,7 +24,7 @@ pub fn addCases(ctx: *TestContext) !void {
24 var case = ctx.exe("hello world with updates", linux_x64);24 var case = ctx.exe("hello world with updates", linux_x64);
2525
26 case.addError("", &[_][]const u8{26 case.addError("", &[_][]const u8{
27 ":84:9: error: struct 'test_case.test_case' has no member named 'main'",27 ":85:9: error: struct 'test_case.test_case' has no member named 'main'",
28 });28 });
2929
30 // Incorrect return type30 // Incorrect return type
test/standalone.zig+5
...@@ -31,4 +31,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -31,4 +31,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
31 cases.addBuildFile("test/stage1/c_abi/build.zig", .{});31 cases.addBuildFile("test/stage1/c_abi/build.zig", .{});
32 }32 }
33 cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ .build_modes = true, .cross_targets = true });33 cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ .build_modes = true, .cross_targets = true });
34
35 // Try to build and run a PIE executable.
36 if (std.Target.current.os.tag == .linux) {
37 cases.addBuildFile("test/standalone/pie/build.zig", .{});
38 }
34}39}
test/standalone/pie/build.zig created+12
...@@ -0,0 +1,12 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const main = b.addTest("main.zig");
5 main.setBuildMode(b.standardReleaseOptions());
6 main.pie = true;
7
8 const test_step = b.step("test", "Test the program");
9 test_step.dependOn(&main.step);
10
11 b.default_step.dependOn(test_step);
12}
test/standalone/pie/main.zig created+15
...@@ -0,0 +1,15 @@
1const std = @import("std");
2const elf = std.elf;
3
4threadlocal var foo: u8 = 42;
5
6test "Check ELF header" {
7 // PIE executables are marked as ET_DYN, regular exes as ET_EXEC.
8 const header = @intToPtr(*elf.Ehdr, std.process.getBaseAddress());
9 try std.testing.expectEqual(elf.ET.DYN, header.e_type);
10}
11
12test "TLS is initialized" {
13 // Ensure the TLS is initialized by the startup code.
14 try std.testing.expectEqual(@as(u8, 42), foo);
15}