[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