authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-03-24 04:07:41+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-03-25 00:54:44+01:00
log589bcb2544fed9a3454908adba4a2f97f1405e9c
tree9449d3f5c2e3a9bb0f9e0f129851b2530392fd90
parent5861afb1897872c9054ffbc36a221090a2228863

std.heap.ArenaAllocator: Make `resize` and `free` check whether allocation is within current node more rigorously

This prevents the following scenario where an allocation is wrongly assumed to be part of the current head node (`node0`): ``` | node0 - - - - | node1 - - - - - - - - - - - - | | | | | | | | | end_index0 end_index1 | | | | alloc0 alloc1 free(alloc1): load node0 buf0.ptr + end_index0 == alloc1.ptr + alloc1.len ? yes! end_index0 -= alloc1.len | node0 - - - - | node1 - - - - - - - - - - - | | | | | | end_index0 end_index1 | | alloc0 ``` which could move `end_index0` *into* `alloc0` and make it possible for any subsequent calls to `alloc` to overwrite its contents!

1 files changed, 25 insertions(+), 12 deletions(-)

lib/std/heap/ArenaAllocator.zig+25-12
......@@ -319,6 +319,11 @@ fn pushFreeList(arena: *ArenaAllocator, first: *Node, last: *Node) void {
319319 }
320320}
321321
322fn sliceContainsSlice(container: []u8, slice: []u8) bool {
323 return @intFromPtr(slice.ptr) >= @intFromPtr(container.ptr) and
324 @intFromPtr(slice.ptr + slice.len) <= @intFromPtr(container.ptr + container.len);
325}
326
322327fn alignedIndex(buf_ptr: [*]u8, end_index: usize, alignment: Alignment) usize {
323328 // Wrapping arithmetic to avoid overflows since `end_index` isn't bounded by
324329 // `size`. This is always ok since the max alignment in byte units is also
......@@ -543,12 +548,17 @@ fn resize(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, r
543548 assert(new_len > 0);
544549
545550 const node = arena.loadFirstNode().?;
546 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
551 const buf = node.loadBuf();
552
553 if (!sliceContainsSlice(buf, memory)) {
554 // Not within current node.
555 return new_len <= memory.len;
556 }
547557
548558 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
549 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
550 // It's not the most recent allocation, so it cannot be expanded,
551 // but it's fine if they want to make it smaller.
559
560 if (buf.ptr + cur_end_index != memory.ptr + memory.len) {
561 // It's not the most recent allocation, so it cannot be expanded.
552562 return new_len <= memory.len;
553563 }
554564
......@@ -556,15 +566,12 @@ fn resize(ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, r
556566 if (memory.len >= new_len) {
557567 break :new_end_index cur_end_index - (memory.len - new_len);
558568 }
559 const cur_buf_len: usize = node.loadBuf().len;
560 // Saturating arithmetic because `end_index` and `size` are not
561 // guaranteed to be in sync.
562 if (cur_buf_len -| cur_end_index >= new_len - memory.len) {
569 if (buf.len - cur_end_index >= new_len - memory.len) {
563570 break :new_end_index cur_end_index + (new_len - memory.len);
564571 }
565572 return false;
566573 };
567 assert(buf_ptr + new_end_index == memory.ptr + new_len);
574 assert(buf.ptr + new_end_index == memory.ptr + new_len);
568575
569576 return null == @cmpxchgStrong(
570577 usize,
......@@ -589,16 +596,22 @@ fn free(ctx: *anyopaque, memory: []u8, alignment: Alignment, ret_addr: usize) vo
589596 assert(memory.len > 0);
590597
591598 const node = arena.loadFirstNode().?;
592 const buf_ptr = @as([*]u8, @ptrCast(node)) + @sizeOf(Node);
599 const buf = node.loadBuf();
600
601 if (!sliceContainsSlice(buf, memory)) {
602 // Not within current node; we cannot free it.
603 return;
604 }
593605
594606 const cur_end_index = @atomicLoad(usize, &node.end_index, .monotonic);
595 if (buf_ptr + cur_end_index != memory.ptr + memory.len) {
607
608 if (buf.ptr + cur_end_index != memory.ptr + memory.len) {
596609 // Not the most recent allocation; we cannot free it.
597610 return;
598611 }
599612
600613 const new_end_index = cur_end_index - memory.len;
601 assert(buf_ptr + new_end_index == memory.ptr);
614 assert(buf.ptr + new_end_index == memory.ptr);
602615
603616 _ = @cmpxchgStrong(
604617 usize,