The following program builds and runs without complaint. Is there the intent to detect UAFs or should one expect memory safety similar to C?
const std = @import("std");
const P = struct {
id: u32,
};
pub fn main() !void {
const alloc = std.heap.page_allocator;
const k = try alloc.create(P);
k.id = 5;
alloc.destroy(k);
const m = try alloc.create(P);
std.debug.print("value: {}\n", .{k.id});
}
Use-after-free is a research area of the #2301 project. See also #1966. Regardless of safety protections, however, the semantics of memory is that a variable's lifetime ends upon exit of the scope it is declared in. Same as C.
Oh, see also https://github.com/andrewrk/zig-general-purpose-allocator/. It's not quite ready to merge into std, but that's going to be the go-to allocator for debug builds at least. It has use-after-free detection that works until all virtual memory is exhausted, at which point addresses have to be recycled.
I just happened to have this bug. It still applies, albeit the source should be updated to compile
``
...
warn("value: {}\n", .{k.id});
value: 2863311530
Use after free is now solved as far as safety is concerned with heap allocations, if you use the std lib page_allocator or GeneralPurposeAllocator. This is true even if you use it to e.g. back an ArenaAllocator.
What's left for this issue is to make escaped stack allocations safe.
To revisit the original test case here:
$ ./zig build-exe test.zig
$ ./test
Segmentation fault at address 0x7f7aba428000
/home/andy/Downloads/zig/build/test.zig:13:39: 0x22fd51 in main (test)
std.debug.print("value: {}\n", .{k.id});
^
/home/andy/Downloads/zig/lib/std/start.zig:257:37: 0x204c4d in std.start.posixCallMainAndExit (test)
const result = root.main() catch |err| {
^
/home/andy/Downloads/zig/lib/std/start.zig:128:5: 0x20498f in std.start._start (test)
@call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
^
Aborted (core dumped)
Here's similar test case. This one uses GeneralPurposeAllocator and is adversarial by forcing the page to stay mapped:
const std = @import("std");
const P = struct {
id: u32,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = &gpa.allocator;
const k = try alloc.create(P);
const l = try alloc.create(P);
k.id = 5;
alloc.destroy(k);
const m = try alloc.create(P);
std.debug.print("value: {x}\n", .{k.id});
}
$ ./zig build-exe test.zig
$ ./test
value: aaaaaaaa
Here you can see that the freed memory got set to the undefined bit pattern. If you run it with valgrind it will tell you about the branching on undefined:
$ valgrind ./test
==14833== Memcheck, a memory error detector
==14833== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14833== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14833== Command: ./test
==14833==
value: ==14833== Conditional jump or move depends on uninitialised value(s)
==14833== at 0x2458FC: std.fmt.formatIntUnsigned.240 (fmt.zig:988)
==14833== by 0x245581: std.fmt.formatInt.239 (fmt.zig:940)
==14833== by 0x2454CC: std.fmt.formatIntValue.238 (fmt.zig:559)
==14833== by 0x242293: std.fmt.formatValue.215 (fmt.zig:513)
==14833== by 0x242217: std.fmt.formatType.214 (fmt.zig:340)
==14833== by 0x240C22: std.fmt.format.196 (fmt.zig:219)
==14833== by 0x23A54A: std.io.writer.Writer(std.fs.file.File,std.os.WriteError,std.fs.file.File.write).print.169 (writer.zig:33)
==14833== by 0x238772: std.debug.print.167 (debug.zig:67)
==14833== by 0x23065F: main (test.zig:15)
<snip>
Most helpful comment
Use after free is now solved as far as safety is concerned with heap allocations, if you use the std lib page_allocator or GeneralPurposeAllocator. This is true even if you use it to e.g. back an ArenaAllocator.
What's left for this issue is to make escaped stack allocations safe.
To revisit the original test case here:
Here's similar test case. This one uses GeneralPurposeAllocator and is adversarial by forcing the page to stay mapped:
Here you can see that the freed memory got set to the undefined bit pattern. If you run it with valgrind it will tell you about the branching on undefined: