Skip to content

Commit

Permalink
fix crash found by fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
kristoff-it committed Jul 24, 2024
1 parent e135289 commit 9a7b13a
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 57 deletions.
6 changes: 4 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/fuzz/afl.zig"),
// .target = b.resolveTargetQuery(.{ .cpu_model = .baseline }),
.target = target,
.optimize = .Debug,
.optimize = .ReleaseSafe,
.single_threaded = true,
});

afl_obj.root_module.addImport("scripty", scripty);
afl_obj.root_module.stack_check = false; // not linking with compiler-rt
afl_obj.root_module.link_libc = true; // afl runtime depends on libc
// afl_obj.root_module.fuzz = true;

const afl_fuzz = afl.addInstrumentedExe(b, afl_obj);
const afl_fuzz = afl.addInstrumentedExe(b, target, optimize, afl_obj);
fuzz.dependOn(&b.addInstallBinFile(afl_fuzz, "scriptyfuzz-afl").step);
// fuzz.dependOn(&b.addInstallArtifact(afl_fuzz, .{}).step);
}
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
.version = "0.1.0",
.dependencies = .{
.@"zig-afl-kit" = .{
.url = "git+https://github.com/kristoff-it/zig-afl-kit#bb540a464806b6d0aa7aa068a7e1ef6af4890216",
.hash = "12208af6d678bc0945cf3ca77e95e921ac14b7db698889671debfb552925f55ec8b4",
.url = "git+https://github.com/kristoff-it/zig-afl-kit#40acb13fc69c155996e34ea06fb651f9389ec2cd",
.hash = "1220f2d8402bb7bbc4786b9c0aad73910929ea209cbd3b063842371d68abfed33c1e",
},
},
.paths = .{
Expand Down
100 changes: 50 additions & 50 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,102 +38,102 @@ pub const Node = struct {
};
};

