Skip to content
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

Sandboxing #256

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Added
- REPL (https://github.com/buzz-language/buzz/issues/17) available by running buzz without any argument
- Function argument names and object property names can be ommitted if the provided value is a named variable with the same name (https://github.com/buzz-language/buzz/issues/204)
- Sandboxing build options `memory_limit` and `cycle_limit` (https://github.com/buzz-language/buzz/issues/182)

## Changed
- Map type notation has changed from `{K, V}` to `{K: V}`. Similarly map expression with specified typed went from `{<K, V>, ...}` to `{<K: V>, ...}` (https://github.com/buzz-language/buzz/issues/253)
Expand Down
14 changes: 14 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const BuzzGCOptions = struct {
initial_gc: usize,
next_gc_ratio: usize,
next_full_gc_ratio: usize,
memory_limit: ?usize,

pub fn step(self: BuzzGCOptions, options: *Build.Step.Options) void {
options.addOption(@TypeOf(self.debug), "gc_debug", self.debug);
Expand All @@ -51,6 +52,7 @@ const BuzzGCOptions = struct {
options.addOption(@TypeOf(self.initial_gc), "initial_gc", self.initial_gc);
options.addOption(@TypeOf(self.next_gc_ratio), "next_gc_ratio", self.next_gc_ratio);
options.addOption(@TypeOf(self.next_full_gc_ratio), "next_full_gc_ratio", self.next_full_gc_ratio);
options.addOption(@TypeOf(self.memory_limit), "memory_limit", self.memory_limit);
}
};

Expand All @@ -62,12 +64,14 @@ const BuzzBuildOptions = struct {
gc: BuzzGCOptions,
jit: BuzzJITOptions,
target: Build.ResolvedTarget,
cycle_limit: ?u128,

pub fn step(self: @This(), b: *Build) *Build.Module {
var options = b.addOptions();
options.addOption(@TypeOf(self.version), "version", self.version);
options.addOption(@TypeOf(self.sha), "sha", self.sha);
options.addOption(@TypeOf(self.mimalloc), "mimalloc", self.mimalloc);
options.addOption(@TypeOf(self.cycle_limit), "cycle_limit", self.cycle_limit);

self.debug.step(options);
self.gc.step(options);
Expand Down Expand Up @@ -138,6 +142,11 @@ pub fn build(b: *Build) !void {
}).stdout,
"\n \t",
),
.cycle_limit = b.option(
usize,
"cycle_limit",
"Amount of bytecode (x 1000) the script is allowed to run (WARNING: this disables JIT compilation)",
) orelse null,
.mimalloc = b.option(
bool,
"mimalloc",
Expand Down Expand Up @@ -211,6 +220,11 @@ pub fn build(b: *Build) !void {
"next_full_gc_ratio",
"Ratio applied to get the next full GC threshold",
) orelse 4,
.memory_limit = b.option(
usize,
"memory_limit",
"Memory limit in bytes",
) orelse null,
},
.jit = .{
.debug = b.option(
Expand Down
1 change: 1 addition & 0 deletions src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub const Error = error{
CantCompile,
UnwrappedNull,
OutOfBound,
ReachedMaximumMemoryUsage,
} || std.mem.Allocator.Error || std.fmt.BufPrintError;

pub const Frame = struct {
Expand Down
7 changes: 6 additions & 1 deletion src/Jit.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ const ZigType = @import("zigtypes.zig").Type;
const ExternApi = @import("jit_extern_api.zig").ExternApi;
const api = @import("lib/buzz_api.zig");

pub const Error = error{ CantCompile, UnwrappedNull, OutOfBound } || std.mem.Allocator.Error || std.fmt.BufPrintError;
pub const Error = error{
CantCompile,
UnwrappedNull,
OutOfBound,
ReachedMaximumMemoryUsage,
} || std.mem.Allocator.Error || std.fmt.BufPrintError;

const OptJump = struct {
current_insn: std.ArrayList(m.MIR_insn_t),
Expand Down
7 changes: 6 additions & 1 deletion src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ pub const Error = error{
CantCompile,
UnwrappedNull,
OutOfBound,
ReachedMaximumMemoryUsage,
} || std.mem.Allocator.Error || std.fmt.BufPrintError || CompileError;

const ParseFn = *const fn (*Self, bool) Error!Ast.Node.Index;
Expand Down Expand Up @@ -708,6 +709,10 @@ pub fn parse(self: *Self, source: []const u8, file_name: []const u8) !?Ast {
}
} else {
if (self.declaration() catch |err| {
if (err == error.ReachedMaximumMemoryUsage) {
return err;
}

if (BuildOptions.debug) {
std.debug.print("Parsing failed with error {}\n", .{err});
}
Expand Down Expand Up @@ -7076,7 +7081,7 @@ fn importStatement(self: *Self) Error!Ast.Node.Index {
}

fn zdefStatement(self: *Self) Error!Ast.Node.Index {
if (!BuildOptions.jit) {
if (!BuildOptions.jit and BuildOptions.cycle_limit == null) {
self.reportError(.zdef, "zdef can't be used, this instance of buzz was built with JIT compiler disabled");
}

Expand Down
16 changes: 9 additions & 7 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor:
};
var imports = std.StringHashMap(Parser.ScriptImport).init(allocator);
var vm = try VM.init(&gc, &import_registry, flavor);
vm.jit = if (BuildOptions.jit)
vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null)
JIT.init(&vm)
else
null;
Expand Down Expand Up @@ -264,9 +264,10 @@ pub fn main() !void {

if (flavor == .Repl) {
repl(allocator) catch |err| {
if (BuildOptions.debug) {
std.debug.print("Failed with error {}\n", .{err});
}
std.io.getStdErr().writer().print(
"Failed with error {s}\n",
.{@errorName(err)},
) catch unreachable;

std.os.exit(1);
};
Expand All @@ -277,9 +278,10 @@ pub fn main() !void {
positionals.items[1..],
flavor,
) catch |err| {
if (BuildOptions.debug) {
std.debug.print("Failed with error {}\n", .{err});
}
std.io.getStdErr().writer().print(
"Failed with error {s}\n",
.{@errorName(err)},
) catch unreachable;

std.os.exit(1);
};
Expand Down
4 changes: 4 additions & 0 deletions src/memory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ pub const GarbageCollector = struct {
try self.collectGarbage();
}

if (BuildOptions.memory_limit != null and self.bytes_allocated > BuildOptions.memory_limit.?) {
return error.ReachedMaximumMemoryUsage;
}

const allocated = try self.allocator.create(T);

if (BuildOptions.gc_debug) {
Expand Down
2 changes: 1 addition & 1 deletion src/obj.zig
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ pub const Obj = struct {
}
}

pub fn typeOf(self: *Self, gc: *GarbageCollector) error{ OutOfMemory, NoSpaceLeft }!*ObjTypeDef {
pub fn typeOf(self: *Self, gc: *GarbageCollector) error{ OutOfMemory, NoSpaceLeft, ReachedMaximumMemoryUsage }!*ObjTypeDef {
return switch (self.obj_type) {
.String => try gc.type_registry.getTypeDef(.{ .def_type = .String }),
.Pattern => try gc.type_registry.getTypeDef(.{ .def_type = .Pattern }),
Expand Down
16 changes: 13 additions & 3 deletions src/repl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn printBanner(out: std.fs.File.Writer, full: bool) void {

if (full) {
out.print(
"Built with Zig {} {s}\nAllocator: {s}\nJIT: {s}\n",
"Built with Zig {} {s}\nAllocator: {s}, Memory limit: {} {s}\nJIT: {s}, CPU limit: {} {s}\n",
.{
builtin.zig_version,
switch (builtin.mode) {
Expand All @@ -59,10 +59,20 @@ pub fn printBanner(out: std.fs.File.Writer, full: bool) void {
if (builtin.mode == .Debug)
"gpa"
else if (BuildOptions.mimalloc) "mimalloc" else "c_allocator",
if (BuildOptions.jit)
if (BuildOptions.memory_limit) |ml|
ml
else
0,
if (BuildOptions.memory_limit != null)
"bytes"
else
"(unlimited)",
if (BuildOptions.jit and BuildOptions.cycle_limit == null)
"on"
else
"off",
if (BuildOptions.cycle_limit) |cl| cl else 0,
if (BuildOptions.cycle_limit != null) "cycles" else "(unlimited)",
},
) catch unreachable;
}
Expand All @@ -83,7 +93,7 @@ pub fn repl(allocator: std.mem.Allocator) !void {
};
var imports = std.StringHashMap(Parser.ScriptImport).init(allocator);
var vm = try VM.init(&gc, &import_registry, .Repl);
vm.jit = if (BuildOptions.jit)
vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null)
JIT.init(&vm)
else
null;
Expand Down
25 changes: 22 additions & 3 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,17 @@ pub const Fiber = struct {
pub const VM = struct {
const Self = @This();

var cycles: u128 = 0;

pub const Error = error{
UnwrappedNull,
OutOfBound,
NumberOverflow,
NotInFiber,
FiberOver,
BadNumber,
ReachedMaximumMemoryUsage,
ReachedMaximumCPUUsage,
Custom, // TODO: remove when user can use this set directly in buzz code
} || Allocator.Error || std.fmt.BufPrintError;

Expand Down Expand Up @@ -722,6 +726,21 @@ pub const VM = struct {
self.gc.where = current_frame.closure.function.chunk.lines.items[current_frame.ip - 1];
}

if (BuildOptions.cycle_limit) |limit| {
cycles += 1;

if (cycles > limit * 1000) {
self.throw(
Error.ReachedMaximumCPUUsage,
(self.gc.copyString("Maximum CPU usage reached") catch @panic("Maximum CPU usage reached")).toValue(),
null,
null,
) catch @panic("Maximum CPU usage reached");

return;
}
}

// Tail call
@call(
.always_tail,
Expand Down Expand Up @@ -3927,7 +3946,7 @@ pub const VM = struct {
}

// A JIT compiled function pops its stack on its own
fn callCompiled(self: *Self, closure: ?*ObjClosure, native: NativeFn, arg_count: u8, catch_value: ?Value) !void {
fn callCompiled(self: *Self, closure: *ObjClosure, native: NativeFn, arg_count: u8, catch_value: ?Value) !void {
const was_in_native_call = self.currentFrame() != null and self.currentFrame().?.in_native_call;
if (self.currentFrame()) |frame| {
frame.in_native_call = true;
Expand All @@ -3936,8 +3955,8 @@ pub const VM = struct {

var ctx = NativeCtx{
.vm = self,
.globals = if (closure) |uclosure| uclosure.globals.items.ptr else &[_]Value{},
.upvalues = if (closure) |uclosure| uclosure.upvalues.items.ptr else &[_]*ObjUpValue{},
.globals = closure.globals.items.ptr,
.upvalues = closure.upvalues.items.ptr,
.base = self.current_fiber.stack_top - arg_count - 1,
.stack_top = &self.current_fiber.stack_top,
};
Expand Down
2 changes: 1 addition & 1 deletion tests/bench/001-btree.buzz
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fun btree(int N) > void {
}

fun main([str] args) > void !> any {
int N = if (args.len() > 0) parseInt(args[0]) ?? 0 else 0;
int N = if (args.len() > 0) parseInt(args[0]) ?? 3 else 3;

btree(N);
}
Loading