[compiler-rt] [asan] Add size/alignment checks for free_[aligned_]sized (PR #189216)

via llvm-commits llvm-commits at lists.llvm.org
Sun Mar 29 01:03:28 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: PiJoules

<details>
<summary>Changes</summary>

Historically, alignment and size weren't taken into account when freeing allocations since `free` just takes a pointer. With `free_sized` and `free_aligned_sized`, we can do these size and alignment checks in asan now. This adds a new report type specifically for these functions.

The bulk of this PR was generated by gemini but thoroughly reviewed and edited by me to the best of my ability.

---
Full diff: https://github.com/llvm/llvm-project/pull/189216.diff


8 Files Affected:

- (modified) compiler-rt/lib/asan/asan_allocator.cpp (+12-12) 
- (modified) compiler-rt/lib/asan/asan_errors.cpp (+41) 
- (modified) compiler-rt/lib/asan/asan_errors.h (+20) 
- (modified) compiler-rt/lib/asan/asan_flags.inc (+3) 
- (modified) compiler-rt/lib/asan/asan_report.cpp (+8) 
- (modified) compiler-rt/lib/asan/asan_report.h (+2) 
- (added) compiler-rt/test/asan/TestCases/Linux/free_aligned_sized_mismatch.cpp (+47) 
- (added) compiler-rt/test/asan/TestCases/Linux/free_sized_mismatch.cpp (+55) 


``````````diff
diff --git a/compiler-rt/lib/asan/asan_allocator.cpp b/compiler-rt/lib/asan/asan_allocator.cpp
index f9ec69257ee0c..b339ca1b10e31 100644
--- a/compiler-rt/lib/asan/asan_allocator.cpp
+++ b/compiler-rt/lib/asan/asan_allocator.cpp
@@ -757,14 +757,18 @@ struct Allocator {
         ReportAllocTypeMismatch((uptr)ptr, stack, (AllocType)m->alloc_type,
                                 (AllocType)alloc_type);
       }
-    } else {
-      if (flags()->new_delete_type_mismatch &&
-          (alloc_type == FROM_NEW || alloc_type == FROM_NEW_BR) &&
-          ((delete_size && delete_size != m->UsedSize()) ||
-           ComputeUserRequestedAlignmentLog(delete_alignment) !=
-               m->user_requested_alignment_log)) {
-        ReportNewDeleteTypeMismatch(p, delete_size, delete_alignment, stack);
-      }
+    } else if (flags()->new_delete_type_mismatch &&
+               (alloc_type == FROM_NEW || alloc_type == FROM_NEW_BR) &&
+               ((delete_size && delete_size != m->UsedSize()) ||
+                ComputeUserRequestedAlignmentLog(delete_alignment) !=
+                    m->user_requested_alignment_log)) {
+      ReportNewDeleteTypeMismatch(p, delete_size, delete_alignment, stack);
+    } else if (flags()->free_size_mismatch && alloc_type == FROM_MALLOC &&
+               ((delete_size && delete_size != m->UsedSize()) ||
+                (delete_alignment &&
+                 ComputeUserRequestedAlignmentLog(delete_alignment) !=
+                     m->user_requested_alignment_log))) {
+      ReportFreeSizeMismatch(p, delete_size, delete_alignment, stack);
     }
 
     AsanStats &thread_stats = GetCurrentThreadStats();
@@ -1043,10 +1047,6 @@ void asan_free(void *ptr, BufferedStackTrace *stack) {
   instance.Deallocate(ptr, 0, 0, stack, FROM_MALLOC);
 }
 
-// TODO: Both the size and alignment arguments of Allocator::Deallocate
-// are actually unused when the alloc type is FROM_MALLOC. We should look
-// into why this is the case, but for now passing the arguments like so
-// is the correct thing to do.
 void asan_free_sized(void* ptr, uptr size, BufferedStackTrace* stack) {
   instance.Deallocate(ptr, size, /*delete_alignment=*/0, stack, FROM_MALLOC);
 }
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index 99d6bdac3d720..c4100cf7244e3 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -100,6 +100,47 @@ void ErrorNewDeleteTypeMismatch::Print() {
       "ASAN_OPTIONS=new_delete_type_mismatch=0\n");
 }
 
