-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
memory safety when use-after-free occurs of a local function stack variable #3180
Comments
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
|
See #3180 for a more comprehensive plan to catch this problem. More sophisticated control flow analysis is needed to provide compile errors for returning local variable addresses from a function.
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: 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});
}
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:
|
@andrewrk I think you made a comment on discord about solving the local address problem by doing escape analysis and then allocating memory on the heap for locals that might have escaped (so it would work like in the other cases of use-after-free). The memory allocation scares me for release_safe mode, since this could have a significant cost. Another idea is to use escape analysis, generate compiler errors for locals that might escape, and have a way for the programmer to suppress these compiler errors in specific instances, an 'unsafe' keyword or something similar. While Rust is too complex for my taste, one of the things I think they got right was to provide a clear way to isolate the unsafe code. This approach acknowledges that there will always be unsafe code while isolating it in a way that makes you aware of it as the coder, also making it easier to code review and audit. Maybe this approach could be used for other things as well. For example, the compiler could check that all 'undefined' variables are initialized and generate an error when they can't be verified, with a way for the user to suppress them. I'm sure this is not a new idea and you're more knowledgeable about this than I am (I know nothing about compiler internals). It is probably difficult as well as expensive at compile time. But I wanted to post my reaction and preference. |
Could that be split into its own issue? The core of this one seems solved, judging by the scrollback, unless I misunderstood something. Plus, it'd encourage less milestone juggling. It would also help repel some of this notion from the general public of Zig as a not entirely safe language, like sugary C. |
Has setting locals to |
The following program builds and runs without complaint. Is there the intent to detect UAFs or should one expect memory safety similar to C?
The text was updated successfully, but these errors were encountered: