From 333eec557f8ab89d74e9d66e55eea037ba0433cb Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 17 Dec 2019 15:07:19 +0100 Subject: [PATCH 1/2] Initial support for static PIE executables --- lib/std/dynamic_library.zig | 54 ++++++------- lib/std/elf.zig | 38 +++++++++ lib/std/os/bits/linux.zig | 2 +- lib/std/os/linux/start_pie.zig | 138 +++++++++++++++++++++++++++++++++ lib/std/os/test.zig | 2 + lib/std/start.zig | 6 ++ src/all_types.hpp | 8 ++ src/codegen.cpp | 55 ++++++++++--- src/link.cpp | 19 +++-- src/main.cpp | 7 ++ src/zig_llvm.cpp | 8 ++ src/zig_llvm.h | 2 + 12 files changed, 296 insertions(+), 43 deletions(-) create mode 100644 lib/std/os/linux/start_pie.zig diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 2132f03bd0cf..e5a345d42b6d 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -24,7 +24,7 @@ pub const DynLib = switch (builtin.os) { // fashion. const LinkMap = extern struct { l_addr: usize, - l_name: [*]const u8, + l_name: [*:0]const u8, l_ld: ?*elf.Dyn, l_next: ?*LinkMap, l_prev: ?*LinkMap, @@ -53,48 +53,48 @@ const RDebug = extern struct { r_ldbase: usize, }; -fn elf_get_va_offset(phdrs: []elf.Phdr) !usize { - for (phdrs) |*phdr| { - if (phdr.p_type == elf.PT_LOAD) { - return @ptrToInt(phdr) - phdr.p_vaddr; - } +// XXX: This should be weak (#1917) +extern var _DYNAMIC: [128]elf.Dyn; + +comptime { + if (builtin.os == .linux) { + asm ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + ); } - return error.InvalidExe; } pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { - const va_offset = try elf_get_va_offset(phdrs); - - const dyn_table = init: { - for (phdrs) |*phdr| { - if (phdr.p_type == elf.PT_DYNAMIC) { - const ptr = @intToPtr([*]elf.Dyn, va_offset + phdr.p_vaddr); - break :init ptr[0 .. phdr.p_memsz / @sizeOf(elf.Dyn)]; - } - } + if (@ptrToInt(&_DYNAMIC[0]) == 0) { // No PT_DYNAMIC means this is either a statically-linked program or a // badly corrupted one return LinkMap.Iterator{ .current = null }; - }; + } const link_map_ptr = init: { - for (dyn_table) |*dyn| { - switch (dyn.d_tag) { + var i: usize = 0; + while (_DYNAMIC[i].d_tag != elf.DT_NULL) : (i += 1) { + switch (_DYNAMIC[i].d_tag) { elf.DT_DEBUG => { - const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr); - if (r_debug.r_version != 1) return error.InvalidExe; - break :init r_debug.r_map; + const ptr = @intToPtr(?*RDebug, _DYNAMIC[i].d_un.d_ptr); + if (ptr) |r_debug| { + if (r_debug.r_version != 1) return error.InvalidExe; + break :init r_debug.r_map; + } }, elf.DT_PLTGOT => { - const got_table = @intToPtr([*]usize, dyn.d_un.d_ptr); - // The address to the link_map structure is stored in the - // second slot - break :init @intToPtr(?*LinkMap, got_table[1]); + const ptr = @intToPtr(?[*]usize, _DYNAMIC[i].d_un.d_ptr); + if (ptr) |got_table| { + // The address to the link_map structure is stored in + // the second slot + break :init @intToPtr(?*LinkMap, got_table[1]); + } }, else => {}, } } - return error.InvalidExe; + return LinkMap.Iterator{ .current = null }; }; return LinkMap.Iterator{ .current = link_map_ptr }; diff --git a/lib/std/elf.zig b/lib/std/elf.zig index dc1638c5cb30..5f1a0672f7b8 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -640,20 +640,48 @@ pub const Elf64_Syminfo = extern struct { pub const Elf32_Rel = extern struct { r_offset: Elf32_Addr, r_info: Elf32_Word, + + pub inline fn r_sym(self: @This()) u24 { + return @truncate(u24, self.r_info >> 8); + } + pub inline fn r_type(self: @This()) u8 { + return @truncate(u8, self.r_info & 0xff); + } }; pub const Elf64_Rel = extern struct { r_offset: Elf64_Addr, r_info: Elf64_Xword, + + pub inline fn r_sym(self: @This()) u32 { + return @truncate(u32, self.r_info >> 32); + } + pub inline fn r_type(self: @This()) u32 { + return @truncate(u32, self.r_info & 0xffffffff); + } }; pub const Elf32_Rela = extern struct { r_offset: Elf32_Addr, r_info: Elf32_Word, r_addend: Elf32_Sword, + + pub inline fn r_sym(self: @This()) u24 { + return @truncate(u24, self.r_info >> 8); + } + pub inline fn r_type(self: @This()) u8 { + return @truncate(u8, self.r_info & 0xff); + } }; pub const Elf64_Rela = extern struct { r_offset: Elf64_Addr, r_info: Elf64_Xword, r_addend: Elf64_Sxword, + + pub inline fn r_sym(self: @This()) u32 { + return @truncate(u32, self.r_info >> 32); + } + pub inline fn r_type(self: @This()) u32 { + return @truncate(u32, self.r_info & 0xffffffff); + } }; pub const Elf32_Phdr = extern struct { p_type: Elf32_Word, @@ -853,6 +881,16 @@ pub const Dyn = switch (@sizeOf(usize)) { 8 => Elf64_Dyn, else => @compileError("expected pointer size of 32 or 64"), }; +pub const Rel = switch (@sizeOf(usize)) { + 4 => Elf32_Rel, + 8 => Elf64_Rel, + else => @compileError("expected pointer size of 32 or 64"), +}; +pub const Rela = switch (@sizeOf(usize)) { + 4 => Elf32_Rela, + 8 => Elf64_Rela, + else => @compileError("expected pointer size of 32 or 64"), +}; pub const Shdr = switch (@sizeOf(usize)) { 4 => Elf32_Shdr, 8 => Elf64_Shdr, diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index a6d010e26d3a..8650b701b8fa 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -996,7 +996,7 @@ pub const dirent64 = extern struct { pub const dl_phdr_info = extern struct { dlpi_addr: usize, - dlpi_name: ?[*]const u8, + dlpi_name: ?[*:0]const u8, dlpi_phdr: [*]std.elf.Phdr, dlpi_phnum: u16, }; diff --git a/lib/std/os/linux/start_pie.zig b/lib/std/os/linux/start_pie.zig new file mode 100644 index 000000000000..d0ef79e1fba3 --- /dev/null +++ b/lib/std/os/linux/start_pie.zig @@ -0,0 +1,138 @@ +const std = @import("std"); +const elf = std.elf; +const builtin = @import("builtin"); +const assert = std.debug.assert; + +const R_AMD64_RELATIVE = 8; +const R_386_RELATIVE = 8; +const R_ARM_RELATIVE = 23; +const R_AARCH64_RELATIVE = 1027; +const R_RISCV_RELATIVE = 3; + +const ARCH_RELATIVE_RELOC = switch (builtin.arch) { + .i386 => R_386_RELATIVE, + .x86_64 => R_AMD64_RELATIVE, + .arm => R_ARM_RELATIVE, + .aarch64 => R_AARCH64_RELATIVE, + .riscv64 => R_RISCV_RELATIVE, + else => @compileError("unsupported architecture"), +}; + +// Just a convoluted (but necessary) way to obtain the address of the _DYNAMIC[] +// vector as PC-relative so that we can use it before any relocation is applied +fn getDynamicSymbol() [*]elf.Dyn { + const addr = switch (builtin.arch) { + .i386 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ call 1f + \\ 1: pop %[ret] + \\ lea _DYNAMIC-1b(%[ret]), %[ret] + : [ret] "=r" (-> usize) + ), + .x86_64 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ lea _DYNAMIC(%%rip), %[ret] + : [ret] "=r" (-> usize) + ), + // Work around the limited offset range of `ldr` + .arm => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ ldr %[ret], 1f + \\ add %[ret], pc + \\ b 2f + \\ 1: .word _DYNAMIC-1b + \\ 2: + : [ret] "=r" (-> usize) + ), + // A simple `adr` is not enough as it has a limited offset range + .aarch64 => asm volatile ( + \\ .weak _DYNAMIC + \\ .hidden _DYNAMIC + \\ adrp %[ret], _DYNAMIC + \\ add %[ret], %[ret], #:lo12:_DYNAMIC + : [ret] "=r" (-> usize) + ), + .riscv64 => asm volatile ( + \\ lla %[ret], _DYNAMIC + : [ret] "=r" (-> usize) + ), + else => @compileError("???"), + }; + if (addr == 0) unreachable; + return @intToPtr([*]elf.Dyn, addr); +} + +pub fn apply_relocations() void { + @setRuntimeSafety(false); + + const dynv = getDynamicSymbol(); + const auxv = std.os.linux.elf_aux_maybe.?; + var at_phent: usize = undefined; + var at_phnum: usize = undefined; + var at_phdr: usize = undefined; + var at_hwcap: usize = undefined; + + { + var i: usize = 0; + while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { + switch (auxv[i].a_type) { + elf.AT_PHENT => at_phent = auxv[i].a_un.a_val, + elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val, + elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val, + else => continue, + } + } + } + + // Sanity check + assert(at_phent == @sizeOf(elf.Phdr)); + + // Search the TLS section + const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum]; + + const base_addr = blk: { + for (phdrs) |*phdr| { + if (phdr.p_type == elf.PT_DYNAMIC) { + break :blk @ptrToInt(&dynv[0]) - phdr.p_vaddr; + } + } + unreachable; + }; + + var rel_addr: usize = 0; + var rela_addr: usize = 0; + var rel_size: usize = 0; + var rela_size: usize = 0; + + { + var i: usize = 0; + while (dynv[i].d_tag != elf.DT_NULL) : (i += 1) { + switch (dynv[i].d_tag) { + elf.DT_REL => rel_addr = base_addr + dynv[i].d_un.d_ptr, + elf.DT_RELA => rela_addr = base_addr + dynv[i].d_un.d_ptr, + elf.DT_RELSZ => rel_size = dynv[i].d_un.d_val, + elf.DT_RELASZ => rela_size = dynv[i].d_un.d_val, + else => {}, + } + } + } + + // Perform the relocations + if (rel_addr != 0) { + const rel = @bytesToSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]); + for (rel) |r| { + if (r.r_type() != ARCH_RELATIVE_RELOC) continue; + @intToPtr(*usize, base_addr + r.r_offset).* += base_addr; + } + } + if (rela_addr != 0) { + const rela = @bytesToSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]); + for (rela) |r| { + if (r.r_type() != ARCH_RELATIVE_RELOC) continue; + @intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend); + } + } +} diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index f61e8b4657d9..7e9c5e0bc217 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -205,6 +205,8 @@ export fn iter_fn(info: *dl_phdr_info, size: usize, data: ?*usize) i32 { test "dl_iterate_phdr" { if (builtin.os == .windows or builtin.os == .wasi or builtin.os == .macosx) return error.SkipZigTest; + if (builtin.position_independent_executable) + return error.SkipZigTest; var counter: usize = 0; expect(os.dl_iterate_phdr(usize, iter_fn, &counter) != 0); diff --git a/lib/std/start.zig b/lib/std/start.zig index 3c46449949d3..e7994e940462 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -155,6 +155,12 @@ fn posixCallMainAndExit() noreturn { // Find the beginning of the auxiliary vector const auxv = @ptrCast([*]std.elf.Auxv, envp.ptr + envp_count + 1); std.os.linux.elf_aux_maybe = auxv; + + // Do this as early as possible, the aux vector is needed + if (builtin.position_independent_executable) { + @import("os/linux/start_pie.zig").apply_relocations(); + } + // Initialize the TLS area const gnu_stack_phdr = std.os.linux.tls.initTLS() orelse @panic("ELF missing stack size"); diff --git a/src/all_types.hpp b/src/all_types.hpp index bcef45f71844..3ad2021ced0f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1922,6 +1922,12 @@ enum WantPIC { WantPICEnabled, }; +enum WantPIE { + WantPIEAuto, + WantPIEDisabled, + WantPIEEnabled, +}; + enum WantStackCheck { WantStackCheckAuto, WantStackCheckDisabled, @@ -2101,6 +2107,7 @@ struct CodeGen { Stage2ProgressNode *sub_progress_node; WantPIC want_pic; + WantPIE want_pie; WantStackCheck want_stack_check; WantCSanitize want_sanitize_c; CacheHash cache_hash; @@ -2175,6 +2182,7 @@ struct CodeGen { bool disable_gen_h; bool bundle_compiler_rt; bool have_pic; + bool have_pie; bool have_dynamic_link; // this is whether the final thing will be dynamically linked. see also is_dynamic bool have_stack_probing; bool have_sanitize_c; diff --git a/src/codegen.cpp b/src/codegen.cpp index edc2c7f4351f..bc2a6af52f8a 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8233,18 +8233,39 @@ static bool detect_dynamic_link(CodeGen *g) { return false; } -static bool detect_pic(CodeGen *g) { - if (target_requires_pic(g->zig_target, g->libc_link_lib != nullptr)) - return true; +static void detect_pic_pie(CodeGen *g) { + const bool requires_pic = target_requires_pic(g->zig_target, g->libc_link_lib != nullptr); + const bool requires_pie = target_requires_pie(g->zig_target); + + bool have_pic = false; + bool have_pie = false; + + switch (g->want_pie) { + case WantPIEDisabled: + have_pie = false; + break; + case WantPIEEnabled: + have_pie = true; + break; + case WantPIEAuto: + have_pie = requires_pie; + break; + } + switch (g->want_pic) { case WantPICDisabled: - return false; + have_pic = false; + break; case WantPICEnabled: - return true; + have_pic = true; + break; case WantPICAuto: - return g->have_dynamic_link; + have_pic = have_pie || g->have_dynamic_link || requires_pic; + break; } - zig_unreachable(); + + g->have_pic = have_pic; + g->have_pie = have_pie; } static bool detect_stack_probing(CodeGen *g) { @@ -8309,7 +8330,7 @@ static bool detect_err_ret_tracing(CodeGen *g) { Buf *codegen_generate_builtin_source(CodeGen *g) { g->have_dynamic_link = detect_dynamic_link(g); - g->have_pic = detect_pic(g); + detect_pic_pie(g); g->have_stack_probing = detect_stack_probing(g); g->have_sanitize_c = detect_sanitize_c(g); g->is_single_threaded = detect_single_threaded(g); @@ -8465,6 +8486,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing)); buf_appendf(contents, "pub const valgrind_support = %s;\n", bool_to_str(want_valgrind_support(g))); buf_appendf(contents, "pub const position_independent_code = %s;\n", bool_to_str(g->have_pic)); + buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie)); buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols)); { @@ -8526,6 +8548,8 @@ static Error define_builtin_compile_vars(CodeGen *g) { cache_bool(&cache_hash, g->libc_link_lib != nullptr); cache_bool(&cache_hash, g->valgrind_support); cache_int(&cache_hash, detect_subsystem(g)); + cache_bool(&cache_hash, g->have_pic); + cache_bool(&cache_hash, g->have_pie); Buf digest = BUF_INIT; buf_resize(&digest, 0); @@ -8595,7 +8619,7 @@ static void init(CodeGen *g) { return; g->have_dynamic_link = detect_dynamic_link(g); - g->have_pic = detect_pic(g); + detect_pic_pie(g); g->have_stack_probing = detect_stack_probing(g); g->have_sanitize_c = detect_sanitize_c(g); g->is_single_threaded = detect_single_threaded(g); @@ -8640,6 +8664,14 @@ static void init(CodeGen *g) { reloc_mode = LLVMRelocStatic; } + if (g->have_pic) { + ZigLLVMSetModulePICLevel(g->module); + } + + if (g->have_pie) { + ZigLLVMSetModulePIELevel(g->module); + } + const char *target_specific_cpu_args; const char *target_specific_features; if (g->zig_target->is_native) { @@ -9327,6 +9359,7 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose cache_bool(cache_hash, g->strip_debug_symbols); cache_int(cache_hash, g->build_mode); cache_bool(cache_hash, g->have_pic); + cache_bool(cache_hash, g->have_pie); cache_bool(cache_hash, g->have_sanitize_c); cache_bool(cache_hash, want_valgrind_support(g)); cache_bool(cache_hash, g->function_sections); @@ -10104,6 +10137,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { cache_bool(ch, g->bundle_compiler_rt); cache_bool(ch, want_valgrind_support(g)); cache_bool(ch, g->have_pic); + cache_bool(ch, g->have_pie); cache_bool(ch, g->have_dynamic_link); cache_bool(ch, g->have_stack_probing); cache_bool(ch, g->have_sanitize_c); @@ -10224,7 +10258,7 @@ void codegen_build_and_link(CodeGen *g) { } g->have_dynamic_link = detect_dynamic_link(g); - g->have_pic = detect_pic(g); + detect_pic_pie(g); g->is_single_threaded = detect_single_threaded(g); g->have_err_ret_tracing = detect_err_ret_tracing(g); g->have_sanitize_c = detect_sanitize_c(g); @@ -10429,6 +10463,7 @@ CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType o codegen_set_strip(child_gen, parent_gen->strip_debug_symbols); child_gen->want_pic = parent_gen->have_pic ? WantPICEnabled : WantPICDisabled; + child_gen->want_pie = parent_gen->have_pie ? WantPIEEnabled : WantPIEDisabled; child_gen->valgrind_support = ValgrindSupportDisabled; codegen_set_errmsg_color(child_gen, parent_gen->err_color); diff --git a/src/link.cpp b/src/link.cpp index 530a0c27c45d..563a174a0bbe 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -1469,6 +1469,14 @@ static const char *get_libc_crt_file(CodeGen *parent, const char *file, Stage2Pr c_file->args.append("-fno-stack-protector"); c_file->args.append("-DCRT"); return build_libc_object(parent, "crt1", c_file, progress_node); + } else if (strcmp(file, "rcrt1.o") == 0) { + CFile *c_file = allocate(1); + c_file->source_path = path_from_libc(parent, "musl" OS_SEP "crt" OS_SEP "rcrt1.c"); + musl_add_cc_args(parent, c_file, false); + c_file->args.append("-fno-stack-protector"); + c_file->args.append("-DCRT"); + c_file->args.append("-fPIC"); + return build_libc_object(parent, "rcrt1", c_file, progress_node); } else if (strcmp(file, "Scrt1.o") == 0) { CFile *c_file = allocate(1); c_file->source_path = path_from_libc(parent, "musl" OS_SEP "crt" OS_SEP "Scrt1.c"); @@ -1661,6 +1669,11 @@ static void construct_linker_job_elf(LinkJob *lj) { } else { lj->args.append("-static"); } + + if (g->out_type == OutTypeExe && g->have_pie) { + lj->args.append("-pie"); + lj->args.append("--no-dynamic-linker"); + } } else if (is_dyn_lib) { lj->args.append("-shared"); @@ -1668,10 +1681,6 @@ static void construct_linker_job_elf(LinkJob *lj) { soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize, buf_ptr(g->root_out_name), g->version_major); } - if (target_requires_pie(g->zig_target) && g->out_type == OutTypeExe) { - lj->args.append("-pie"); - } - lj->args.append("-o"); lj->args.append(buf_ptr(&g->output_file_path)); @@ -1686,7 +1695,7 @@ static void construct_linker_job_elf(LinkJob *lj) { crt1o = "crtbegin_static.o"; } } else if (!g->have_dynamic_link) { - crt1o = "crt1.o"; + crt1o = g->have_pie ? "rcrt1.o" : "crt1.o"; } else { crt1o = "Scrt1.o"; } diff --git a/src/main.cpp b/src/main.cpp index 2cda18f7a0d9..3262a4b7478d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -525,6 +525,7 @@ int main(int argc, char **argv) { Buf *main_pkg_path = nullptr; ValgrindSupport valgrind_support = ValgrindSupportAuto; WantPIC want_pic = WantPICAuto; + WantPIE want_pie = WantPIEAuto; WantStackCheck want_stack_check = WantStackCheckAuto; WantCSanitize want_sanitize_c = WantCSanitizeAuto; bool function_sections = false; @@ -721,6 +722,10 @@ int main(int argc, char **argv) { want_pic = WantPICEnabled; } else if (strcmp(arg, "-fno-PIC") == 0) { want_pic = WantPICDisabled; + } else if (strcmp(arg, "-fPIE") == 0) { + want_pie = WantPIEEnabled; + } else if (strcmp(arg, "-fno-PIE") == 0) { + want_pie = WantPIEDisabled; } else if (strcmp(arg, "-fstack-check") == 0) { want_stack_check = WantStackCheckEnabled; } else if (strcmp(arg, "-fno-stack-check") == 0) { @@ -1099,6 +1104,7 @@ int main(int argc, char **argv) { g->subsystem = subsystem; g->valgrind_support = valgrind_support; g->want_pic = want_pic; + g->want_pie = want_pie; g->want_stack_check = want_stack_check; g->want_sanitize_c = want_sanitize_c; g->want_single_threaded = want_single_threaded; @@ -1199,6 +1205,7 @@ int main(int argc, char **argv) { if (llvm_argv.length >= 2) codegen_set_llvm_argv(g, llvm_argv.items + 1, llvm_argv.length - 2); g->valgrind_support = valgrind_support; g->want_pic = want_pic; + g->want_pie = want_pie; g->want_stack_check = want_stack_check; g->want_sanitize_c = want_sanitize_c; g->subsystem = subsystem; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 852475c3c4b2..a0d1c3e08e50 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -877,6 +877,14 @@ void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module) { unwrap(module)->addModuleFlag(Module::Warning, "CodeView", 1); } +void ZigLLVMSetModulePICLevel(LLVMModuleRef module) { + unwrap(module)->setPICLevel(PICLevel::Level::BigPIC); +} + +void ZigLLVMSetModulePIELevel(LLVMModuleRef module) { + unwrap(module)->setPIELevel(PIELevel::Level::Large); +} + static AtomicOrdering mapFromLLVMOrdering(LLVMAtomicOrdering Ordering) { switch (Ordering) { case LLVMAtomicOrderingNotAtomic: return AtomicOrdering::NotAtomic; diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 0fad21a19bf6..1627c5de40e1 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -158,6 +158,8 @@ ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef modul ZIG_EXTERN_C void ZigLLVMDisposeDIBuilder(struct ZigLLVMDIBuilder *dbuilder); ZIG_EXTERN_C void ZigLLVMAddModuleDebugInfoFlag(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module); +ZIG_EXTERN_C void ZigLLVMSetModulePICLevel(LLVMModuleRef module); +ZIG_EXTERN_C void ZigLLVMSetModulePIELevel(LLVMModuleRef module); ZIG_EXTERN_C void ZigLLVMSetCurrentDebugLocation(LLVMBuilderRef builder, int line, int column, struct ZigLLVMDIScope *scope); From abc717f203060f7ab16d36f2afe681d838b46801 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 22 Nov 2020 17:28:11 -0700 Subject: [PATCH 2/2] modernize the PIE patch for the latest master branch This is the part of #3960 that has to be rewritten to apply to latest master branch code. --- lib/libc/musl/ldso/dlstart.c | 148 +++++++++++++++++++++++++++++++++ lib/std/build.zig | 11 +++ lib/std/dynamic_library.zig | 2 +- lib/std/os/linux/start_pie.zig | 12 +-- src/Compilation.zig | 33 +++++++- src/clang_options_data.zig | 18 +++- src/libcxx.zig | 2 + src/libunwind.zig | 1 + src/link.zig | 1 + src/link/Elf.zig | 8 +- src/main.zig | 12 +++ src/musl.zig | 18 ++++ src/stage1.zig | 1 + src/stage1/all_types.hpp | 1 + src/stage1/codegen.cpp | 9 ++ src/stage1/stage1.cpp | 1 + src/stage1/stage1.h | 1 + tools/update_clang_options.zig | 8 ++ 18 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 lib/libc/musl/ldso/dlstart.c diff --git a/lib/libc/musl/ldso/dlstart.c b/lib/libc/musl/ldso/dlstart.c new file mode 100644 index 000000000000..20d50f2cbc8d --- /dev/null +++ b/lib/libc/musl/ldso/dlstart.c @@ -0,0 +1,148 @@ +#include +#include "dynlink.h" +#include "libc.h" + +#ifndef START +#define START "_dlstart" +#endif + +#define SHARED + +#include "crt_arch.h" + +#ifndef GETFUNCSYM +#define GETFUNCSYM(fp, sym, got) do { \ + hidden void sym(); \ + static void (*static_func_ptr)() = sym; \ + __asm__ __volatile__ ( "" : "+m"(static_func_ptr) : : "memory"); \ + *(fp) = static_func_ptr; } while(0) +#endif + +hidden void _dlstart_c(size_t *sp, size_t *dynv) +{ + size_t i, aux[AUX_CNT], dyn[DYN_CNT]; + size_t *rel, rel_size, base; + + int argc = *sp; + char **argv = (void *)(sp+1); + + for (i=argc+1; argv[i]; i++); + size_t *auxv = (void *)(argv+i+1); + + for (i=0; isegs; + } else { + /* If dynv is null, the entry point was started from loader + * that is not fdpic-aware. We can assume normal fixed- + * displacement ELF loading was performed, but when ldso was + * run as a command, finding the Ehdr is a heursitic: we + * have to assume Phdrs start in the first 4k of the file. */ + base = aux[AT_BASE]; + if (!base) base = aux[AT_PHDR] & -4096; + segs = &fakeseg; + segs[0].addr = base; + segs[0].p_vaddr = 0; + segs[0].p_memsz = -1; + Ehdr *eh = (void *)base; + Phdr *ph = (void *)(base + eh->e_phoff); + size_t phnum = eh->e_phnum; + size_t phent = eh->e_phentsize; + while (phnum-- && ph->p_type != PT_DYNAMIC) + ph = (void *)((size_t)ph + phent); + dynv = (void *)(base + ph->p_vaddr); + } +#endif + + for (i=0; i= segs[j].p_memsz; j++); + dyn[i] += segs[j].addr - segs[j].p_vaddr; + } + base = 0; + + const Sym *syms = (void *)dyn[DT_SYMTAB]; + + rel = (void *)dyn[DT_RELA]; + rel_size = dyn[DT_RELASZ]; + for (; rel_size; rel+=3, rel_size-=3*sizeof(size_t)) { + if (!IS_RELATIVE(rel[1], syms)) continue; + for (j=0; rel[0]-segs[j].p_vaddr >= segs[j].p_memsz; j++); + size_t *rel_addr = (void *) + (rel[0] + segs[j].addr - segs[j].p_vaddr); + if (R_TYPE(rel[1]) == REL_FUNCDESC_VAL) { + *rel_addr += segs[rel_addr[1]].addr + - segs[rel_addr[1]].p_vaddr + + syms[R_SYM(rel[1])].st_value; + rel_addr[1] = dyn[DT_PLTGOT]; + } else { + size_t val = syms[R_SYM(rel[1])].st_value; + for (j=0; val-segs[j].p_vaddr >= segs[j].p_memsz; j++); + *rel_addr = rel[2] + segs[j].addr - segs[j].p_vaddr + val; + } + } +#else + /* If the dynamic linker is invoked as a command, its load + * address is not available in the aux vector. Instead, compute + * the load address as the difference between &_DYNAMIC and the + * virtual address in the PT_DYNAMIC program header. */ + base = aux[AT_BASE]; + if (!base) { + size_t phnum = aux[AT_PHNUM]; + size_t phentsize = aux[AT_PHENT]; + Phdr *ph = (void *)aux[AT_PHDR]; + for (i=phnum; i--; ph = (void *)((char *)ph + phentsize)) { + if (ph->p_type == PT_DYNAMIC) { + base = (size_t)dynv - ph->p_vaddr; + break; + } + } + } + + /* MIPS uses an ugly packed form for GOT relocations. Since we + * can't make function calls yet and the code is tiny anyway, + * it's simply inlined here. */ + if (NEED_MIPS_GOT_RELOCS) { + size_t local_cnt = 0; + size_t *got = (void *)(base + dyn[DT_PLTGOT]); + for (i=0; dynv[i]; i+=2) if (dynv[i]==DT_MIPS_LOCAL_GOTNO) + local_cnt = dynv[i+1]; + for (i=0; i rel_addr = base_addr + dynv[i].d_un.d_ptr, - elf.DT_RELA => rela_addr = base_addr + dynv[i].d_un.d_ptr, - elf.DT_RELSZ => rel_size = dynv[i].d_un.d_val, - elf.DT_RELASZ => rela_size = dynv[i].d_un.d_val, + elf.DT_REL => rel_addr = base_addr + dynv[i].d_val, + elf.DT_RELA => rela_addr = base_addr + dynv[i].d_val, + elf.DT_RELSZ => rel_size = dynv[i].d_val, + elf.DT_RELASZ => rela_size = dynv[i].d_val, else => {}, } } @@ -122,14 +122,14 @@ pub fn apply_relocations() void { // Perform the relocations if (rel_addr != 0) { - const rel = @bytesToSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]); + const rel = std.mem.bytesAsSlice(elf.Rel, @intToPtr([*]u8, rel_addr)[0..rel_size]); for (rel) |r| { if (r.r_type() != ARCH_RELATIVE_RELOC) continue; @intToPtr(*usize, base_addr + r.r_offset).* += base_addr; } } if (rela_addr != 0) { - const rela = @bytesToSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]); + const rela = std.mem.bytesAsSlice(elf.Rela, @intToPtr([*]u8, rela_addr)[0..rela_size]); for (rela) |r| { if (r.r_type() != ARCH_RELATIVE_RELOC) continue; @intToPtr(*usize, base_addr + r.r_offset).* += base_addr + @bitCast(usize, r.r_addend); diff --git a/src/Compilation.zig b/src/Compilation.zig index b3cb9e31f0d5..b813182e30d0 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -343,6 +343,10 @@ pub const InitOptions = struct { link_libc: bool = false, link_libcpp: bool = false, want_pic: ?bool = null, + /// This means that if the output mode is an executable it will be a + /// Position Independent Executable. If the output mode is not an + /// executable this field is ignored. + want_pie: ?bool = null, want_sanitize_c: ?bool = null, want_stack_check: ?bool = null, want_valgrind: ?bool = null, @@ -527,17 +531,30 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { options.libc_installation, ); + const must_pie = target_util.requiresPIE(options.target); + const pie = if (options.want_pie) |explicit| pie: { + if (!explicit and must_pie) { + return error.TargetRequiresPIE; + } + break :pie explicit; + } else must_pie; + const must_pic: bool = b: { if (target_util.requiresPIC(options.target, link_libc)) break :b true; break :b link_mode == .Dynamic; }; const pic = if (options.want_pic) |explicit| pic: { - if (!explicit and must_pic) { - return error.TargetRequiresPIC; + if (!explicit) { + if (must_pic) { + return error.TargetRequiresPIC; + } + if (pie) { + return error.PIERequiresPIC; + } } break :pic explicit; - } else must_pic; + } else pie or must_pic; // Make a decision on whether to use Clang for translate-c and compiling C files. const use_clang = if (options.use_clang) |explicit| explicit else blk: { @@ -618,6 +635,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(options.target.abi); cache.hash.add(ofmt); cache.hash.add(pic); + cache.hash.add(pie); cache.hash.add(stack_check); cache.hash.add(link_mode); cache.hash.add(options.function_sections); @@ -814,6 +832,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .version = options.version, .libc_installation = libc_dirs.libc_installation, .pic = pic, + .pie = pie, .valgrind = valgrind, .stack_check = stack_check, .single_threaded = single_threaded, @@ -898,7 +917,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { try comp.addBuildingGLibCJobs(); } if (comp.wantBuildMuslFromSource()) { - try comp.work_queue.ensureUnusedCapacity(5); + try comp.work_queue.ensureUnusedCapacity(6); if (target_util.libc_needs_crti_crtn(comp.getTarget())) { comp.work_queue.writeAssumeCapacity(&[_]Job{ .{ .musl_crt_file = .crti_o }, @@ -908,6 +927,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { comp.work_queue.writeAssumeCapacity(&[_]Job{ .{ .musl_crt_file = .crt1_o }, .{ .musl_crt_file = .scrt1_o }, + .{ .musl_crt_file = .rcrt1_o }, .{ .musl_crt_file = .libc_a }, }); } @@ -2473,6 +2493,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const have_error_return_tracing = {}; \\pub const valgrind_support = {}; \\pub const position_independent_code = {}; + \\pub const position_independent_executable = {}; \\pub const strip_debug_info = {}; \\pub const code_model = CodeModel.{}; \\ @@ -2484,6 +2505,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 comp.bin_file.options.error_return_tracing, comp.bin_file.options.valgrind, comp.bin_file.options.pic, + comp.bin_file.options.pie, comp.bin_file.options.strip, @tagName(comp.bin_file.options.machine_code_model), }); @@ -2587,6 +2609,7 @@ fn buildStaticLibFromZig(comp: *Compilation, src_basename: []const u8, out: *?CR .want_stack_check = false, .want_valgrind = false, .want_pic = comp.bin_file.options.pic, + .want_pie = comp.bin_file.options.pie, .emit_h = null, .strip = comp.bin_file.options.strip, .is_native_os = comp.bin_file.options.is_native_os, @@ -2795,6 +2818,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .subsystem = subsystem, .err_color = @enumToInt(comp.color), .pic = comp.bin_file.options.pic, + .pie = comp.bin_file.options.pie, .link_libc = comp.bin_file.options.link_libc, .link_libcpp = comp.bin_file.options.link_libcpp, .strip = comp.bin_file.options.strip, @@ -2950,6 +2974,7 @@ pub fn build_crt_file( .want_stack_check = false, .want_valgrind = false, .want_pic = comp.bin_file.options.pic, + .want_pie = comp.bin_file.options.pie, .emit_h = null, .strip = comp.bin_file.options.strip, .is_native_os = comp.bin_file.options.is_native_os, diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index e9093ac8454f..52dc8c028041 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -2460,7 +2460,14 @@ sepd1("exported_symbols_list"), .pd2 = false, .psl = false, }, -flagpd1("fPIE"), +.{ + .name = "fPIE", + .syntax = .flag, + .zig_equivalent = .pie, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fno-access-control"), flagpd1("faddrsig"), flagpd1("faggressive-function-elimination"), @@ -2775,7 +2782,14 @@ flagpd1("fnext-runtime"), .pd2 = false, .psl = false, }, -flagpd1("fno-PIE"), +.{ + .name = "fno-PIE", + .syntax = .flag, + .zig_equivalent = .no_pie, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fno-no-access-control"), flagpd1("fno-addrsig"), flagpd1("fno-aggressive-function-elimination"), diff --git a/src/libcxx.zig b/src/libcxx.zig index b76c8f116332..9c5ec937767a 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -171,6 +171,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { .want_stack_check = false, .want_valgrind = false, .want_pic = comp.bin_file.options.pic, + .want_pie = comp.bin_file.options.pie, .emit_h = null, .strip = comp.bin_file.options.strip, .is_native_os = comp.bin_file.options.is_native_os, @@ -288,6 +289,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void { .want_stack_check = false, .want_valgrind = false, .want_pic = comp.bin_file.options.pic, + .want_pie = comp.bin_file.options.pie, .emit_h = null, .strip = comp.bin_file.options.strip, .is_native_os = comp.bin_file.options.is_native_os, diff --git a/src/libunwind.zig b/src/libunwind.zig index d47eed40dd08..3607488abae9 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -104,6 +104,7 @@ pub fn buildStaticLib(comp: *Compilation) !void { .want_stack_check = false, .want_valgrind = false, .want_pic = comp.bin_file.options.pic, + .want_pie = comp.bin_file.options.pie, .emit_h = null, .strip = comp.bin_file.options.strip, .is_native_os = comp.bin_file.options.is_native_os, diff --git a/src/link.zig b/src/link.zig index 7b0271f97819..064c178d661c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -71,6 +71,7 @@ pub const Options = struct { bind_global_refs_locally: bool, is_native_os: bool, pic: bool, + pie: bool, valgrind: bool, stack_check: bool, single_threaded: bool, diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 18388f058923..9747eced4126 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1425,7 +1425,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { try argv.append("-shared"); } - if (target_util.requiresPIE(target) and self.base.options.output_mode == .Exe) { + if (self.base.options.pie and self.base.options.output_mode == .Exe) { try argv.append("-pie"); } @@ -1444,7 +1444,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { break :o "crtbegin_static.o"; } } else if (self.base.options.link_mode == .Static) { - break :o "crt1.o"; + if (self.base.options.pie) { + break :o "rcrt1.o"; + } else { + break :o "crt1.o"; + } } else { break :o "Scrt1.o"; } diff --git a/src/main.zig b/src/main.zig index d09599e3c666..595faed88f77 100644 --- a/src/main.zig +++ b/src/main.zig @@ -270,6 +270,8 @@ const usage_build_generic = \\ --main-pkg-path Set the directory of the root package \\ -fPIC Force-enable Position Independent Code \\ -fno-PIC Force-disable Position Independent Code + \\ -fPIE Force-enable Position Independent Executable + \\ -fno-PIE Force-disable Position Independent Executable \\ -fstack-check Enable stack probing in unsafe builds \\ -fno-stack-check Disable stack probing in safe builds \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds @@ -457,6 +459,7 @@ fn buildOutputType( var want_native_include_dirs = false; var enable_cache: ?bool = null; var want_pic: ?bool = null; + var want_pie: ?bool = null; var want_sanitize_c: ?bool = null; var want_stack_check: ?bool = null; var want_valgrind: ?bool = null; @@ -795,6 +798,10 @@ fn buildOutputType( want_pic = true; } else if (mem.eql(u8, arg, "-fno-PIC")) { want_pic = false; + } else if (mem.eql(u8, arg, "-fPIE")) { + want_pie = true; + } else if (mem.eql(u8, arg, "-fno-PIE")) { + want_pie = false; } else if (mem.eql(u8, arg, "-fstack-check")) { want_stack_check = true; } else if (mem.eql(u8, arg, "-fno-stack-check")) { @@ -1006,6 +1013,8 @@ fn buildOutputType( }, .pic => want_pic = true, .no_pic => want_pic = false, + .pie => want_pie = true, + .no_pie => want_pie = false, .nostdlib => ensure_libc_on_non_freestanding = false, .nostdlib_cpp => ensure_libcpp_on_non_freestanding = false, .shared => { @@ -1640,6 +1649,7 @@ fn buildOutputType( .link_libc = link_libc, .link_libcpp = link_libcpp, .want_pic = want_pic, + .want_pie = want_pie, .want_sanitize_c = want_sanitize_c, .want_stack_check = want_stack_check, .want_valgrind = want_valgrind, @@ -2773,6 +2783,8 @@ pub const ClangArgIterator = struct { driver_punt, pic, no_pic, + pie, + no_pie, nostdlib, nostdlib_cpp, shared, diff --git a/src/musl.zig b/src/musl.zig index 75b0a67584a9..c405013afe17 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -12,6 +12,7 @@ pub const CRTFile = enum { crti_o, crtn_o, crt1_o, + rcrt1_o, scrt1_o, libc_a, }; @@ -68,6 +69,23 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }, }); }, + .rcrt1_o => { + var args = std.ArrayList([]const u8).init(arena); + try add_cc_args(comp, arena, &args, false); + try args.appendSlice(&[_][]const u8{ + "-fPIC", + "-fno-stack-protector", + "-DCRT", + }); + return comp.build_crt_file("rcrt1", .Obj, &[1]Compilation.CSourceFile{ + .{ + .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ + "libc", "musl", "crt", "rcrt1.c", + }), + .extra_flags = args.items, + }, + }); + }, .scrt1_o => { var args = std.ArrayList([]const u8).init(arena); try add_cc_args(comp, arena, &args, false); diff --git a/src/stage1.zig b/src/stage1.zig index 2dabea91aded..504b35df9469 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -108,6 +108,7 @@ pub const Module = extern struct { subsystem: TargetSubsystem, err_color: ErrColor, pic: bool, + pie: bool, link_libc: bool, link_libcpp: bool, strip: bool, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index d56bcffff119..957a377b6dcb 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2177,6 +2177,7 @@ struct CodeGen { bool is_test_build; bool is_single_threaded; bool have_pic; + bool have_pie; bool link_mode_dynamic; bool dll_export_fns; bool have_stack_probing; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 26fe00228e23..d0bb9b441159 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9043,6 +9043,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const have_error_return_tracing = %s;\n", bool_to_str(g->have_err_ret_tracing)); buf_appendf(contents, "pub const valgrind_support = false;\n"); buf_appendf(contents, "pub const position_independent_code = %s;\n", bool_to_str(g->have_pic)); + buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie)); buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols)); buf_appendf(contents, "pub const code_model = CodeModel.default;\n"); @@ -9170,6 +9171,14 @@ static void init(CodeGen *g) { reloc_mode = LLVMRelocStatic; } + if (g->have_pic) { + ZigLLVMSetModulePICLevel(g->module); + } + + if (g->have_pie) { + ZigLLVMSetModulePIELevel(g->module); + } + const char *target_specific_cpu_args = ""; const char *target_specific_features = ""; diff --git a/src/stage1/stage1.cpp b/src/stage1/stage1.cpp index 1034f9ff88f2..fec6760623be 100644 --- a/src/stage1/stage1.cpp +++ b/src/stage1/stage1.cpp @@ -89,6 +89,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) { g->link_mode_dynamic = stage1->link_mode_dynamic; g->dll_export_fns = stage1->dll_export_fns; g->have_pic = stage1->pic; + g->have_pie = stage1->pie; g->have_stack_probing = stage1->enable_stack_probing; g->is_single_threaded = stage1->is_single_threaded; g->valgrind_enabled = stage1->valgrind_enabled; diff --git a/src/stage1/stage1.h b/src/stage1/stage1.h index 412118a6fa1c..0552aa09e05c 100644 --- a/src/stage1/stage1.h +++ b/src/stage1/stage1.h @@ -177,6 +177,7 @@ struct ZigStage1 { enum ErrColor err_color; bool pic; + bool pie; bool link_libc; bool link_libcpp; bool strip; diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 009eeb9ec140..fd64bb062abc 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -54,6 +54,14 @@ const known_options = [_]KnownOpt{ .name = "fno-PIC", .ident = "no_pic", }, + .{ + .name = "fPIE", + .ident = "pie", + }, + .{ + .name = "fno-PIE", + .ident = "no_pie", + }, .{ .name = "nolibc", .ident = "nostdlib",