pub fn next(self: *Parser, code: []const u8) ?Node {
pub fn next(p: *Parser, code: []const u8) ?Node {
var path: Node = .{
.tag = .path,
.loc = undefined,
};

var path_segments: u32 = 0;

while (self.it.next(code)) |tok| switch (self.state) {
while (p.it.next(code)) |tok| switch (p.state) {
.syntax => unreachable,
.start => switch (tok.tag) {
.dollar => {
self.state = .global;
p.state = .global;
path.loc = tok.loc;
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
.global => switch (tok.tag) {
.identifier => {
self.state = .extend_path;
p.state = .extend_path;
path_segments = 1;
path.loc.end = tok.loc.end;
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
.extend_path => switch (tok.tag) {
.dot => {
const id_tok = self.it.next(code);
const id_tok = p.it.next(code);
if (id_tok == null or id_tok.?.tag != .identifier) {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}

if (path_segments == 0) {
path.loc = id_tok.?.loc;
} else {
self.last_path_end = path.loc.end;
p.last_path_end = path.loc.end;
path.loc.end = id_tok.?.loc.end;
}

path_segments += 1;
},
.lparen => {
if (path_segments == 0) {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}

// roll back to get a a lparen
self.it.idx -= 1;
self.state = .call_begin;
p.it.idx -= 1;
p.state = .call_begin;
if (path_segments > 1) {
path.loc.end = self.last_path_end;
path.loc.end = p.last_path_end;
return path;
} else {
// self.last_path_end = path.loc.start - 1; // TODO: check tha this is correct
}
},
.rparen => {
self.state = .call_end;
p.state = .call_end;
// roll back to get a rparen token next
self.it.idx -= 1;
p.it.idx -= 1;
if (path_segments == 0) {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}
return path;
},
.comma => {
self.state = .call_arg;
if (path_segments == 0) {
self.state = .syntax;
p.state = .call_arg;
if (p.call_depth == 0) {
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}
return path;
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
.call_begin => {
self.call_depth += 1;
p.call_depth += 1;
switch (tok.tag) {
.lparen => {
self.state = .call_arg;
p.state = .call_arg;
return .{
.tag = .call,
.loc = .{
.start = self.last_path_end + 1,
.start = p.last_path_end + 1,
.end = tok.loc.start,
},
};
Expand All @@ -143,89 +143,89 @@ pub fn next(self: *Parser, code: []const u8) ?Node {
},
.call_arg => switch (tok.tag) {
.dollar => {
self.state = .global;
p.state = .global;
path.loc = tok.loc;
},

.rparen => {
// rollback to get a rparen next
self.it.idx -= 1;
self.state = .call_end;
p.it.idx -= 1;
p.state = .call_end;
},
.identifier => {
self.state = .extend_call;
p.state = .extend_call;
const src = tok.loc.src(code);
if (std.mem.eql(u8, "true", src)) {
return .{ .tag = .true, .loc = tok.loc };
} else if (std.mem.eql(u8, "false", src)) {
return .{ .tag = .false, .loc = tok.loc };
} else {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}
},
.string => {
self.state = .extend_call;
p.state = .extend_call;
return .{ .tag = .string, .loc = tok.loc };
},
.number => {
self.state = .extend_call;
p.state = .extend_call;
return .{ .tag = .number, .loc = tok.loc };
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
.extend_call => switch (tok.tag) {
.comma => self.state = .call_arg,
.comma => p.state = .call_arg,
.rparen => {
// rewind to get a .rparen next call
self.it.idx -= 1;
self.state = .call_end;
p.it.idx -= 1;
p.state = .call_end;
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
.call_end => {
if (self.call_depth == 0) {
self.state = .syntax;
if (p.call_depth == 0) {
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
}
self.call_depth -= 1;
self.state = .after_call;
p.call_depth -= 1;
p.state = .after_call;
return .{ .tag = .apply, .loc = tok.loc };
},
.after_call => switch (tok.tag) {
.dot => {
// rewind to get a .dot next
self.it.idx -= 1;
self.last_path_end = tok.loc.start;
self.state = .extend_path;
p.it.idx -= 1;
p.last_path_end = tok.loc.start;
p.state = .extend_path;
},
.comma => {
self.state = .call_arg;
p.state = .call_arg;
},
.rparen => {
// rewind to get a .rparen next
self.it.idx -= 1;
self.state = .call_end;
p.it.idx -= 1;
p.state = .call_end;
},
else => {
self.state = .syntax;
p.state = .syntax;
return .{ .tag = .syntax_error, .loc = tok.loc };
},
},
};

const not_good_state = (self.state != .after_call and
self.state != .extend_path);
const not_good_state = (p.state != .after_call and
p.state != .extend_path);

const code_len: u32 = @intCast(code.len);
if (self.call_depth > 0 or not_good_state) {
self.state = .syntax;
if (p.call_depth > 0 or not_good_state) {
p.state = .syntax;
return .{
.tag = .syntax_error,
.loc = .{ .start = code_len - 1, .end = code_len },
Expand Down
2 changes: 2 additions & 0 deletions src/fuzz.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub fn main() !void {
test "afl++ fuzz cases" {
const cases: []const []const u8 = &.{
@embedFile("fuzz/cases/1-1.txt"),
@embedFile("fuzz/cases/2-1.txt"),
};

var arena_impl = std.heap.ArenaAllocator.init(std.testing.allocator);
Expand All @@ -50,6 +51,7 @@ test "afl++ fuzz cases" {
for (cases) |c| {
var t = ctx;
var vm: Interpreter = .{};
std.debug.print("\n\ncase: '{s}'\n", .{c});
const result = try vm.run(arena, &t, c, .{});
std.debug.print("Result:\n{any}\n", .{result});
}
Expand Down
1 change: 0 additions & 1 deletion src/fuzz/1-1.txt

This file was deleted.

1 change: 1 addition & 0 deletions src/fuzz/cases/2-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$page,$page
11 changes: 9 additions & 2 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ pub fn VM(
.loc = node.loc,
}),
.call => {
// std.debug.assert(vm.stack.len > 0);
if (vm.stack.len == 0) {
vm.reset();
return .{
.loc = node.loc,
.value = .{ .err = "top-level builtin calls are not allowed" },
};
}
std.debug.assert(src[node.loc.end] == '(');
try vm.stack.append(gpa, .{
.loc = node.loc,
Expand Down Expand Up @@ -181,8 +189,7 @@ pub fn VM(

const fn_name = src[call_loc.start..call_loc.end];
const args = stack_values[call_idx + 1 ..];
// TODO: this is actually a parsing error
// std.debug.assert(call_idx > 0);
// // TODO: this is actually a parsing error
if (call_idx == 0) {
vm.reset();
return .{
Expand Down

0 comments on commit 9a7b13a

Please sign in to comment.