authorgravatar for rganesan@arista.comGanesan Rajagopal <rganesan@arista.com> 2023-04-04 15:41:25+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-04-04 13:11:25+03:00
log49b56f88b92eb0f0e66e7cfa329392c918c1777e
tree69db3a981457c21cac91aa1729f2eb8c419100af
parent771d07268f7ecc9535ad6fbb8448c76581bf5188
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

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 files changed, 82 insertions(+), 0 deletions(-)

lib/std/heap/general_purpose_allocator.zig+82
......@@ -160,6 +160,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
160160 backing_allocator: Allocator = std.heap.page_allocator,
161161 buckets: [small_bucket_count]?*BucketHeader = [1]?*BucketHeader{null} ** small_bucket_count,
162162 large_allocations: LargeAllocTable = .{},
163 small_allocations: if (config.safety) SmallAllocTable else void = if (config.safety) .{} else {},
163164 empty_buckets: if (config.retain_metadata) ?*BucketHeader else void =
164165 if (config.retain_metadata) null else {},
165166
......@@ -194,6 +195,11 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
194195 const small_bucket_count = math.log2(page_size);
195196 const largest_bucket_object_size = 1 << (small_bucket_count - 1);
196197
198 const SmallAlloc = struct {
199 requested_size: usize,
200 log2_ptr_align: u8,
201 };
202
197203 const LargeAlloc = struct {
198204 bytes: []u8,
199205 requested_size: if (config.enable_memory_limit) usize else void,
......@@ -227,6 +233,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
227233 }
228234 };
229235 const LargeAllocTable = std.AutoHashMapUnmanaged(usize, LargeAlloc);
236 const SmallAllocTable = std.AutoHashMapUnmanaged(usize, SmallAlloc);
230237
231238 // Bucket: In memory, in order:
232239 // * BucketHeader
......@@ -430,6 +437,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
430437 self.freeRetainedMetadata();
431438 }
432439 self.large_allocations.deinit(self.backing_allocator);
440 if (config.safety) {
441 self.small_allocations.deinit(self.backing_allocator);
442 }
433443 self.* = undefined;
434444 return leaks;
435445 }
......@@ -706,6 +716,34 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
706716 }
707717
708718 // Definitely an in-use small alloc now.
719 if (config.safety) {
720 const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse
721 @panic("Invalid free");
722 if (old_mem.len != entry.value_ptr.requested_size or log2_old_align != entry.value_ptr.log2_ptr_align) {
723 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
724 var free_stack_trace = StackTrace{
725 .instruction_addresses = &addresses,
726 .index = 0,
727 };
728 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
729 if (old_mem.len != entry.value_ptr.requested_size) {
730 log.err("Allocation size {d} bytes does not match resize size {d}. Allocation: {} Resize: {}", .{
731 entry.value_ptr.requested_size,
732 old_mem.len,
733 bucketStackTrace(bucket, size_class, slot_index, .alloc),
734 free_stack_trace,
735 });
736 }
737 if (log2_old_align != entry.value_ptr.log2_ptr_align) {
738 log.err("Allocation alignment {d} does not match resize alignment {d}. Allocation: {} Resize: {}", .{
739 @as(usize, 1) << @intCast(math.Log2Int(usize), entry.value_ptr.log2_ptr_align),
740 @as(usize, 1) << @intCast(math.Log2Int(usize), log2_old_align),
741 bucketStackTrace(bucket, size_class, slot_index, .alloc),
742 free_stack_trace,
743 });
744 }
745 }
746 }
709747 const prev_req_bytes = self.total_requested_bytes;
710748 if (config.enable_memory_limit) {
711749 const new_req_bytes = prev_req_bytes + new_size - old_mem.len;
......@@ -726,6 +764,10 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
726764 old_mem.len, old_mem.ptr, new_size,
727765 });
728766 }
767 if (config.safety) {
768 const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)).?;
769 entry.value_ptr.requested_size = new_size;
770 }
729771 return true;
730772 }
731773
......@@ -796,6 +838,35 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
796838 }
797839
798840 // Definitely an in-use small alloc now.
841 if (config.safety) {
842 const entry = self.small_allocations.getEntry(@ptrToInt(old_mem.ptr)) orelse
843 @panic("Invalid free");
844 if (old_mem.len != entry.value_ptr.requested_size or log2_old_align != entry.value_ptr.log2_ptr_align) {
845 var addresses: [stack_n]usize = [1]usize{0} ** stack_n;
846 var free_stack_trace = StackTrace{
847 .instruction_addresses = &addresses,
848 .index = 0,
849 };
850 std.debug.captureStackTrace(ret_addr, &free_stack_trace);
851 if (old_mem.len != entry.value_ptr.requested_size) {
852 log.err("Allocation size {d} bytes does not match free size {d}. Allocation: {} Free: {}", .{
853 entry.value_ptr.requested_size,
854 old_mem.len,
855 bucketStackTrace(bucket, size_class, slot_index, .alloc),
856 free_stack_trace,
857 });
858 }
859 if (log2_old_align != entry.value_ptr.log2_ptr_align) {
860 log.err("Allocation alignment {d} does not match free alignment {d}. Allocation: {} Free: {}", .{
861 @as(usize, 1) << @intCast(math.Log2Int(usize), entry.value_ptr.log2_ptr_align),
862 @as(usize, 1) << @intCast(math.Log2Int(usize), log2_old_align),
863 bucketStackTrace(bucket, size_class, slot_index, .alloc),
864 free_stack_trace,
865 });
866 }
867 }
868 }
869
799870 if (config.enable_memory_limit) {
800871 self.total_requested_bytes -= old_mem.len;
801872 }
......@@ -840,6 +911,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
840911 } else {
841912 @memset(old_mem.ptr, undefined, old_mem.len);
842913 }
914 if (config.safety) {
915 assert(self.small_allocations.remove(@ptrToInt(old_mem.ptr)));
916 }
843917 if (config.verbose_log) {
844918 log.info("small free {d} bytes at {*}", .{ old_mem.len, old_mem.ptr });
845919 }
......@@ -903,8 +977,16 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
903977 return slice.ptr;
904978 }
905979
980 if (config.safety) {
981 try self.small_allocations.ensureUnusedCapacity(self.backing_allocator, 1);
982 }
906983 const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size);
907984 const ptr = try self.allocSlot(new_size_class, ret_addr);
985 if (config.safety) {
986 const gop = self.small_allocations.getOrPutAssumeCapacity(@ptrToInt(ptr));
987 gop.value_ptr.requested_size = len;
988 gop.value_ptr.log2_ptr_align = log2_ptr_align;
989 }
908990 if (config.verbose_log) {
909991 log.info("small alloc {d} bytes at {*}", .{ len, ptr });
910992 }