commit 017228de89ef2ea4ed229c4eea62317826a909b6 (tree)
parent d2c862e6ff355000f3a04ce8e2531dd4fe01d820
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 13 Feb 2026 14:32:27 -0800
libc malloc: introduce a canary
Instead of padding, use static entropy to detect corrupted header.
* 64-bit, safe modes: 10 canary bits
* 64-bit, unsafe modes: 0 canary bits
* 32-bit: 27 canary bits
A further enhancement, not done here, would be to upgrade from canary to
parity.
Diffstat:
| M | lib/c/malloc.zig | | | 60 | +++++++++++++++++++++++++++++++----------------------------- |
1 file changed, 31 insertions(+), 29 deletions(-)
diff --git a/lib/c/malloc.zig b/lib/c/malloc.zig
@@ -52,17 +52,37 @@ const Header = packed struct(u64) {
alignment: Alignment,
/// Does not include the extra alignment bytes added.
size: Size,
- padding: Padding = 0,
+ canary: Canary = magic,
comptime {
assert(@sizeOf(Header) <= alignment_bytes);
}
- const Size = @Int(.unsigned, @min(64 - @bitSizeOf(Alignment), @bitSizeOf(usize)));
- const Padding = @Int(.unsigned, 64 - @bitSizeOf(Alignment) - @bitSizeOf(Size));
+ const safety = switch (builtin.mode) {
+ .Debug, .ReleaseSafe => true,
+ .ReleaseFast, .ReleaseSmall => false,
+ };
+ const max_addr_bits = switch (safety) {
+ true => 48, // Ensures space for Canary bits.
+ false => 64,
+ };
+ const Size = @Int(.unsigned, @min(max_addr_bits, 64 - @bitSizeOf(Alignment), @bitSizeOf(usize)));
+ const Canary = @Int(.unsigned, 64 - @bitSizeOf(Alignment) - @bitSizeOf(Size));
+ const magic: Canary = switch (safety) {
+ true => @truncate(@as(u64, 0x76fa65bebb3d7a39)), // statically chosen entropy
+ false => 0,
+ };
+
+ fn get(base: [*]align(alignment_bytes) u8) Header {
+ const header: *Header = @ptrCast(base - @sizeOf(Header));
+ assert(header.canary == magic);
+ return header.*;
+ }
- fn fromBase(base: [*]align(alignment_bytes) u8) *Header {
- return @ptrCast(base - @sizeOf(Header));
+ fn set(base: [*]align(alignment_bytes) u8, a: Alignment, size: Size) [*]align(alignment_bytes) u8 {
+ const header: *Header = @ptrCast(base - @sizeOf(Header));
+ header.* = .{ .alignment = a, .size = size };
+ return base;
}
};
@@ -72,12 +92,7 @@ fn malloc(n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
vtable.alloc(no_context, n + alignment_bytes, alignment, no_ra) orelse return nomem(),
);
const base = ptr + alignment_bytes;
- const header: *Header = .fromBase(base);
- header.* = .{
- .alignment = alignment,
- .size = size,
- };
- return base;
+ return Header.set(base, alignment, size);
}
fn aligned_alloc(alloc_alignment: usize, n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
@@ -93,12 +108,7 @@ fn aligned_alloc_inner(alloc_alignment: usize, n: usize) ?[*]align(alignment_byt
vtable.alloc(no_context, n + max_align_bytes, max_align, no_ra) orelse return null,
);
const base: [*]align(alignment_bytes) u8 = @alignCast(ptr + max_align_bytes);
- const header: *Header = .fromBase(base);
- header.* = .{
- .alignment = max_align,
- .size = size,
- };
- return base;
+ return Header.set(base, max_align, size);
}
fn calloc(elems: usize, len: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
@@ -115,8 +125,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?
}
const old_base = opt_old_base orelse return malloc(n);
const new_size = std.math.cast(Header.Size, n) orelse return nomem();
- const old_header: *Header = .fromBase(old_base);
- assert(old_header.padding == 0);
+ const old_header: Header = .get(old_base);
const old_size = old_header.size;
const old_alignment = old_header.alignment;
const old_alignment_bytes = old_alignment.toByteUnits();
@@ -139,12 +148,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?
vtable.free(no_context, old_slice, old_alignment, no_ra);
break :b new_base;
};
- const new_header: *Header = .fromBase(new_base);
- new_header.* = .{
- .alignment = old_alignment,
- .size = new_size,
- };
- return new_base;
+ return Header.set(new_base, old_alignment, new_size);
}
fn reallocarray(opt_base: ?[*]align(alignment_bytes) u8, elems: usize, len: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
@@ -154,8 +158,7 @@ fn reallocarray(opt_base: ?[*]align(alignment_bytes) u8, elems: usize, len: usiz
fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
const old_base = opt_old_base orelse return;
- const old_header: *Header = .fromBase(old_base);
- assert(old_header.padding == 0);
+ const old_header: Header = .get(old_base);
const old_size = old_header.size;
const old_alignment = old_header.alignment;
const old_alignment_bytes = old_alignment.toByteUnits();
@@ -166,8 +169,7 @@ fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
fn malloc_usable_size(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) usize {
const old_base = opt_old_base orelse return 0;
- const old_header: *Header = .fromBase(old_base);
- assert(old_header.padding == 0);
+ const old_header: Header = .get(old_base);
const old_size = old_header.size;
return old_size;
}