Skip to content

Commit be2bd58

Browse files
authored
Merge pull request #12472 from ziglang/stage2-stack-protector
stage2: implement stack protectors
2 parents 2ccaa54 + fdb934a commit be2bd58

15 files changed

+193
-61
lines changed

src/Compilation.zig

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ astgen_wait_group: WaitGroup = .{},
173173
/// TODO: Remove this when Stage2 becomes the default compiler as it will already have this information.
174174
export_symbol_names: std.ArrayListUnmanaged([]const u8) = .{},
175175

176+
pub const default_stack_protector_buffer_size = 4;
176177
pub const SemaError = Module.SemaError;
177178

178179
pub const CRTFile = struct {
@@ -837,6 +838,10 @@ pub const InitOptions = struct {
837838
want_pie: ?bool = null,
838839
want_sanitize_c: ?bool = null,
839840
want_stack_check: ?bool = null,
841+
/// null means default.
842+
/// 0 means no stack protector.
843+
/// other number means stack protection with that buffer size.
844+
want_stack_protector: ?u32 = null,
840845
want_red_zone: ?bool = null,
841846
omit_frame_pointer: ?bool = null,
842847
want_valgrind: ?bool = null,
@@ -1014,6 +1019,15 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
10141019
return error.ExportTableAndImportTableConflict;
10151020
}
10161021

1022+
// The `have_llvm` condition is here only because native backends cannot yet build compiler-rt.
1023+
// Once they are capable this condition could be removed. When removing this condition,
1024+
// also test the use case of `build-obj -fcompiler-rt` with the native backends
1025+
// and make sure the compiler-rt symbols are emitted.
1026+
const capable_of_building_compiler_rt = build_options.have_llvm;
1027+
1028+
const capable_of_building_zig_libc = build_options.have_llvm;
1029+
const capable_of_building_ssp = build_options.have_llvm;
1030+
10171031
const comp: *Compilation = comp: {
10181032
// For allocations that have the same lifetime as Compilation. This arena is used only during this
10191033
// initialization and then is freed in deinit().
@@ -1289,11 +1303,36 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
12891303

12901304
const sanitize_c = options.want_sanitize_c orelse is_safe_mode;
12911305

1292-
const stack_check: bool = b: {
1293-
if (!target_util.supportsStackProbing(options.target))
1294-
break :b false;
1295-
break :b options.want_stack_check orelse is_safe_mode;
1306+
const stack_check: bool = options.want_stack_check orelse b: {
1307+
if (!target_util.supportsStackProbing(options.target)) break :b false;
1308+
break :b is_safe_mode;
12961309
};
1310+
if (stack_check and !target_util.supportsStackProbing(options.target))
1311+
return error.StackCheckUnsupportedByTarget;
1312+
1313+
const stack_protector: u32 = options.want_stack_protector orelse b: {
1314+
if (!target_util.supportsStackProtector(options.target)) break :b @as(u32, 0);
1315+
1316+
// This logic is checking for linking libc because otherwise our start code
1317+
// which is trying to set up TLS (i.e. the fs/gs registers) but the stack
1318+
// protection code depends on fs/gs registers being already set up.
1319+
// If we were able to annotate start code, or perhaps the entire std lib,
1320+
// as being exempt from stack protection checks, we could change this logic
1321+
// to supporting stack protection even when not linking libc.
1322+
// TODO file issue about this
1323+
if (!link_libc) break :b 0;
1324+
if (!capable_of_building_ssp) break :b 0;
1325+
if (is_safe_mode) break :b default_stack_protector_buffer_size;
1326+
break :b 0;
1327+
};
1328+
if (stack_protector != 0) {
1329+
if (!target_util.supportsStackProtector(options.target))
1330+
return error.StackProtectorUnsupportedByTarget;
1331+
if (!capable_of_building_ssp)
1332+
return error.StackProtectorUnsupportedByBackend;
1333+
if (!link_libc)
1334+
return error.StackProtectorUnavailableWithoutLibC;
1335+
}
12971336

12981337
const valgrind: bool = b: {
12991338
if (!target_util.hasValgrindSupport(options.target))
@@ -1378,6 +1417,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
13781417
cache.hash.add(unwind_tables);
13791418
cache.hash.add(tsan);
13801419
cache.hash.add(stack_check);
1420+
cache.hash.add(stack_protector);
13811421
cache.hash.add(red_zone);
13821422
cache.hash.add(omit_frame_pointer);
13831423
cache.hash.add(link_mode);
@@ -1741,6 +1781,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
17411781
.valgrind = valgrind,
17421782
.tsan = tsan,
17431783
.stack_check = stack_check,
1784+
.stack_protector = stack_protector,
17441785
.red_zone = red_zone,
17451786
.omit_frame_pointer = omit_frame_pointer,
17461787
.single_threaded = single_threaded,
@@ -1822,6 +1863,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18221863
};
18231864
errdefer comp.destroy();
18241865

1866+
const target = comp.getTarget();
1867+
18251868
// Add a `CObject` for each `c_source_files`.
18261869
try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
18271870
for (options.c_source_files) |c_source_file| {
@@ -1837,11 +1880,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18371880

18381881
const have_bin_emit = comp.bin_file.options.emit != null or comp.whole_bin_sub_path != null;
18391882

1840-
if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and
1841-
options.target.ofmt != .c)
1842-
{
1843-
if (comp.getTarget().isDarwin()) {
1844-
switch (comp.getTarget().abi) {
1883+
if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and target.ofmt != .c) {
1884+
if (target.isDarwin()) {
1885+
switch (target.abi) {
18451886
.none,
18461887
.simulator,
18471888
.macabi,
@@ -1852,9 +1893,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18521893
// If we need to build glibc for the target, add work items for it.
18531894
// We go through the work queue so that building can be done in parallel.
18541895
if (comp.wantBuildGLibCFromSource()) {
1855-
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
1896+
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
18561897

1857-
if (glibc.needsCrtiCrtn(comp.getTarget())) {
1898+
if (glibc.needsCrtiCrtn(target)) {
18581899
try comp.work_queue.write(&[_]Job{
18591900
.{ .glibc_crt_file = .crti_o },
18601901
.{ .glibc_crt_file = .crtn_o },
@@ -1867,10 +1908,10 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18671908
});
18681909
}
18691910
if (comp.wantBuildMuslFromSource()) {
1870-
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
1911+
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
18711912

18721913
try comp.work_queue.ensureUnusedCapacity(6);
1873-
if (musl.needsCrtiCrtn(comp.getTarget())) {
1914+
if (musl.needsCrtiCrtn(target)) {
18741915
comp.work_queue.writeAssumeCapacity(&[_]Job{
18751916
.{ .musl_crt_file = .crti_o },
18761917
.{ .musl_crt_file = .crtn_o },
@@ -1887,7 +1928,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
18871928
});
18881929
}
18891930
if (comp.wantBuildWasiLibcFromSource()) {
1890-
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
1931+
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
18911932

18921933
const wasi_emulated_libs = comp.bin_file.options.wasi_emulated_libs;
18931934
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 {
19021943
});
19031944
}
19041945
if (comp.wantBuildMinGWFromSource()) {
1905-
if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable;
1946+
if (!target_util.canBuildLibC(target)) return error.LibCUnavailable;
19061947

19071948
const static_lib_jobs = [_]Job{
19081949
.{ .mingw_crt_file = .mingw32_lib },
@@ -1921,7 +1962,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
19211962
}
19221963
}
19231964
// Generate Windows import libs.
1924-
if (comp.getTarget().os.tag == .windows) {
1965+
if (target.os.tag == .windows) {
19251966
const count = comp.bin_file.options.system_libs.count();
19261967
try comp.work_queue.ensureUnusedCapacity(count);
19271968
var i: usize = 0;
@@ -1940,15 +1981,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
19401981
try comp.work_queue.writeItem(.libtsan);
19411982
}
19421983

1943-
// The `have_llvm` condition is here only because native backends cannot yet build compiler-rt.
1944-
// Once they are capable this condition could be removed. When removing this condition,
1945-
// also test the use case of `build-obj -fcompiler-rt` with the native backends
1946-
// and make sure the compiler-rt symbols are emitted.
1947-
const capable_of_building_compiler_rt = build_options.have_llvm;
1948-
1949-
const capable_of_building_zig_libc = build_options.have_llvm;
1950-
const capable_of_building_ssp = comp.bin_file.options.use_stage1;
1951-
19521984
if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) {
19531985
if (is_exe_or_dyn_lib) {
19541986
log.debug("queuing a job to build compiler_rt_lib", .{});
@@ -1962,8 +1994,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
19621994
}
19631995
}
19641996
if (needs_c_symbols) {
1965-
// MinGW provides no libssp, use our own implementation.
1966-
if (comp.getTarget().isMinGW() and capable_of_building_ssp) {
1997+
// Related: https://github.com/ziglang/zig/issues/7265.
1998+
if (comp.bin_file.options.stack_protector != 0 and
1999+
(!comp.bin_file.options.link_libc or
2000+
!target_util.libcProvidesStackProtector(target)))
2001+
{
19672002
try comp.work_queue.writeItem(.{ .libssp = {} });
19682003
}
19692004

@@ -4123,6 +4158,17 @@ pub fn addCCArgs(
41234158
try argv.append("-fno-omit-frame-pointer");
41244159
}
41254160

4161+
const ssp_buf_size = comp.bin_file.options.stack_protector;
4162+
if (ssp_buf_size != 0) {
4163+
try argv.appendSlice(&[_][]const u8{
4164+
"-fstack-protector-strong",
4165+
"--param",
4166+
try std.fmt.allocPrint(arena, "ssp-buffer-size={d}", .{ssp_buf_size}),
4167+
});
4168+
} else {
4169+
try argv.append("-fno-stack-protector");
4170+
}
4171+
41264172
switch (comp.bin_file.options.optimize_mode) {
41274173
.Debug => {
41284174
// windows c runtime requires -D_DEBUG if using debug libraries
@@ -4131,27 +4177,12 @@ pub fn addCCArgs(
41314177
// to -O1. Besides potentially impairing debugging, -O1/-Og significantly
41324178
// increases compile times.
41334179
try argv.append("-O0");
4134-
4135-
if (comp.bin_file.options.link_libc and target.os.tag != .wasi) {
4136-
try argv.append("-fstack-protector-strong");
4137-
try argv.append("--param");
4138-
try argv.append("ssp-buffer-size=4");
4139-
} else {
4140-
try argv.append("-fno-stack-protector");
4141-
}
41424180
},
41434181
.ReleaseSafe => {
41444182
// See the comment in the BuildModeFastRelease case for why we pass -O2 rather
41454183
// than -O3 here.
41464184
try argv.append("-O2");
4147-
if (comp.bin_file.options.link_libc and target.os.tag != .wasi) {
4148-
try argv.append("-D_FORTIFY_SOURCE=2");
4149-
try argv.append("-fstack-protector-strong");
4150-
try argv.append("--param");
4151-
try argv.append("ssp-buffer-size=4");
4152-
} else {
4153-
try argv.append("-fno-stack-protector");
4154-
}
4185+
try argv.append("-D_FORTIFY_SOURCE=2");
41554186
},
41564187
.ReleaseFast => {
41574188
try argv.append("-DNDEBUG");
@@ -4161,12 +4192,10 @@ pub fn addCCArgs(
41614192
// Zig code than it is for C code. Also, C programmers are used to their code
41624193
// running in -O2 and thus the -O3 path has been tested less.
41634194
try argv.append("-O2");
4164-
try argv.append("-fno-stack-protector");
41654195
},
41664196
.ReleaseSmall => {
41674197
try argv.append("-DNDEBUG");
41684198
try argv.append("-Os");
4169-
try argv.append("-fno-stack-protector");
41704199
},
41714200
}
41724201

@@ -5031,6 +5060,7 @@ fn buildOutputFromZig(
50315060
.use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1,
50325061
.want_sanitize_c = false,
50335062
.want_stack_check = false,
5063+
.want_stack_protector = 0,
50345064
.want_red_zone = comp.bin_file.options.red_zone,
50355065
.omit_frame_pointer = comp.bin_file.options.omit_frame_pointer,
50365066
.want_valgrind = false,
@@ -5311,6 +5341,7 @@ pub fn build_crt_file(
53115341
.optimize_mode = comp.compilerRtOptMode(),
53125342
.want_sanitize_c = false,
53135343
.want_stack_check = false,
5344+
.want_stack_protector = 0,
53145345
.want_red_zone = comp.bin_file.options.red_zone,
53155346
.omit_frame_pointer = comp.bin_file.options.omit_frame_pointer,
53165347
.want_valgrind = false,

src/Sema.zig

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7567,45 +7567,46 @@ fn handleExternLibName(
75677567
) CompileError![:0]u8 {
75687568
blk: {
75697569
const mod = sema.mod;
7570+
const comp = mod.comp;
75707571
const target = mod.getTarget();
75717572
log.debug("extern fn symbol expected in lib '{s}'", .{lib_name});
75727573
if (target_util.is_libc_lib_name(target, lib_name)) {
7573-
if (!mod.comp.bin_file.options.link_libc) {
7574+
if (!comp.bin_file.options.link_libc and !comp.bin_file.options.parent_compilation_link_libc) {
75747575
return sema.fail(
75757576
block,
75767577
src_loc,
75777578
"dependency on libc must be explicitly specified in the build command",
75787579
.{},
75797580
);
75807581
}
7581-
mod.comp.bin_file.options.link_libc = true;
7582+
comp.bin_file.options.link_libc = true;
75827583
break :blk;
75837584
}
75847585
if (target_util.is_libcpp_lib_name(target, lib_name)) {
7585-
if (!mod.comp.bin_file.options.link_libcpp) {
7586+
if (!comp.bin_file.options.link_libcpp) {
75867587
return sema.fail(
75877588
block,
75887589
src_loc,
75897590
"dependency on libc++ must be explicitly specified in the build command",
75907591
.{},
75917592
);
75927593
}
7593-
mod.comp.bin_file.options.link_libcpp = true;
7594+
comp.bin_file.options.link_libcpp = true;
75947595
break :blk;
75957596
}
75967597
if (mem.eql(u8, lib_name, "unwind")) {
7597-
mod.comp.bin_file.options.link_libunwind = true;
7598+
comp.bin_file.options.link_libunwind = true;
75987599
break :blk;
75997600
}
7600-
if (!target.isWasm() and !mod.comp.bin_file.options.pic) {
7601+
if (!target.isWasm() and !comp.bin_file.options.pic) {
76017602
return sema.fail(
76027603
block,
76037604
src_loc,
76047605
"dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.",
76057606
.{ lib_name, lib_name },
76067607
);
76077608
}
7608-
mod.comp.stage1AddLinkLib(lib_name) catch |err| {
7609+
comp.stage1AddLinkLib(lib_name) catch |err| {
76097610
return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{
76107611
lib_name, @errorName(err),
76117612
});

src/clang_options_data.zig

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3290,7 +3290,14 @@ flagpd1("fno-stack-arrays"),
32903290
.psl = false,
32913291
},
32923292
flagpd1("fno-stack-clash-protection"),
3293-
flagpd1("fno-stack-protector"),
3293+
.{
3294+
.name = "fno-stack-protector",
3295+
.syntax = .flag,
3296+
.zig_equivalent = .no_stack_protector,
3297+
.pd1 = true,
3298+
.pd2 = false,
3299+
.psl = false,
3300+
},
32943301
flagpd1("fno-stack-size-section"),
32953302
flagpd1("fno-standalone-debug"),
32963303
flagpd1("fno-strength-reduce"),
@@ -3588,9 +3595,30 @@ flagpd1("fstack-arrays"),
35883595
.psl = false,
35893596
},
35903597
flagpd1("fstack-clash-protection"),
3591-
flagpd1("fstack-protector"),
3592-
flagpd1("fstack-protector-all"),
3593-
flagpd1("fstack-protector-strong"),
3598+
.{
3599+
.name = "fstack-protector",
3600+
.syntax = .flag,
3601+
.zig_equivalent = .stack_protector,
3602+
.pd1 = true,
3603+
.pd2 = false,
3604+
.psl = false,
3605+
},
3606+
.{
3607+
.name = "fstack-protector-all",
3608+
.syntax = .flag,
3609+
.zig_equivalent = .stack_protector,
3610+
.pd1 = true,
3611+
.pd2 = false,
3612+
.psl = false,
3613+
},
3614+
.{
3615+
.name = "fstack-protector-strong",
3616+
.syntax = .flag,
3617+
.zig_equivalent = .stack_protector,
3618+
.pd1 = true,
3619+
.pd2 = false,
3620+
.psl = false,
3621+
},
35943622
flagpd1("fstack-size-section"),
35953623
flagpd1("fstack-usage"),
35963624
flagpd1("fstandalone-debug"),
@@ -4809,7 +4837,14 @@ flagpd1("single_module"),
48094837
},
48104838
sepd1("split-dwarf-file"),
48114839
sepd1("split-dwarf-output"),
4812-
sepd1("stack-protector"),
4840+
.{
4841+
.name = "stack-protector",
4842+
.syntax = .separate,
4843+
.zig_equivalent = .stack_protector,
4844+
.pd1 = true,
4845+
.pd2 = false,
4846+
.psl = false,
4847+
},
48134848
sepd1("stack-protector-buffer-size"),
48144849
sepd1("stack-usage-file"),
48154850
.{

0 commit comments

Comments
 (0)