Skip to content

Commit b3537d0

Browse files
committed
compiler: Allow configuring UBSan mode at the module level.
* Accept -fsanitize-c=trap|full in addition to the existing form. * Accept -f(no-)sanitize-trap=undefined in zig cc. * Change type of std.Build.Module.sanitize_c to std.zig.SanitizeC. * Add some missing Compilation.Config fields to the cache. Closes #23216.
1 parent 23440fb commit b3537d0

File tree

15 files changed

+176
-57
lines changed

15 files changed

+176
-57
lines changed

lib/std/Build/Module.zig

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ unwind_tables: ?std.builtin.UnwindTables,
2222
single_threaded: ?bool,
2323
stack_protector: ?bool,
2424
stack_check: ?bool,
25-
sanitize_c: ?bool,
25+
sanitize_c: ?std.zig.SanitizeC,
2626
sanitize_thread: ?bool,
2727
fuzz: ?bool,
2828
code_model: std.builtin.CodeModel,
@@ -256,7 +256,7 @@ pub const CreateOptions = struct {
256256
code_model: std.builtin.CodeModel = .default,
257257
stack_protector: ?bool = null,
258258
stack_check: ?bool = null,
259-
sanitize_c: ?bool = null,
259+
sanitize_c: ?std.zig.SanitizeC = null,
260260
sanitize_thread: ?bool = null,
261261
fuzz: ?bool = null,
262262
/// Whether to emit machine code that integrates with Valgrind.
@@ -559,13 +559,18 @@ pub fn appendZigProcessFlags(
559559
try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");
560560
try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer");
561561
try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");
562-
try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c");
563562
try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");
564563
try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");
565564
try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
566565
try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
567566
try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
568567

568+
if (m.sanitize_c) |sc| switch (sc) {
569+
.off => try zig_args.append("-fno-sanitize-c"),
570+
.trap => try zig_args.append("-fsanitize-c=trap"),
571+
.full => try zig_args.append("-fsanitize-c=full"),
572+
};
573+
569574
if (m.dwarf_format) |dwarf_format| {
570575
try zig_args.append(switch (dwarf_format) {
571576
.@"32" => "-gdwarf32",

lib/std/zig.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe
236236
}
237237
}
238238

239+
pub const SanitizeC = enum {
240+
off,
241+
trap,
242+
full,
243+
};
244+
239245
pub const BuildId = union(enum) {
240246
none,
241247
fast,

src/Compilation.zig

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,14 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
12871287
const any_unwind_tables = options.config.any_unwind_tables or options.root_mod.unwind_tables != .none;
12881288
const any_non_single_threaded = options.config.any_non_single_threaded or !options.root_mod.single_threaded;
12891289
const any_sanitize_thread = options.config.any_sanitize_thread or options.root_mod.sanitize_thread;
1290-
const any_sanitize_c = options.config.any_sanitize_c or options.root_mod.sanitize_c;
1290+
const any_sanitize_c: std.zig.SanitizeC = switch (options.config.any_sanitize_c) {
1291+
.off => options.root_mod.sanitize_c,
1292+
.trap => if (options.root_mod.sanitize_c == .full)
1293+
.full
1294+
else
1295+
.trap,
1296+
.full => .full,
1297+
};
12911298
const any_fuzz = options.config.any_fuzz or options.root_mod.fuzz;
12921299

12931300
const link_eh_frame_hdr = options.link_eh_frame_hdr or any_unwind_tables;
@@ -1346,7 +1353,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
13461353
// and this reduces unnecessary bloat.
13471354
const ubsan_rt_strat: RtStrat = s: {
13481355
const is_spirv = options.root_mod.resolved_target.result.cpu.arch.isSpirV();
1349-
const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c and is_exe_or_dyn_lib);
1356+
const want_ubsan_rt = options.want_ubsan_rt orelse (!is_spirv and any_sanitize_c == .full and is_exe_or_dyn_lib);
13501357
if (!want_ubsan_rt) break :s .none;
13511358
if (options.skip_linker_dependencies) break :s .none;
13521359
if (have_zcu) break :s .zcu;
@@ -1418,6 +1425,10 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
14181425
cache.hash.add(options.config.lto);
14191426
cache.hash.add(options.config.link_mode);
14201427
cache.hash.add(options.config.any_unwind_tables);
1428+
cache.hash.add(options.config.any_non_single_threaded);
1429+
cache.hash.add(options.config.any_sanitize_thread);
1430+
cache.hash.add(options.config.any_sanitize_c);
1431+
cache.hash.add(options.config.any_fuzz);
14211432
cache.hash.add(options.function_sections);
14221433
cache.hash.add(options.data_sections);
14231434
cache.hash.add(link_libc);
@@ -6048,7 +6059,7 @@ pub fn addCCArgs(
60486059
{
60496060
var san_arg: std.ArrayListUnmanaged(u8) = .empty;
60506061
const prefix = "-fsanitize=";
6051-
if (mod.sanitize_c) {
6062+
if (mod.sanitize_c != .off) {
60526063
if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);
60536064
try san_arg.appendSlice(arena, "undefined,");
60546065
}
@@ -6064,37 +6075,33 @@ pub fn addCCArgs(
60646075
if (san_arg.pop()) |_| {
60656076
try argv.append(san_arg.items);
60666077

6067-
// These args have to be added after the `-fsanitize` arg or
6068-
// they won't take effect.
6069-
if (mod.sanitize_c) {
6070-
// This check requires implementing the Itanium C++ ABI.
6071-
// We would make it `-fsanitize-trap=vptr`, however this check requires
6072-
// a full runtime due to the type hashing involved.
6073-
try argv.append("-fno-sanitize=vptr");
6074-
6075-
// It is very common, and well-defined, for a pointer on one side of a C ABI
6076-
// to have a different but compatible element type. Examples include:
6077-
// `char*` vs `uint8_t*` on a system with 8-bit bytes
6078-
// `const char*` vs `char*`
6079-
// `char*` vs `unsigned char*`
6080-
// Without this flag, Clang would invoke UBSAN when such an extern
6081-
// function was called.
6082-
try argv.append("-fno-sanitize=function");
6083-
6084-
if (mod.optimize_mode == .ReleaseSafe) {
6085-
// It's recommended to use the minimal runtime in production
6086-
// environments due to the security implications of the full runtime.
6087-
// The minimal runtime doesn't provide much benefit over simply
6088-
// trapping, however, so we do that instead.
6078+
switch (mod.sanitize_c) {
6079+
.off => {},
6080+
.trap => {
60896081
try argv.append("-fsanitize-trap=undefined");
6090-
} else {
6082+
},
6083+
.full => {
6084+
// This check requires implementing the Itanium C++ ABI.
6085+
// We would make it `-fsanitize-trap=vptr`, however this check requires
6086+
// a full runtime due to the type hashing involved.
6087+
try argv.append("-fno-sanitize=vptr");
6088+
6089+
// It is very common, and well-defined, for a pointer on one side of a C ABI
6090+
// to have a different but compatible element type. Examples include:
6091+
// `char*` vs `uint8_t*` on a system with 8-bit bytes
6092+
// `const char*` vs `char*`
6093+
// `char*` vs `unsigned char*`
6094+
// Without this flag, Clang would invoke UBSAN when such an extern
6095+
// function was called.
6096+
try argv.append("-fno-sanitize=function");
6097+
60916098
// This is necessary because, by default, Clang instructs LLVM to embed
60926099
// a COFF link dependency on `libclang_rt.ubsan_standalone.a` when the
60936100
// UBSan runtime is used.
60946101
if (target.os.tag == .windows) {
60956102
try argv.append("-fno-rtlib-defaultlib");
60966103
}
6097-
}
6104+
},
60986105
}
60996106
}
61006107

@@ -6797,7 +6804,7 @@ pub fn build_crt_file(
67976804
.strip = comp.compilerRtStrip(),
67986805
.stack_check = false,
67996806
.stack_protector = 0,
6800-
.sanitize_c = false,
6807+
.sanitize_c = .off,
68016808
.sanitize_thread = false,
68026809
.red_zone = comp.root_mod.red_zone,
68036810
// Some libcs (e.g. musl) are opinionated about -fomit-frame-pointer.

src/Compilation/Config.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ any_non_single_threaded: bool,
3232
/// per-Module setting.
3333
any_error_tracing: bool,
3434
any_sanitize_thread: bool,
35-
any_sanitize_c: bool,
35+
any_sanitize_c: std.zig.SanitizeC,
3636
any_fuzz: bool,
3737
pie: bool,
3838
/// If this is true then linker code is responsible for making an LLVM IR
@@ -86,7 +86,7 @@ pub const Options = struct {
8686
ensure_libcpp_on_non_freestanding: bool = false,
8787
any_non_single_threaded: bool = false,
8888
any_sanitize_thread: bool = false,
89-
any_sanitize_c: bool = false,
89+
any_sanitize_c: std.zig.SanitizeC = .off,
9090
any_fuzz: bool = false,
9191
any_unwind_tables: bool = false,
9292
any_dyn_libs: bool = false,

src/Package/Module.zig

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ omit_frame_pointer: bool,
2424
stack_check: bool,
2525
stack_protector: u32,
2626
red_zone: bool,
27-
sanitize_c: bool,
27+
sanitize_c: std.zig.SanitizeC,
2828
sanitize_thread: bool,
2929
fuzz: bool,
3030
unwind_tables: std.builtin.UnwindTables,
@@ -92,7 +92,7 @@ pub const CreateOptions = struct {
9292
stack_protector: ?u32 = null,
9393
red_zone: ?bool = null,
9494
unwind_tables: ?std.builtin.UnwindTables = null,
95-
sanitize_c: ?bool = null,
95+
sanitize_c: ?std.zig.SanitizeC = null,
9696
sanitize_thread: ?bool = null,
9797
fuzz: ?bool = null,
9898
structured_cfg: ?bool = null,
@@ -113,6 +113,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
113113
if (options.inherited.fuzz == true) assert(options.global.any_fuzz);
114114
if (options.inherited.single_threaded == false) assert(options.global.any_non_single_threaded);
115115
if (options.inherited.unwind_tables) |uwt| if (uwt != .none) assert(options.global.any_unwind_tables);
116+
if (options.inherited.sanitize_c) |sc| if (sc != .off) assert(options.global.any_sanitize_c != .off);
116117
if (options.inherited.error_tracing == true) assert(options.global.any_error_tracing);
117118

118119
const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target;
@@ -249,10 +250,18 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
249250
.ReleaseFast, .ReleaseSmall => false,
250251
};
251252

252-
const sanitize_c = b: {
253+
const sanitize_c: std.zig.SanitizeC = b: {
253254
if (options.inherited.sanitize_c) |x| break :b x;
254255
if (options.parent) |p| break :b p.sanitize_c;
255-
break :b is_safe_mode;
256+
break :b switch (optimize_mode) {
257+
.Debug => .full,
258+
// It's recommended to use the minimal runtime in production
259+
// environments due to the security implications of the full runtime.
260+
// The minimal runtime doesn't provide much benefit over simply
261+
// trapping, however, so we do that instead.
262+
.ReleaseSafe => .trap,
263+
.ReleaseFast, .ReleaseSmall => .off,
264+
};
256265
};
257266

258267
const stack_check = b: {

src/clang_options_data.zig

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3682,7 +3682,14 @@ flagpd1("fno-sanitize-stats"),
36823682
flagpd1("fno-sanitize-thread-atomics"),
36833683
flagpd1("fno-sanitize-thread-func-entry-exit"),
36843684
flagpd1("fno-sanitize-thread-memory-access"),
3685-
flagpd1("fno-sanitize-trap"),
3685+
.{
3686+
.name = "fno-sanitize-trap",
3687+
.syntax = .flag,
3688+
.zig_equivalent = .no_sanitize_trap,
3689+
.pd1 = true,
3690+
.pd2 = false,
3691+
.psl = false,
3692+
},
36863693
flagpd1("fno-sanitize-undefined-trap-on-error"),
36873694
flagpd1("fno-save-main-program"),
36883695
flagpd1("fno-save-optimization-record"),
@@ -4024,7 +4031,14 @@ flagpd1("fsanitize-stats"),
40244031
flagpd1("fsanitize-thread-atomics"),
40254032
flagpd1("fsanitize-thread-func-entry-exit"),
40264033
flagpd1("fsanitize-thread-memory-access"),
4027-
flagpd1("fsanitize-trap"),
4034+
.{
4035+
.name = "fsanitize-trap",
4036+
.syntax = .flag,
4037+
.zig_equivalent = .sanitize_trap,
4038+
.pd1 = true,
4039+
.pd2 = false,
4040+
.psl = false,
4041+
},
40284042
flagpd1("fsanitize-undefined-trap-on-error"),
40294043
flagpd1("fsave-main-program"),
40304044
flagpd1("fsave-optimization-record"),
@@ -6592,7 +6606,7 @@ joinpd1("fmacro-prefix-map="),
65926606
.{
65936607
.name = "fno-sanitize-trap=",
65946608
.syntax = .comma_joined,
6595-
.zig_equivalent = .other,
6609+
.zig_equivalent = .no_sanitize_trap,
65966610
.pd1 = true,
65976611
.pd2 = false,
65986612
.psl = false,
@@ -6864,7 +6878,7 @@ joinpd1("frecord-marker="),
68646878
.{
68656879
.name = "fsanitize-trap=",
68666880
.syntax = .comma_joined,
6867-
.zig_equivalent = .other,
6881+
.zig_equivalent = .sanitize_trap,
68686882
.pd1 = true,
68696883
.pd2 = false,
68706884
.psl = false,

src/glibc.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1231,7 +1231,7 @@ fn buildSharedLib(
12311231
.strip = strip,
12321232
.stack_check = false,
12331233
.stack_protector = 0,
1234-
.sanitize_c = false,
1234+
.sanitize_c = .off,
12351235
.sanitize_thread = false,
12361236
.red_zone = comp.root_mod.red_zone,
12371237
.omit_frame_pointer = comp.root_mod.omit_frame_pointer,

src/libcxx.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
182182
.strip = strip,
183183
.stack_check = false,
184184
.stack_protector = 0,
185-
.sanitize_c = false,
185+
.sanitize_c = .off,
186186
.sanitize_thread = comp.config.any_sanitize_thread,
187187
.red_zone = comp.root_mod.red_zone,
188188
.omit_frame_pointer = comp.root_mod.omit_frame_pointer,
@@ -396,7 +396,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
396396
.strip = strip,
397397
.stack_check = false,
398398
.stack_protector = 0,
399-
.sanitize_c = false,
399+
.sanitize_c = .off,
400400
.sanitize_thread = comp.config.any_sanitize_thread,
401401
.red_zone = comp.root_mod.red_zone,
402402
.omit_frame_pointer = comp.root_mod.omit_frame_pointer,

src/libtsan.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
9595
.strip = strip,
9696
.stack_check = false,
9797
.stack_protector = 0,
98-
.sanitize_c = false,
98+
.sanitize_c = .off,
9999
.sanitize_thread = false,
100100
.red_zone = comp.root_mod.red_zone,
101101
.omit_frame_pointer = optimize_mode != .Debug and !target.os.tag.isDarwin(),

src/libunwind.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
6464
.red_zone = comp.root_mod.red_zone,
6565
.omit_frame_pointer = comp.root_mod.omit_frame_pointer,
6666
.valgrind = false,
67-
.sanitize_c = false,
67+
.sanitize_c = .off,
6868
.sanitize_thread = false,
6969
// necessary so that libunwind can unwind through its own stack frames
7070
// The old 32-bit x86 variant of SEH doesn't use tables.

0 commit comments

Comments
 (0)