Skip to content

initial support for integrated fuzzing #20725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 23, 2024
Merged
62 changes: 62 additions & 0 deletions lib/fuzzer.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const std = @import("std");

export threadlocal var __sancov_lowest_stack: usize = 0;

export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void {
std.debug.print("__sanitizer_cov_8bit_counters_init start={*}, stop={*}\n", .{ start, stop });
}

export fn __sanitizer_cov_pcs_init(pcs_beg: [*]const usize, pcs_end: [*]const usize) void {
std.debug.print("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}\n", .{ pcs_beg, pcs_end });
}

export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_const_cmp2(arg1: u16, arg2: u16) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_cmp2(arg1: u16, arg2: u16) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_const_cmp4(arg1: u32, arg2: u32) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_cmp4(arg1: u32, arg2: u32) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_const_cmp8(arg1: u64, arg2: u64) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64) void {
handleCmp(@returnAddress(), arg1, arg2);
}

export fn __sanitizer_cov_trace_switch(val: u64, cases_ptr: [*]u64) void {
const pc = @returnAddress();
const len = cases_ptr[0];
const val_size_in_bits = cases_ptr[1];
const cases = cases_ptr[2..][0..len];
std.debug.print("0x{x}: switch on value {d} ({d} bits) with {d} cases\n", .{
pc, val, val_size_in_bits, cases.len,
});
}

export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
const pc = @returnAddress();
std.debug.print("0x{x}: indirect call to 0x{x}\n", .{ pc, callee });
}

fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
std.debug.print("0x{x}: comparison of {d} and {d}\n", .{ pc, arg1, arg2 });
}
4 changes: 4 additions & 0 deletions lib/std/Build/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ stack_protector: ?bool,
stack_check: ?bool,
sanitize_c: ?bool,
sanitize_thread: ?bool,
fuzz: ?bool,
code_model: std.builtin.CodeModel,
valgrind: ?bool,
pic: ?bool,
Expand Down Expand Up @@ -186,6 +187,7 @@ pub const CreateOptions = struct {
stack_check: ?bool = null,
sanitize_c: ?bool = null,
sanitize_thread: ?bool = null,
fuzz: ?bool = null,
/// Whether to emit machine code that integrates with Valgrind.
valgrind: ?bool = null,
/// Position Independent Code
Expand Down Expand Up @@ -228,6 +230,7 @@ pub fn init(m: *Module, owner: *std.Build, options: CreateOptions, compile: ?*St
.stack_check = options.stack_check,
.sanitize_c = options.sanitize_c,
.sanitize_thread = options.sanitize_thread,
.fuzz = options.fuzz,
.code_model = options.code_model,
.valgrind = options.valgrind,
.pic = options.pic,
Expand Down Expand Up @@ -642,6 +645,7 @@ pub fn appendZigProcessFlags(
try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing");
try addFlag(zig_args, m.sanitize_c, "-fsanitize-c", "-fno-sanitize-c");
try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread");
try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz");
try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind");
try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC");
try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone");
Expand Down
12 changes: 6 additions & 6 deletions lib/std/mem.zig
Original file line number Diff line number Diff line change
Expand Up @@ -636,18 +636,20 @@ test lessThan {
try testing.expect(lessThan(u8, "", "a"));
}

const backend_can_use_eql_bytes = switch (builtin.zig_backend) {
const eqlBytes_allowed = switch (builtin.zig_backend) {
// The SPIR-V backend does not support the optimized path yet.
.stage2_spirv64 => false,
// The RISC-V does not support vectors.
.stage2_riscv64 => false,
else => true,
// The naive memory comparison implementation is more useful for fuzzers to
// find interesting inputs.
else => !builtin.fuzz,
};

/// Compares two slices and returns whether they are equal.
pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
if (@sizeOf(T) == 0) return true;
if (!@inComptime() and std.meta.hasUniqueRepresentation(T) and backend_can_use_eql_bytes) return eqlBytes(sliceAsBytes(a), sliceAsBytes(b));
if (!@inComptime() and std.meta.hasUniqueRepresentation(T) and eqlBytes_allowed) return eqlBytes(sliceAsBytes(a), sliceAsBytes(b));

if (a.len != b.len) return false;
if (a.len == 0 or a.ptr == b.ptr) return true;
Expand All @@ -660,9 +662,7 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {

/// std.mem.eql heavily optimized for slices of bytes.
fn eqlBytes(a: []const u8, b: []const u8) bool {
if (!backend_can_use_eql_bytes) {
return eql(u8, a, b);
}
comptime assert(eqlBytes_allowed);

if (a.len != b.len) return false;
if (a.len == 0 or a.ptr == b.ptr) return true;
Expand Down
1 change: 1 addition & 0 deletions lib/std/os/linux/start_pie.zig
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fn getDynamicSymbol() [*]elf.Dyn {

pub fn relocate(phdrs: []elf.Phdr) void {
@setRuntimeSafety(false);
@disableInstrumentation();

const dynv = getDynamicSymbol();
// Recover the delta applied by the loader by comparing the effective and
Expand Down
72 changes: 60 additions & 12 deletions lib/std/os/linux/tls.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ const TLSImage = struct {
pub var tls_image: TLSImage = undefined;

pub fn setThreadPointer(addr: usize) void {
@setRuntimeSafety(false);
@disableInstrumentation();
switch (native_arch) {
.x86 => {
var user_desc: linux.user_desc = .{
Expand All @@ -125,7 +127,7 @@ pub fn setThreadPointer(addr: usize) void {
.useable = 1,
},
};
const rc = linux.syscall1(.set_thread_area, @intFromPtr(&user_desc));
const rc = @call(.always_inline, linux.syscall1, .{ .set_thread_area, @intFromPtr(&user_desc) });
assert(rc == 0);

const gdt_entry_number = user_desc.entry_number;
Expand All @@ -138,7 +140,7 @@ pub fn setThreadPointer(addr: usize) void {
);
},
.x86_64 => {
const rc = linux.syscall2(.arch_prctl, linux.ARCH.SET_FS, addr);
const rc = @call(.always_inline, linux.syscall2, .{ .arch_prctl, linux.ARCH.SET_FS, addr });
assert(rc == 0);
},
.aarch64, .aarch64_be => {
Expand All @@ -149,7 +151,7 @@ pub fn setThreadPointer(addr: usize) void {
);
},
.arm, .thumb => {
const rc = linux.syscall1(.set_tls, addr);
const rc = @call(.always_inline, linux.syscall1, .{ .set_tls, addr });
assert(rc == 0);
},
.riscv64 => {
Expand All @@ -160,7 +162,7 @@ pub fn setThreadPointer(addr: usize) void {
);
},
.mips, .mipsel, .mips64, .mips64el => {
const rc = linux.syscall1(.set_thread_area, addr);
const rc = @call(.always_inline, linux.syscall1, .{ .set_thread_area, addr });
assert(rc == 0);
},
.powerpc, .powerpcle => {
Expand Down Expand Up @@ -189,6 +191,9 @@ pub fn setThreadPointer(addr: usize) void {
}

fn initTLS(phdrs: []elf.Phdr) void {
@setRuntimeSafety(false);
@disableInstrumentation();

var tls_phdr: ?*elf.Phdr = null;
var img_base: usize = 0;

Expand Down Expand Up @@ -236,22 +241,22 @@ fn initTLS(phdrs: []elf.Phdr) void {
l += tls_align_factor - delta;
l += @sizeOf(CustomData);
tcb_offset = l;
l += mem.alignForward(usize, tls_tcb_size, tls_align_factor);
l += alignForward(tls_tcb_size, tls_align_factor);
data_offset = l;
l += tls_data_alloc_size;
break :blk l;
},
.VariantII => blk: {
var l: usize = 0;
data_offset = l;
l += mem.alignForward(usize, tls_data_alloc_size, tls_align_factor);
l += alignForward(tls_data_alloc_size, tls_align_factor);
// The thread pointer is aligned to p_align
tcb_offset = l;
l += tls_tcb_size;
// The CustomData structure is right after the TCB with no padding
// in between so it can be easily found
l += @sizeOf(CustomData);
l = mem.alignForward(usize, l, @alignOf(DTV));
l = alignForward(l, @alignOf(DTV));
dtv_offset = l;
l += @sizeOf(DTV);
break :blk l;
Expand All @@ -270,13 +275,28 @@ fn initTLS(phdrs: []elf.Phdr) void {
};
}

/// Inline because TLS is not set up yet.
inline fn alignForward(addr: usize, alignment: usize) usize {
return alignBackward(addr + (alignment - 1), alignment);
}

/// Inline because TLS is not set up yet.
inline fn alignBackward(addr: usize, alignment: usize) usize {
return addr & ~(alignment - 1);
}

/// Inline because TLS is not set up yet.
inline fn alignPtrCast(comptime T: type, ptr: [*]u8) *T {
return @ptrCast(@alignCast(ptr));
}

/// Initializes all the fields of the static TLS area and returns the computed
/// architecture-specific value of the thread-pointer register
///
/// This function is inline because thread local storage is not set up yet.
pub fn prepareTLS(area: []u8) usize {
@setRuntimeSafety(false);
@disableInstrumentation();
// Clear the area we're going to use, just to be safe
@memset(area, 0);
// Prepare the DTV
Expand Down Expand Up @@ -310,6 +330,9 @@ pub fn prepareTLS(area: []u8) usize {
var main_thread_tls_buffer: [0x2100]u8 align(mem.page_size) = undefined;

pub fn initStaticTLS(phdrs: []elf.Phdr) void {
@setRuntimeSafety(false);
@disableInstrumentation();

initTLS(phdrs);

const tls_area = blk: {
Expand All @@ -321,22 +344,47 @@ pub fn initStaticTLS(phdrs: []elf.Phdr) void {
break :blk main_thread_tls_buffer[0..tls_image.alloc_size];
}

const alloc_tls_area = posix.mmap(
const begin_addr = mmap(
null,
tls_image.alloc_size + tls_image.alloc_align - 1,
posix.PROT.READ | posix.PROT.WRITE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
) catch posix.abort();
);
if (@as(isize, @bitCast(begin_addr)) < 0) @trap();
const alloc_tls_area: [*]align(mem.page_size) u8 = @ptrFromInt(begin_addr);

// Make sure the slice is correctly aligned.
const begin_addr = @intFromPtr(alloc_tls_area.ptr);
const begin_aligned_addr = mem.alignForward(usize, begin_addr, tls_image.alloc_align);
const begin_aligned_addr = alignForward(begin_addr, tls_image.alloc_align);
const start = begin_aligned_addr - begin_addr;
break :blk alloc_tls_area[start .. start + tls_image.alloc_size];
break :blk alloc_tls_area[start..][0..tls_image.alloc_size];
};

const tp_value = prepareTLS(tls_area);
setThreadPointer(tp_value);
}

inline fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: linux.MAP, fd: i32, offset: i64) usize {
if (@hasField(linux.SYS, "mmap2")) {
return @call(.always_inline, linux.syscall6, .{
.mmap2,
@intFromPtr(address),
length,
prot,
@as(u32, @bitCast(flags)),
@as(usize, @bitCast(@as(isize, fd))),
@as(usize, @truncate(@as(u64, @bitCast(offset)) / linux.MMAP2_UNIT)),
});
} else {
return @call(.always_inline, linux.syscall6, .{
.mmap,
@intFromPtr(address),
length,
prot,
@as(u32, @bitCast(flags)),
@as(usize, @bitCast(@as(isize, fd))),
@as(u64, @bitCast(offset)),
});
}
}
8 changes: 6 additions & 2 deletions lib/std/start.zig
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ fn wWinMainCRTStartup() callconv(std.os.windows.WINAPI) noreturn {
}

fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn {
// We're not ready to panic until thread local storage is initialized.
@setRuntimeSafety(false);
// Code coverage instrumentation might try to use thread local variables.
@disableInstrumentation();
const argc = argc_argv_ptr[0];
const argv = @as([*][*:0]u8, @ptrCast(argc_argv_ptr + 1));

Expand Down Expand Up @@ -429,9 +433,9 @@ fn posixCallMainAndExit(argc_argv_ptr: [*]usize) callconv(.C) noreturn {
if (comptime native_arch.isARM()) {
if (at_hwcap & std.os.linux.HWCAP.TLS == 0) {
// FIXME: Make __aeabi_read_tp call the kernel helper kuser_get_tls
// For the time being use a simple abort instead of a @panic call to
// For the time being use a simple trap instead of a @panic call to
// keep the binary bloat under control.
std.posix.abort();
@trap();
}
}

Expand Down
14 changes: 8 additions & 6 deletions lib/std/zig/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2817,6 +2817,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As

.extended => switch (gz.astgen.instructions.items(.data)[@intFromEnum(inst)].extended.opcode) {
.breakpoint,
.disable_instrumentation,
.fence,
.set_float_mode,
.set_align_stack,
Expand Down Expand Up @@ -9305,12 +9306,13 @@ fn builtinCall(
},

// zig fmt: off
.This => return rvalue(gz, ri, try gz.addNodeExtended(.this, node), node),
.return_address => return rvalue(gz, ri, try gz.addNodeExtended(.ret_addr, node), node),
.error_return_trace => return rvalue(gz, ri, try gz.addNodeExtended(.error_return_trace, node), node),
.frame => return rvalue(gz, ri, try gz.addNodeExtended(.frame, node), node),
.frame_address => return rvalue(gz, ri, try gz.addNodeExtended(.frame_address, node), node),
.breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node),
.This => return rvalue(gz, ri, try gz.addNodeExtended(.this, node), node),
.return_address => return rvalue(gz, ri, try gz.addNodeExtended(.ret_addr, node), node),
.error_return_trace => return rvalue(gz, ri, try gz.addNodeExtended(.error_return_trace, node), node),
.frame => return rvalue(gz, ri, try gz.addNodeExtended(.frame, node), node),
.frame_address => return rvalue(gz, ri, try gz.addNodeExtended(.frame_address, node), node),
.breakpoint => return rvalue(gz, ri, try gz.addNodeExtended(.breakpoint, node), node),
.disable_instrumentation => return rvalue(gz, ri, try gz.addNodeExtended(.disable_instrumentation, node), node),

.type_info => return simpleUnOpType(gz, scope, ri, node, params[0], .type_info),
.size_of => return simpleUnOpType(gz, scope, ri, node, params[0], .size_of),
Expand Down
1 change: 1 addition & 0 deletions lib/std/zig/AstRlAnnotate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
.error_return_trace,
.frame,
.breakpoint,
.disable_instrumentation,
.in_comptime,
.panic,
.trap,
Expand Down
9 changes: 9 additions & 0 deletions lib/std/zig/BuiltinFn.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub const Tag = enum {
int_from_bool,
bit_size_of,
breakpoint,
disable_instrumentation,
mul_add,
byte_swap,
bit_reverse,
Expand Down Expand Up @@ -263,6 +264,14 @@ pub const list = list: {
.illegal_outside_function = true,
},
},
.{
"@disableInstrumentation",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this built-in to the langref?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it makes sense to leave out of the langref but include in the spec

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this built-in to the langref?

Yes of course

i think it makes sense to leave out of the langref but include in the spec

This not Meghan's Opinion Tracker. Provide technical arguments, or keep your opinion to yourself.

.{
.tag = .disable_instrumentation,
.param_count = 0,
.illegal_outside_function = true,
},
},
.{
"@mulAdd",
.{
Expand Down
Loading
Loading