From 4c4d3cfc3f3260c786edfab48dffa0e86ee2a041 Mon Sep 17 00:00:00 2001 From: Alex Kladov Date: Fri, 15 Aug 2025 18:06:25 +0100 Subject: [PATCH] fix UAF in grow Grow needs to allocate and might fail midway. It tries to handle this using "undo" pattern, and restoring old state on error. But this is exactly what steps into UAF, as, on error, both errdefer and defer are run, and the old data is freed. Instead, use a more robust "reservation" pattern, where we first fallibly resrve all the resources we need, without applying any changes, and than do the actual change once we are sure that cannot fail. --- src/font/Atlas.zig | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index 7b31e2794..3f1caf562 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -287,22 +287,19 @@ pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void assert(size_new >= self.size); if (size_new == self.size) return; + try self.nodes.ensureUnusedCapacity(alloc, 1); + const data_new = try alloc.alloc(u8, size_new * size_new * self.format.depth()); + errdefer comptime unreachable; // End resource reservation phase. + // Preserve our old values so we can copy the old data const data_old = self.data; const size_old = self.size; - // Allocate our new data - self.data = try alloc.alloc(u8, size_new * size_new * self.format.depth()); + self.data = data_new; defer alloc.free(data_old); - errdefer { - alloc.free(self.data); - self.data = data_old; - } - // Add our new rectangle for our added righthand space. We do this - // right away since its the only operation that can fail and we want - // to make error cleanup easier. - try self.nodes.append(alloc, .{ + // Add our new rectangle for our added righthand space. + self.nodes.appendAssumeCapacity(alloc, .{ .x = size_old - 1, .y = 1, .width = size_new - size_old,