diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2024-07-31 22:54:05 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2024-08-07 00:48:32 -0700 |
| commit | e0ffac4e3c2271a617616760f3084f5f01fb0785 (patch) | |
| tree | 5769c9280878ebb3cc5f76345cf00050012e8198 /lib/fuzzer | |
| parent | ffc050e0557c5d951cd293ca365ed0cd3cdf83db (diff) | |
| download | zig-e0ffac4e3c2271a617616760f3084f5f01fb0785.tar.gz zig-e0ffac4e3c2271a617616760f3084f5f01fb0785.zip | |
introduce a web interface for fuzzing
* new .zig-cache subdirectory: 'v'
- stores coverage information with filename of hash of PCs that want
coverage. This hash is a hex encoding of the 64-bit coverage ID.
* build runner
* fixed bug in file system inputs when a compile step has an
overridden zig_lib_dir field set.
* set some std lib options optimized for the build runner
- no side channel mitigations
- no Transport Layer Security
- no crypto fork safety
* add a --port CLI arg for choosing the port the fuzzing web interface
listens on. it defaults to choosing a random open port.
* introduce a web server, and serve a basic single page application
- shares wasm code with autodocs
- assets are created live on request, for convenient development
experience. main.wasm is properly cached if nothing changes.
- sources.tar comes from file system inputs (introduced with the
`--watch` feature)
* receives coverage ID from test runner and sends it on a thread-safe
queue to the WebServer.
* test runner
- takes a zig cache directory argument now, for where to put coverage
information.
- sends coverage ID to parent process
* fuzzer
- puts its logs (in debug mode) in .zig-cache/tmp/libfuzzer.log
- computes coverage_id and makes it available with
`fuzzer_coverage_id` exported function.
- the memory-mapped coverage file is now namespaced by the coverage id
in hex encoding, in `.zig-cache/v`
* tokenizer
- add a fuzz test to check that several properties are upheld
Diffstat (limited to 'lib/fuzzer')
| -rw-r--r-- | lib/fuzzer/index.html | 76 | ||||
| -rw-r--r-- | lib/fuzzer/main.js | 40 | ||||
| -rw-r--r-- | lib/fuzzer/wasm/main.zig | 99 |
3 files changed, 215 insertions, 0 deletions
diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html new file mode 100644 index 0000000000..c1ef059ad6 --- /dev/null +++ b/lib/fuzzer/index.html @@ -0,0 +1,76 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"> + <title>Zig Documentation</title> + <style type="text/css"> + body { + font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif; + color: #000000; + } + .tok-kw { + color: #333; + font-weight: bold; + } + .tok-str { + color: #d14; + } + .tok-builtin { + color: #0086b3; + } + .tok-comment { + color: #777; + font-style: italic; + } + .tok-fn { + color: #900; + font-weight: bold; + } + .tok-null { + color: #008080; + } + .tok-number { + color: #008080; + } + .tok-type { + color: #458; + font-weight: bold; + } + + @media (prefers-color-scheme: dark) { + body { + background-color: #111; + color: #bbb; + } + .tok-kw { + color: #eee; + } + .tok-str { + color: #2e5; + } + .tok-builtin { + color: #ff894c; + } + .tok-comment { + color: #aa7; + } + .tok-fn { + color: #B1A0F8; + } + .tok-null { + color: #ff8080; + } + .tok-number { + color: #ff8080; + } + .tok-type { + color: #68f; + } + } + </style> + </head> + <body> + <script src="main.js"></script> + </body> +</html> + diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js new file mode 100644 index 0000000000..9b0d4cd8c3 --- /dev/null +++ b/lib/fuzzer/main.js @@ -0,0 +1,40 @@ +(function() { + let wasm_promise = fetch("main.wasm"); + let sources_promise = fetch("sources.tar").then(function(response) { + if (!response.ok) throw new Error("unable to download sources"); + return response.arrayBuffer(); + }); + var wasm_exports = null; + + const text_decoder = new TextDecoder(); + const text_encoder = new TextEncoder(); + + WebAssembly.instantiateStreaming(wasm_promise, { + js: { + log: function(ptr, len) { + const msg = decodeString(ptr, len); + console.log(msg); + }, + panic: function (ptr, len) { + const msg = decodeString(ptr, len); + throw new Error("panic: " + msg); + }, + }, + }).then(function(obj) { + wasm_exports = obj.instance.exports; + window.wasm = obj; // for debugging + + sources_promise.then(function(buffer) { + const js_array = new Uint8Array(buffer); + const ptr = wasm_exports.alloc(js_array.length); + const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); + wasm_array.set(js_array); + wasm_exports.unpack(ptr, js_array.length); + }); + }); + + function decodeString(ptr, len) { + if (len === 0) return ""; + return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); + } +})(); diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig new file mode 100644 index 0000000000..09b9d81068 --- /dev/null +++ b/lib/fuzzer/wasm/main.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const assert = std.debug.assert; + +const Walk = @import("Walk"); + +const gpa = std.heap.wasm_allocator; +const log = std.log; + +const js = struct { + extern "js" fn log(ptr: [*]const u8, len: usize) void; + extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; +}; + +pub const std_options: std.Options = .{ + .logFn = logFn, +}; + +pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn { + _ = st; + _ = addr; + log.err("panic: {s}", .{msg}); + @trap(); +} + +fn logFn( + comptime message_level: log.Level, + comptime scope: @TypeOf(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + const level_txt = comptime message_level.asText(); + const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; + var buf: [500]u8 = undefined; + const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: { + buf[buf.len - 3 ..][0..3].* = "...".*; + break :l &buf; + }; + js.log(line.ptr, line.len); +} + +export fn alloc(n: usize) [*]u8 { + const slice = gpa.alloc(u8, n) catch @panic("OOM"); + return slice.ptr; +} + +export fn unpack(tar_ptr: [*]u8, tar_len: usize) void { + const tar_bytes = tar_ptr[0..tar_len]; + log.debug("received {d} bytes of tar file", .{tar_bytes.len}); + + unpackInner(tar_bytes) catch |err| { + fatal("unable to unpack tar: {s}", .{@errorName(err)}); + }; +} + +fn unpackInner(tar_bytes: []u8) !void { + var fbs = std.io.fixedBufferStream(tar_bytes); + var file_name_buffer: [1024]u8 = undefined; + var link_name_buffer: [1024]u8 = undefined; + var it = std.tar.iterator(fbs.reader(), .{ + .file_name_buffer = &file_name_buffer, + .link_name_buffer = &link_name_buffer, + }); + while (try it.next()) |tar_file| { + switch (tar_file.kind) { + .file => { + if (tar_file.size == 0 and tar_file.name.len == 0) break; + if (std.mem.endsWith(u8, tar_file.name, ".zig")) { + log.debug("found file: '{s}'", .{tar_file.name}); + const file_name = try gpa.dupe(u8, tar_file.name); + if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| { + const pkg_name = file_name[0..pkg_name_end]; + const gop = try Walk.modules.getOrPut(gpa, pkg_name); + const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); + if (!gop.found_existing or + std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or + std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name)) + { + gop.value_ptr.* = file; + } + const file_bytes = tar_bytes[fbs.pos..][0..@intCast(tar_file.size)]; + assert(file == try Walk.add_file(file_name, file_bytes)); + } + } else { + log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name}); + } + }, + else => continue, + } + } +} + +fn fatal(comptime format: []const u8, args: anytype) noreturn { + var buf: [500]u8 = undefined; + const line = std.fmt.bufPrint(&buf, format, args) catch l: { + buf[buf.len - 3 ..][0..3].* = "...".*; + break :l &buf; + }; + js.panic(line.ptr, line.len); +} |
