Commit 49b56f88b9

Ganesan Rajagopal <rganesan@arista.com>
2023-04-04 12:11:25
GPA: Catch invalid frees
* GPA: Catch invalid frees Fix #14791: Catch cases where an invalid slice is passed to free(). This was silently ignored before but now logs an error. This change uses a AutoHashMap to keep track of the sizes which seems to be an overkill but seems like the easiest way to catch these errors. * GPA: Add wrong alignment checks to free/resize Implement @Inkryption's suggestion to catch free/resize with the wrong alignment. I also changed the naming to match large allocations.
1 parent 771d072
Changed files (1)
lib/std/heap/general_purpose_allocator.zig
@@ -160,6 +160,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
         backing_allocator: Allocator = std.heap.page_allocator,
         buckets: [small_bucket_count]?*BucketHeader = [1]?*BucketHeader{null} ** small_bucket_count,
         large_allocations: LargeAllocTable = .{},
+        small_allocations: if (config.safety) SmallAllocTable else void = if (config.safety) .{} else {},
         empty_buckets: if (config.retain_metadata) ?*BucketHeader else void =
             if (config.retain_metadata) null else {},
 
@@ -194,6 +195,11 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
         const small_bucket_count = math.log2(page_size);
         const largest_bucket_object_size = 1 << (small_bucket_count - 1);
 
+        const SmallAlloc = struct {
+            requested_size: usize,
+            log2_ptr_align: u8,
+        };
+
         const LargeAlloc = struct {
             bytes: []u8,
             requested_size: if (config.enable_memory_limit) usize else void,
@@ -227,6 +233,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
             }
         };
         const LargeAllocTable = std.AutoHashMapUnmanaged(usize, LargeAlloc);
+        const SmallAllocTable = std.AutoHashMapUnmanaged(usize, SmallAlloc);
 
         // Bucket: In memory, in order:
         // * BucketHeader
@@ -430,6 +437,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
                 self.freeRetainedMetadata();
             }
             self.large_allocations.deinit(self.backing_allocator);
+            if (config.safety) {
+                self.small_allocations.deinit(self.backing_allocator);
+            }
             self.* = undefined;
             return leaks;
         }
@@ -706,6 +716,34 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
             }
 
             // Definitely an in-use small alloc now.
+            if (config.safety) {
+                const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse
+                    @panic("Invalid free");
+                if (old_mem.len != entry.value_ptr.requested_size or log2_old_align != entry.value_ptr.log2_ptr_align) {
+                    var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
+                    var free_stack_trace = StackTrace{
+                        .instruction_addresses = &addresses,
+                        .index = 0,
+                    };
+                    std.debug.captureStackTrace(ret_addr, &free_stack_trace);
+                    if (old_mem.len != entry.value_ptr.requested_size) {
+                        log.err("Allocation size {d} bytes does not match resize size {d}. Allocation: {} Resize: {}", .{
+                            entry.value_ptr.requested_size,
+                            old_mem.len,
+                            bucketStackTrace(bucket, size_class, slot_index, .alloc),
+                            free_stack_trace,
+                        });
+                    }
+                    if (log2_old_align != entry.value_ptr.log2_ptr_align) {
+                        log.err("Allocation alignment {d} does not match resize alignment {d}. Allocation: {} Resize: {}", .{
+                            @as(usize, 1) << @intCast(math.Log2Int(usize), entry.value_ptr.log2_ptr_align),
+                            @as(usize, 1) << @intCast(math.Log2Int(usize), log2_old_align),
+                            bucketStackTrace(bucket, size_class, slot_index, .alloc),
+                            free_stack_trace,
+                        });
+                    }
+                }
+            }
             const prev_req_bytes = self.total_requested_bytes;
             if (config.enable_memory_limit) {
                 const new_req_bytes = prev_req_bytes + new_size - old_mem.len;
@@ -726,6 +764,10 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
                         old_mem.len, old_mem.ptr, new_size,
                     });
                 }
+                if (config.safety) {
+                    const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)).?;
+                    entry.value_ptr.requested_size = new_size;
+                }
                 return true;
             }
 
@@ -796,6 +838,35 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
             }
 
             // Definitely an in-use small alloc now.
+            if (config.safety) {
+                const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse
+                    @panic("Invalid free");
+                if (old_mem.len != entry.value_ptr.requested_size or log2_old_align != entry.value_ptr.log2_ptr_align) {
+                    var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
+                    var free_stack_trace = StackTrace{
+                        .instruction_addresses = &addresses,
+                        .index = 0,
+                    };
+                    std.debug.captureStackTrace(ret_addr, &free_stack_trace);
+                    if (old_mem.len != entry.value_ptr.requested_size) {
+                        log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {} Free: {}", .{
+                            entry.value_ptr.requested_size,
+                            old_mem.len,
+                            bucketStackTrace(bucket, size_class, slot_index, .alloc),
+                            free_stack_trace,
+                        });
+                    }
+                    if (log2_old_align != entry.value_ptr.log2_ptr_align) {
+                        log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {} Free: {}", .{
+                            @as(usize, 1) << @intCast(math.Log2Int(usize), entry.value_ptr.log2_ptr_align),
+                            @as(usize, 1) << @intCast(math.Log2Int(usize), log2_old_align),
+                            bucketStackTrace(bucket, size_class, slot_index, .alloc),
+                            free_stack_trace,
+                        });
+                    }
+                }
+            }
+
             if (config.enable_memory_limit) {
                 self.total_requested_bytes -= old_mem.len;
             }
@@ -840,6 +911,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
             } else {
                 @memset(old_mem.ptr, undefined, old_mem.len);
             }
+            if (config.safety) {
+                assert(self.small_allocations.remove(@ptrToInt(old_mem.ptr)));
+            }
             if (config.verbose_log) {
                 log.info("small free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr });
             }
@@ -903,8 +977,16 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
                 return slice.ptr;
             }
 
+            if (config.safety) {
+                try self.small_allocations.ensureUnusedCapacity(self.backing_allocator, 1);
+            }
             const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
             const ptr = try self.allocSlot(new_size_class, ret_addr);
+            if (config.safety) {
+                const gop = self.small_allocations.getOrPutAssumeCapacity(@ptrToInt(ptr));
+                gop.value_ptr.requested_size = len;
+                gop.value_ptr.log2_ptr_align = log2_ptr_align;
+            }
             if (config.verbose_log) {
                 log.info("small alloc {d} bytes at {*}", .{ len, ptr });
             }