+void ErrorFreeSizeMismatch::Print() {
+  Decorator d;
+  Printf("%s", d.Error());
+  Report("ERROR: AddressSanitizer: %s on %p in thread %s:\n",
+         scariness.GetDescription(), (void*)addr_description.addr,
+         AsanThreadIdAndName(tid).c_str());
+  Printf("%s  object passed to %s has wrong size or alignment:\n", d.Default(),
+         (isFreeAlignedSized() ? "free_aligned_sized" : "free_sized"));
+  if (delete_size != 0) {
+    Printf(
+        "  size of the allocation:   %zd bytes;\n"
+        "  size of the deallocation: %zd bytes.\n",
+        addr_description.chunk_access.chunk_size, delete_size);
+  }
+  const uptr user_alignment =
+      addr_description.chunk_access.user_requested_alignment;
+  if (isFreeAlignedSized() && delete_alignment != user_alignment) {
+    char user_alignment_str[32];
+    char delete_alignment_str[32];
+    internal_snprintf(user_alignment_str, sizeof(user_alignment_str),
+                      "%zd bytes", user_alignment);
+    internal_snprintf(delete_alignment_str, sizeof(delete_alignment_str),
+                      "%zd bytes", delete_alignment);
+    static const char* kDefaultAlignment = "default-aligned";
+    Printf(
+        "  alignment of the allocation:   %s;\n"
+        "  alignment of the deallocation: %s.\n",
+        user_alignment > 0 ? user_alignment_str : kDefaultAlignment,
+        delete_alignment > 0 ? delete_alignment_str : kDefaultAlignment);
+  }
+  CHECK_GT(free_stack->size, 0);
+  scariness.Print();
+  GET_STACK_TRACE_FATAL(free_stack->trace[0], free_stack->top_frame_bp);
+  stack.Print();
+  addr_description.Print();
+  ReportErrorSummary(scariness.GetDescription(), &stack);
+  Report(
+      "HINT: if you don't care about these errors you may set "
+      "ASAN_OPTIONS=free_size_mismatch=0\n");
+}
+
 void ErrorFreeNotMalloced::Print() {
   Decorator d;
   Printf("%s", d.Error());
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index f339b35d2a764..9c6843774f376 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -96,6 +96,25 @@ struct ErrorNewDeleteTypeMismatch : ErrorBase {
   void Print();
 };
 
+struct ErrorFreeSizeMismatch : ErrorBase {
+  const BufferedStackTrace* free_stack;
+  HeapAddressDescription addr_description;
+  uptr delete_size;
+  uptr delete_alignment;
+
+  ErrorFreeSizeMismatch() = default;  // (*)
+  ErrorFreeSizeMismatch(u32 tid, BufferedStackTrace* stack, uptr addr,
+                        uptr delete_size, uptr delete_alignment)
+      : ErrorBase(tid, 10, "free-size-mismatch"),
+        free_stack(stack),
+        delete_size(delete_size),
+        delete_alignment(delete_alignment) {
+    GetHeapAddressInformation(addr, 1, &addr_description);
+  }
+  void Print();
+  bool isFreeAlignedSized() const { return delete_alignment != 0; }
+};
+
 struct ErrorFreeNotMalloced : ErrorBase {
   const BufferedStackTrace *free_stack;
   AddressDescription addr_description;
@@ -422,6 +441,7 @@ struct ErrorGeneric : ErrorBase {
   macro(DeadlySignal)                                      \
   macro(DoubleFree)                                        \
   macro(NewDeleteTypeMismatch)                             \
+  macro(FreeSizeMismatch)                                  \
   macro(FreeNotMalloced)                                   \
   macro(AllocTypeMismatch)                                 \
   macro(MallocUsableSizeNotOwned)                          \
diff --git a/compiler-rt/lib/asan/asan_flags.inc b/compiler-rt/lib/asan/asan_flags.inc
index 32e6d3405533d..a3ba01c8c32a9 100644
--- a/compiler-rt/lib/asan/asan_flags.inc
+++ b/compiler-rt/lib/asan/asan_flags.inc
@@ -130,6 +130,9 @@ ASAN_FLAG(bool, alloc_dealloc_mismatch,
 
 ASAN_FLAG(bool, new_delete_type_mismatch, true,
           "Report errors on mismatch between size of new and delete.")
+ASAN_FLAG(bool, free_size_mismatch, true,
+          "Report errors on mismatch between size of malloc and "
+          "free_sized/free_aligned_sized.")
 ASAN_FLAG(
     bool, strict_init_order, false,
     "If true, assume that dynamic initializers can never access globals from "
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index e049a21e4e16d..e50faf0223212 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -263,6 +263,14 @@ void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
   in_report.ReportError(error);
 }
 
+void ReportFreeSizeMismatch(uptr addr, uptr delete_size, uptr delete_alignment,
+                            BufferedStackTrace* free_stack) {
+  ScopedInErrorReport in_report;
+  ErrorFreeSizeMismatch error(GetCurrentTidOrInvalid(), free_stack, addr,
+                              delete_size, delete_alignment);
+  in_report.ReportError(error);
+}
+
 void ReportFreeNotMalloced(uptr addr, BufferedStackTrace *free_stack) {
   ScopedInErrorReport in_report;
   ErrorFreeNotMalloced error(GetCurrentTidOrInvalid(), free_stack, addr);
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index 3143d83abe390..b181849b10f92 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -53,6 +53,8 @@ void ReportDeadlySignal(const SignalContext &sig);
 void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
                                  uptr delete_alignment,
                                  BufferedStackTrace *free_stack);
+void ReportFreeSizeMismatch(uptr addr, uptr delete_size, uptr delete_alignment,
+                            BufferedStackTrace* free_stack);
 void ReportDoubleFree(uptr addr, BufferedStackTrace *free_stack);
 void ReportFreeNotMalloced(uptr addr, BufferedStackTrace *free_stack);
 void ReportAllocTypeMismatch(uptr addr, BufferedStackTrace *free_stack,
diff --git a/compiler-rt/test/asan/TestCases/Linux/free_aligned_sized_mismatch.cpp b/compiler-rt/test/asan/TestCases/Linux/free_aligned_sized_mismatch.cpp
new file mode 100644
index 0000000000000..781b55712a690
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Linux/free_aligned_sized_mismatch.cpp
@@ -0,0 +1,47 @@
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 128 128 2>&1 | \
+// RUN:  FileCheck %s --check-prefixes=CHECK,CHECK-SIZE
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 64 256 2>&1 | \
+// RUN:  FileCheck %s --check-prefixes=CHECK,CHECK-ALIGN
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 64 128 2>&1 | \
+// RUN:  FileCheck %s --check-prefixes=CHECK,CHECK-ALIGN,CHECK-SIZE
+
+/// Checks disabled here.
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 128 128
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 64 256
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 64 128
+
+/// The test should not fail for these cases since the size and alignment match.
+// RUN: %env_asan_opts=free_size_mismatch=1 %run %t 128 256
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern "C" void free_aligned_sized(void *p, size_t alignment, size_t size);
+
+int main(int argc, char **argv) {
+  if (argc != 3)
+    return 1;
+
+  size_t free_alignment = atoi(argv[1]);
+  size_t free_size = atoi(argv[2]);
+
+  // TODO: free_aligned_sized is only usable with aligned_alloc, but this isn't
+  // checked here, so we can probably add support for checking that later.
+  void *p = aligned_alloc(128, 256);
+  if (!p)
+    return 1;
+
+  free_aligned_sized(p, free_alignment, free_size);
+  // CHECK:       ERROR: AddressSanitizer: free-size-mismatch on
+  // CHECK:         object passed to free_aligned_sized has wrong size or alignment:
+  // CHECK-SIZE:    size of the allocation:   256 bytes;
+  // CHECK-SIZE:    size of the deallocation: 128 bytes.
+  // CHECK-ALIGN:   alignment of the allocation:   128 bytes;
+  // CHECK-ALIGN:   alignment of the deallocation:  64 bytes.
+  // CHECK:         is located 0 bytes inside of 256-byte region
+  // CHECK:         allocated by thread T0 here:
+  // CHECK:         #0{{.*}}aligned_alloc
+
+  return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/Linux/free_sized_mismatch.cpp b/compiler-rt/test/asan/TestCases/Linux/free_sized_mismatch.cpp
new file mode 100644
index 0000000000000..c78fbe52cad0c
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/Linux/free_sized_mismatch.cpp
@@ -0,0 +1,55 @@
+// RUN: %clangxx_asan -O0 %s -o %t
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 1 32 2>&1 | \
+// RUN:   FileCheck %s --check-prefixes=CHECK,CHECK-MALLOC
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 2 32 2>&1 | \
+// RUN:   FileCheck %s --check-prefixes=CHECK,CHECK-CALLOC
+// RUN: %env_asan_opts=free_size_mismatch=1 not %run %t 3 32 2>&1 | \
+// RUN:   FileCheck %s --check-prefixes=CHECK,CHECK-REALLOC
+
+/// Checks disabled here.
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 1 32
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 2 32
+// RUN: %env_asan_opts=free_size_mismatch=0 %run %t 3 32
+
+/// The test should not fail for these cases since the sizes match.
+// RUN: %env_asan_opts=free_size_mismatch=1 %run %t 1 64
+// RUN: %env_asan_opts=free_size_mismatch=1 %run %t 2 64
+// RUN: %env_asan_opts=free_size_mismatch=1 %run %t 3 64
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern "C" void free_sized(void *p, size_t size);
+
+int main(int argc, char **argv) {
+  if (argc != 3)
+    return 1;
+  int alloc_type = atoi(argv[1]);
+
+  void *p = nullptr;
+  if (alloc_type == 1) {
+    p = malloc(64);
+  } else if (alloc_type == 2) {
+    p = calloc(8, 8);
+  } else if (alloc_type == 3) {
+    p = malloc(32);
+    p = realloc(p, 64);
+  }
+
+  if (!p)
+    return 1;
+
+  size_t free_size = atoi(argv[2]);
+  free_sized(p, free_size);
+  // CHECK:        ERROR: AddressSanitizer: free-size-mismatch on
+  // CHECK:          object passed to free_sized has wrong size or alignment:
+  // CHECK:          size of the allocation:   64 bytes;
+  // CHECK:          size of the deallocation: 32 bytes.
+  // CHECK:          is located 0 bytes inside of 64-byte region
+  // CHECK:          allocated by thread T0 here:
+  // CHECK-MALLOC:   #0{{.*}}malloc
+  // CHECK-CALLOC:   #0{{.*}}calloc
+  // CHECK-REALLOC:  #0{{.*}}realloc
+
+  return 0;
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/189216


More information about the llvm-commits mailing list