authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-13 14:32:27-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-14 09:25:41+01:00
log017228de89ef2ea4ed229c4eea62317826a909b6
treeee1b68e5f86a35217e0258351e7b9ac6505206c0
parentd2c862e6ff355000f3a04ce8e2531dd4fe01d820

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.

1 files changed, 31 insertions(+), 29 deletions(-)

lib/c/malloc.zig+31-29
......@@ -52,17 +52,37 @@ const Header = packed struct(u64) {
5252 alignment: Alignment,
5353 /// Does not include the extra alignment bytes added.
5454 size: Size,
55 padding: Padding = 0,
55 canary: Canary = magic,
5656
5757 comptime {
5858 assert(@sizeOf(Header) <= alignment_bytes);
5959 }
6060
61 const Size = @Int(.unsigned, @min(64 - @bitSizeOf(Alignment), @bitSizeOf(usize)));
62 const Padding = @Int(.unsigned, 64 - @bitSizeOf(Alignment) - @bitSizeOf(Size));
61 const safety = switch (builtin.mode) {
62 .Debug, .ReleaseSafe => true,
63 .ReleaseFast, .ReleaseSmall => false,
64 };
65 const max_addr_bits = switch (safety) {
66 true => 48, // Ensures space for Canary bits.
67 false => 64,
68 };
69 const Size = @Int(.unsigned, @min(max_addr_bits, 64 - @bitSizeOf(Alignment), @bitSizeOf(usize)));
70 const Canary = @Int(.unsigned, 64 - @bitSizeOf(Alignment) - @bitSizeOf(Size));
71 const magic: Canary = switch (safety) {
72 true => @truncate(@as(u64, 0x76fa65bebb3d7a39)), // statically chosen entropy
73 false => 0,
74 };
75
76 fn get(base: [*]align(alignment_bytes) u8) Header {
77 const header: *Header = @ptrCast(base - @sizeOf(Header));
78 assert(header.canary == magic);
79 return header.*;
80 }
6381
64 fn fromBase(base: [*]align(alignment_bytes) u8) *Header {
65 return @ptrCast(base - @sizeOf(Header));
82 fn set(base: [*]align(alignment_bytes) u8, a: Alignment, size: Size) [*]align(alignment_bytes) u8 {
83 const header: *Header = @ptrCast(base - @sizeOf(Header));
84 header.* = .{ .alignment = a, .size = size };
85 return base;
6686 }
6787};
6888
......@@ -72,12 +92,7 @@ fn malloc(n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
7292 vtable.alloc(no_context, n + alignment_bytes, alignment, no_ra) orelse return nomem(),
7393 );
7494 const base = ptr + alignment_bytes;
75 const header: *Header = .fromBase(base);
76 header.* = .{
77 .alignment = alignment,
78 .size = size,
79 };
80 return base;
95 return Header.set(base, alignment, size);
8196}
8297
8398fn 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
93108 vtable.alloc(no_context, n + max_align_bytes, max_align, no_ra) orelse return null,
94109 );
95110 const base: [*]align(alignment_bytes) u8 = @alignCast(ptr + max_align_bytes);
96 const header: *Header = .fromBase(base);
97 header.* = .{
98 .alignment = max_align,
99 .size = size,
100 };
101 return base;
111 return Header.set(base, max_align, size);
102112}
103113
104114fn 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) ?
115125 }
116126 const old_base = opt_old_base orelse return malloc(n);
117127 const new_size = std.math.cast(Header.Size, n) orelse return nomem();
118 const old_header: *Header = .fromBase(old_base);
119 assert(old_header.padding == 0);
128 const old_header: Header = .get(old_base);
120129 const old_size = old_header.size;
121130 const old_alignment = old_header.alignment;
122131 const old_alignment_bytes = old_alignment.toByteUnits();
......@@ -139,12 +148,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?
139148 vtable.free(no_context, old_slice, old_alignment, no_ra);
140149 break :b new_base;
141150 };
142 const new_header: *Header = .fromBase(new_base);
143 new_header.* = .{
144 .alignment = old_alignment,
145 .size = new_size,
146 };
147 return new_base;
151 return Header.set(new_base, old_alignment, new_size);
148152}
149153
150154fn 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
154158
155159fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
156160 const old_base = opt_old_base orelse return;
157 const old_header: *Header = .fromBase(old_base);
158 assert(old_header.padding == 0);
161 const old_header: Header = .get(old_base);
159162 const old_size = old_header.size;
160163 const old_alignment = old_header.alignment;
161164 const old_alignment_bytes = old_alignment.toByteUnits();
......@@ -166,8 +169,7 @@ fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
166169
167170fn malloc_usable_size(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) usize {
168171 const old_base = opt_old_base orelse return 0;
169 const old_header: *Header = .fromBase(old_base);
170 assert(old_header.padding == 0);
172 const old_header: Header = .get(old_base);
171173 const old_size = old_header.size;
172174 return old_size;
173175}