stage2: implement stack protectors
This is one of the final remaining TODOs for the LLVM backend.
This commit is contained in:
@@ -173,6 +173,7 @@ astgen_wait_group: WaitGroup = .{},
|
||||
/// TODO: Remove this when Stage2 becomes the default compiler as it will already have this information.
|
||||
export_symbol_names: std.ArrayListUnmanaged([]const u8) = .{},
|
||||
|
||||
pub const default_stack_protector_buffer_size = 4;
|
||||
pub const SemaError = Module.SemaError;
|
||||
|
||||
pub const CRTFile = struct {
|
||||
@@ -837,6 +838,10 @@ pub const InitOptions = struct {
|
||||
want_pie: ?bool = null,
|
||||
want_sanitize_c: ?bool = null,
|
||||
want_stack_check: ?bool = null,
|
||||
/// null means default.
|
||||
/// 0 means no stack protector.
|
||||
/// other number means stack protection with that buffer size.
|
||||
want_stack_protector: ?u32 = null,
|
||||
want_red_zone: ?bool = null,
|
||||
omit_frame_pointer: ?bool = null,
|
||||
want_valgrind: ?bool = null,
|
||||
@@ -1014,6 +1019,15 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
return error.ExportTableAndImportTableConflict;
|
||||
}
|
||||
|
||||
// The `have_llvm` condition is here only because native backends cannot yet build compiler-rt.
|
||||
// Once they are capable this condition could be removed. When removing this condition,
|
||||
// also test the use case of `build-obj -fcompiler-rt` with the native backends
|
||||
// and make sure the compiler-rt symbols are emitted.
|
||||
const capable_of_building_compiler_rt = build_options.have_llvm;
|
||||
|
||||
const capable_of_building_zig_libc = build_options.have_llvm;
|
||||
const capable_of_building_ssp = build_options.have_llvm;
|
||||
|
||||
const comp: *Compilation = comp: {
|
||||
// For allocations that have the same lifetime as Compilation. This arena is used only during this
|
||||
// initialization and then is freed in deinit().
|
||||
@@ -1289,11 +1303,36 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
|
||||
const sanitize_c = options.want_sanitize_c orelse is_safe_mode;
|
||||
|
||||
const stack_check: bool = b: {
|
||||
if (!target_util.supportsStackProbing(options.target))
|
||||
break :b false;
|
||||
break :b options.want_stack_check orelse is_safe_mode;
|
||||
const stack_check: bool = options.want_stack_check orelse b: {
|
||||
if (!target_util.supportsStackProbing(options.target)) break :b false;
|
||||
break :b is_safe_mode;
|
||||
};
|
||||
if (stack_check and !target_util.supportsStackProbing(options.target))
|
||||
return error.StackCheckUnsupportedByTarget;
|
||||
|
||||
const stack_protector: u32 = options.want_stack_protector orelse b: {
|
||||
if (!target_util.supportsStackProtector(options.target)) break :b @as(u32, 0);
|
||||
|
||||
// This logic is checking for linking libc because otherwise our start code
|
||||
// which is trying to set up TLS (i.e. the fs/gs registers) but the stack
|
||||
// protection code depends on fs/gs registers being already set up.
|
||||
// If we were able to annotate start code, or perhaps the entire std lib,
|
||||
// as being exempt from stack protection checks, we could change this logic
|
||||
// to supporting stack protection even when not linking libc.
|
||||
// TODO file issue about this
|
||||
if (!link_libc) break :b 0;
|
||||
if (!capable_of_building_ssp) break :b 0;
|
||||
if (is_safe_mode) break :b default_stack_protector_buffer_size;
|
||||
break :b 0;
|
||||
};
|
||||
if (stack_protector != 0) {
|
||||
if (!target_util.supportsStackProtector(options.target))
|
||||
return error.StackProtectorUnsupportedByTarget;
|
||||
if (!capable_of_building_ssp)
|
||||
return error.StackProtectorUnsupportedByBackend;
|
||||
if (!link_libc)
|
||||
return error.StackProtectorUnavailableWithoutLibC;
|
||||
}
|
||||
|
||||
const valgrind: bool = b: {
|
||||
if (!target_util.hasValgrindSupport(options.target))
|
||||
@@ -1378,6 +1417,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
cache.hash.add(unwind_tables);
|
||||
cache.hash.add(tsan);
|
||||
cache.hash.add(stack_check);
|
||||
cache.hash.add(stack_protector);
|
||||
cache.hash.add(red_zone);
|
||||
cache.hash.add(omit_frame_pointer);
|
||||
cache.hash.add(link_mode);
|
||||
@@ -1741,6 +1781,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
.valgrind = valgrind,
|
||||
.tsan = tsan,
|
||||
.stack_check = stack_check,
|
||||
.stack_protector = stack_protector,
|
||||
.red_zone = red_zone,
|
||||
.omit_frame_pointer = omit_frame_pointer,
|
||||
.single_threaded = single_threaded,
|
||||
@@ -1822,6 +1863,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
};
|
||||
errdefer comp.destroy();
|
||||
|
||||
const target = comp.getTarget();
|
||||
|
||||
// Add a `CObject` for each `c_source_files`.
|
||||
try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
|
||||
for (options.c_source_files) |c_source_file| {
|
||||
@@ -1837,11 +1880,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
|
||||
const have_bin_emit = comp.bin_file.options.emit != null or comp.whole_bin_sub_path != null;
|
||||
|
||||
if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and
|
||||
options.target.ofmt != .c)
|
||||
{
|
||||
if (comp.getTarget().isDarwin()) {
|
||||
switch (comp.getTarget().abi) {
|
||||
if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and target.ofmt != .c) {
|
||||
if (target.isDarwin()) {
|
||||
switch (target.abi) {
|
||||
.none,
|
||||
.simulator,
|
||||
.macabi,
|
||||
@@ -1852,9 +1893,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
// If we need to build glibc for the target, add work items for it.
|
||||
// We go through the work queue so that building can be done in parallel.
|
||||
if (comp.wantBuildGLibCFromSource()) {
|
||||
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
|
||||
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
|
||||
|
||||
if (glibc.needsCrtiCrtn(comp.getTarget())) {
|
||||
if (glibc.needsCrtiCrtn(target)) {
|
||||
try comp.work_queue.write(&[_]Job{
|
||||
.{ .glibc_crt_file = .crti_o },
|
||||
.{ .glibc_crt_file = .crtn_o },
|
||||
@@ -1867,10 +1908,10 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
});
|
||||
}
|
||||
if (comp.wantBuildMuslFromSource()) {
|
||||
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
|
||||
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
|
||||
|
||||
try comp.work_queue.ensureUnusedCapacity(6);
|
||||
if (musl.needsCrtiCrtn(comp.getTarget())) {
|
||||
if (musl.needsCrtiCrtn(target)) {
|
||||
comp.work_queue.writeAssumeCapacity(&[_]Job{
|
||||
.{ .musl_crt_file = .crti_o },
|
||||
.{ .musl_crt_file = .crtn_o },
|
||||
@@ -1887,7 +1928,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
});
|
||||
}
|
||||
if (comp.wantBuildWasiLibcFromSource()) {
|
||||
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
|
||||
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
|
||||
|
||||
const wasi_emulated_libs = comp.bin_file.options.wasi_emulated_libs;
|
||||
try comp.work_queue.ensureUnusedCapacity(wasi_emulated_libs.len + 2); // worst-case we need all components
|
||||
@@ -1902,7 +1943,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
});
|
||||
}
|
||||
if (comp.wantBuildMinGWFromSource()) {
|
||||
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
|
||||
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
|
||||
|
||||
const static_lib_jobs = [_]Job{
|
||||
.{ .mingw_crt_file = .mingw32_lib },
|
||||
@@ -1921,7 +1962,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
}
|
||||
}
|
||||
// Generate Windows import libs.
|
||||
if (comp.getTarget().os.tag == .windows) {
|
||||
if (target.os.tag == .windows) {
|
||||
const count = comp.bin_file.options.system_libs.count();
|
||||
try comp.work_queue.ensureUnusedCapacity(count);
|
||||
var i: usize = 0;
|
||||
@@ -1940,15 +1981,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
try comp.work_queue.writeItem(.libtsan);
|
||||
}
|
||||
|
||||
// The `have_llvm` condition is here only because native backends cannot yet build compiler-rt.
|
||||
// Once they are capable this condition could be removed. When removing this condition,
|
||||
// also test the use case of `build-obj -fcompiler-rt` with the native backends
|
||||
// and make sure the compiler-rt symbols are emitted.
|
||||
const capable_of_building_compiler_rt = build_options.have_llvm;
|
||||
|
||||
const capable_of_building_zig_libc = build_options.have_llvm;
|
||||
const capable_of_building_ssp = comp.bin_file.options.use_stage1;
|
||||
|
||||
if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) {
|
||||
if (is_exe_or_dyn_lib) {
|
||||
log.debug("queuing a job to build compiler_rt_lib", .{});
|
||||
@@ -1962,8 +1994,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||
}
|
||||
}
|
||||
if (needs_c_symbols) {
|
||||
// MinGW provides no libssp, use our own implementation.
|
||||
if (comp.getTarget().isMinGW() and capable_of_building_ssp) {
|
||||
// Related: https://github.com/ziglang/zig/issues/7265.
|
||||
if (comp.bin_file.options.stack_protector != 0 and
|
||||
(!comp.bin_file.options.link_libc or
|
||||
!target_util.libcProvidesStackProtector(target)))
|
||||
{
|
||||
try comp.work_queue.writeItem(.{ .libssp = {} });
|
||||
}
|
||||
|
||||
@@ -4123,6 +4158,17 @@ pub fn addCCArgs(
|
||||
try argv.append("-fno-omit-frame-pointer");
|
||||
}
|
||||
|
||||
const ssp_buf_size = comp.bin_file.options.stack_protector;
|
||||
if (ssp_buf_size != 0) {
|
||||
try argv.appendSlice(&[_][]const u8{
|
||||
"-fstack-protector-strong",
|
||||
"--param",
|
||||
try std.fmt.allocPrint(arena, "ssp-buffer-size={d}", .{ssp_buf_size}),
|
||||
});
|
||||
} else {
|
||||
try argv.append("-fno-stack-protector");
|
||||
}
|
||||
|
||||
switch (comp.bin_file.options.optimize_mode) {
|
||||
.Debug => {
|
||||
// windows c runtime requires -D_DEBUG if using debug libraries
|
||||
@@ -4131,27 +4177,12 @@ pub fn addCCArgs(
|
||||
// to -O1. Besides potentially impairing debugging, -O1/-Og significantly
|
||||
// increases compile times.
|
||||
try argv.append("-O0");
|
||||
|
||||
if (comp.bin_file.options.link_libc and target.os.tag != .wasi) {
|
||||
try argv.append("-fstack-protector-strong");
|
||||
try argv.append("--param");
|
||||
try argv.append("ssp-buffer-size=4");
|
||||
} else {
|
||||
try argv.append("-fno-stack-protector");
|
||||
}
|
||||
},
|
||||
.ReleaseSafe => {
|
||||
// See the comment in the BuildModeFastRelease case for why we pass -O2 rather
|
||||
// than -O3 here.
|
||||
try argv.append("-O2");
|
||||
if (comp.bin_file.options.link_libc and target.os.tag != .wasi) {
|
||||
try argv.append("-D_FORTIFY_SOURCE=2");
|
||||
try argv.append("-fstack-protector-strong");
|
||||
try argv.append("--param");
|
||||
try argv.append("ssp-buffer-size=4");
|
||||
} else {
|
||||
try argv.append("-fno-stack-protector");
|
||||
}
|
||||
try argv.append("-D_FORTIFY_SOURCE=2");
|
||||
},
|
||||
.ReleaseFast => {
|
||||
try argv.append("-DNDEBUG");
|
||||
@@ -4161,12 +4192,10 @@ pub fn addCCArgs(
|
||||
// Zig code than it is for C code. Also, C programmers are used to their code
|
||||
// running in -O2 and thus the -O3 path has been tested less.
|
||||
try argv.append("-O2");
|
||||
try argv.append("-fno-stack-protector");
|
||||
},
|
||||
.ReleaseSmall => {
|
||||
try argv.append("-DNDEBUG");
|
||||
try argv.append("-Os");
|
||||
try argv.append("-fno-stack-protector");
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5031,6 +5060,7 @@ fn buildOutputFromZig(
|
||||
.use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1,
|
||||
.want_sanitize_c = false,
|
||||
.want_stack_check = false,
|
||||
.want_stack_protector = 0,
|
||||
.want_red_zone = comp.bin_file.options.red_zone,
|
||||
.omit_frame_pointer = comp.bin_file.options.omit_frame_pointer,
|
||||
.want_valgrind = false,
|
||||
@@ -5311,6 +5341,7 @@ pub fn build_crt_file(
|
||||
.optimize_mode = comp.compilerRtOptMode(),
|
||||
.want_sanitize_c = false,
|
||||
.want_stack_check = false,
|
||||
.want_stack_protector = 0,
|
||||
.want_red_zone = comp.bin_file.options.red_zone,
|
||||
.omit_frame_pointer = comp.bin_file.options.omit_frame_pointer,
|
||||
.want_valgrind = false,
|
||||
|
||||
Reference in New Issue
Block a user