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) {...@@ -52,17 +52,37 @@ const Header = packed struct(u64) {
52 alignment: Alignment,52 alignment: Alignment,
53 /// Does not include the extra alignment bytes added.53 /// Does not include the extra alignment bytes added.
54 size: Size,54 size: Size,
55 padding: Padding = 0,55 canary: Canary = magic,
5656
57 comptime {57 comptime {
58 assert(@sizeOf(Header) <= alignment_bytes);58 assert(@sizeOf(Header) <= alignment_bytes);
59 }59 }
6060
61 const Size = @Int(.unsigned, @min(64 - @bitSizeOf(Alignment), @bitSizeOf(usize)));61 const safety = switch (builtin.mode) {
62 const Padding = @Int(.unsigned, 64 - @bitSizeOf(Alignment) - @bitSizeOf(Size));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 {82 fn set(base: [*]align(alignment_bytes) u8, a: Alignment, size: Size) [*]align(alignment_bytes) u8 {
65 return @ptrCast(base - @sizeOf(Header));83 const header: *Header = @ptrCast(base - @sizeOf(Header));
84 header.* = .{ .alignment = a, .size = size };
85 return base;
66 }86 }
67};87};
6888
...@@ -72,12 +92,7 @@ fn malloc(n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {...@@ -72,12 +92,7 @@ fn malloc(n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {
72 vtable.alloc(no_context, n + alignment_bytes, alignment, no_ra) orelse return nomem(),92 vtable.alloc(no_context, n + alignment_bytes, alignment, no_ra) orelse return nomem(),
73 );93 );
74 const base = ptr + alignment_bytes;94 const base = ptr + alignment_bytes;
75 const header: *Header = .fromBase(base);95 return Header.set(base, alignment, size);
76 header.* = .{
77 .alignment = alignment,
78 .size = size,
79 };
80 return base;
81}96}
8297
83fn aligned_alloc(alloc_alignment: usize, n: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {98fn 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...@@ -93,12 +108,7 @@ fn aligned_alloc_inner(alloc_alignment: usize, n: usize) ?[*]align(alignment_byt
93 vtable.alloc(no_context, n + max_align_bytes, max_align, no_ra) orelse return null,108 vtable.alloc(no_context, n + max_align_bytes, max_align, no_ra) orelse return null,
94 );109 );
95 const base: [*]align(alignment_bytes) u8 = @alignCast(ptr + max_align_bytes);110 const base: [*]align(alignment_bytes) u8 = @alignCast(ptr + max_align_bytes);
96 const header: *Header = .fromBase(base);111 return Header.set(base, max_align, size);
97 header.* = .{
98 .alignment = max_align,
99 .size = size,
100 };
101 return base;
102}112}
103113
104fn calloc(elems: usize, len: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {114fn 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) ?...@@ -115,8 +125,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?
115 }125 }
116 const old_base = opt_old_base orelse return malloc(n);126 const old_base = opt_old_base orelse return malloc(n);
117 const new_size = std.math.cast(Header.Size, n) orelse return nomem();127 const new_size = std.math.cast(Header.Size, n) orelse return nomem();
118 const old_header: *Header = .fromBase(old_base);128 const old_header: Header = .get(old_base);
119 assert(old_header.padding == 0);
120 const old_size = old_header.size;129 const old_size = old_header.size;
121 const old_alignment = old_header.alignment;130 const old_alignment = old_header.alignment;
122 const old_alignment_bytes = old_alignment.toByteUnits();131 const old_alignment_bytes = old_alignment.toByteUnits();
...@@ -139,12 +148,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?...@@ -139,12 +148,7 @@ fn realloc(opt_old_base: ?[*]align(alignment_bytes) u8, n: usize) callconv(.c) ?
139 vtable.free(no_context, old_slice, old_alignment, no_ra);148 vtable.free(no_context, old_slice, old_alignment, no_ra);
140 break :b new_base;149 break :b new_base;
141 };150 };
142 const new_header: *Header = .fromBase(new_base);151 return Header.set(new_base, old_alignment, new_size);
143 new_header.* = .{
144 .alignment = old_alignment,
145 .size = new_size,
146 };
147 return new_base;
148}152}
149153
150fn reallocarray(opt_base: ?[*]align(alignment_bytes) u8, elems: usize, len: usize) callconv(.c) ?[*]align(alignment_bytes) u8 {154fn 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...@@ -154,8 +158,7 @@ fn reallocarray(opt_base: ?[*]align(alignment_bytes) u8, elems: usize, len: usiz
154158
155fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {159fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
156 const old_base = opt_old_base orelse return;160 const old_base = opt_old_base orelse return;
157 const old_header: *Header = .fromBase(old_base);161 const old_header: Header = .get(old_base);
158 assert(old_header.padding == 0);
159 const old_size = old_header.size;162 const old_size = old_header.size;
160 const old_alignment = old_header.alignment;163 const old_alignment = old_header.alignment;
161 const old_alignment_bytes = old_alignment.toByteUnits();164 const old_alignment_bytes = old_alignment.toByteUnits();
...@@ -166,8 +169,7 @@ fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {...@@ -166,8 +169,7 @@ fn free(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) void {
166169
167fn malloc_usable_size(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) usize {170fn malloc_usable_size(opt_old_base: ?[*]align(alignment_bytes) u8) callconv(.c) usize {
168 const old_base = opt_old_base orelse return 0;171 const old_base = opt_old_base orelse return 0;
169 const old_header: *Header = .fromBase(old_base);172 const old_header: Header = .get(old_base);
170 assert(old_header.padding == 0);
171 const old_size = old_header.size;173 const old_size = old_header.size;
172 return old_size;174 return old_size;
173}175}