[clang] [compiler-rt] [UBSAN] add null and alignment checks for aggregates (PR #164548)
VASU SHARMA via llvm-commits
llvm-commits at lists.llvm.org
Tue Oct 28 03:51:11 PDT 2025
https://github.com/vasu-the-sharma updated https://github.com/llvm/llvm-project/pull/164548
>From 856ac3b4110d79e57bfef9fed52c00a989683083 Mon Sep 17 00:00:00 2001
From: Vasu Sharma <vasusharma at Vasus-MacBook-Pro.local>
Date: Wed, 22 Oct 2025 10:04:59 +0530
Subject: [PATCH 1/3] add null and aligment checks for aggregates
---
clang/lib/CodeGen/CGExprAgg.cpp | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index eee397f1f3d19..de6d80a273dbd 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -2249,6 +2249,21 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
bool isVolatile) {
assert(!Ty->isAnyComplexType() && "Shouldn't happen for complex");
+ if (SanOpts.hasOneOf(SanitizerKind::Null | SanitizerKind::Alignment)) {
+ Address SrcAddr = Src.getAddress();
+ Address DestAddr = Dest.getAddress();
+
+ // Check source pointer for null and alignment violations
+ EmitTypeCheck(TCK_Load, SourceLocation(),
+ SrcAddr.emitRawPointer(*this), Ty, SrcAddr.getAlignment(),
+ SanitizerSet());
+
+ // Check destination pointer for null and alignment violations
+ EmitTypeCheck(TCK_Store, SourceLocation(),
+ DestAddr.emitRawPointer(*this), Ty, DestAddr.getAlignment(),
+ SanitizerSet());
+ }
+
Address DestPtr = Dest.getAddress();
Address SrcPtr = Src.getAddress();
>From 4b7ff1ed2976e27b82b0ce660d47749add49f817 Mon Sep 17 00:00:00 2001
From: Vasu Sharma <vasusharma at Vasus-MacBook-Pro.local>
Date: Wed, 22 Oct 2025 16:15:22 +0530
Subject: [PATCH 2/3] Add null and alignment checks for aggregate copy
operation
---
clang/lib/CodeGen/CGExprAgg.cpp | 3 +
.../Misc/Posix/aggregate_null_alignment.cpp | 79 +++++++++++++++++++
2 files changed, 82 insertions(+)
create mode 100644 compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index de6d80a273dbd..2e5456233c711 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -2249,6 +2249,9 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
bool isVolatile) {
assert(!Ty->isAnyComplexType() && "Shouldn't happen for complex");
+ // Sanitizer checks to verify source and destination pointers are
+ // non-null and properly aligned before copying.
+ // Without these checks, undefined behavior from invalid pointers goes undetected.
if (SanOpts.hasOneOf(SanitizerKind::Null | SanitizerKind::Alignment)) {
Address SrcAddr = Src.getAddress();
Address DestAddr = Dest.getAddress();
diff --git a/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp b/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
new file mode 100644
index 0000000000000..de62c3d5a4df7
--- /dev/null
+++ b/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
@@ -0,0 +1,79 @@
+// RUN: %clangxx -fsanitize=alignment,null -O0 %s -o %t && %run %t
+// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_NULL_SRC %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL-SRC
+// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_NULL_DEST %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL-DEST
+// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_MISALIGN_SRC %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALIGN-SRC
+// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_MISALIGN_DEST %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALIGN-DEST
+
+// Tests for null pointer and alignment checks in aggregate copy operations.
+// This validates the sanitizer checks added to EmitAggregateCopy for both
+// source and destination pointers with null and alignment violations.
+
+#include <stdlib.h>
+#include <string.h>
+
+struct alignas(16) AlignedStruct {
+ int a;
+ int b;
+ int c;
+ int d;
+};
+
+struct NormalStruct {
+ int x;
+ int y;
+ int z;
+};
+
+void test_null_src() {
+ AlignedStruct dest;
+ AlignedStruct *src = nullptr;
+ // CHECK-NULL-SRC: runtime error: load of null pointer of type 'AlignedStruct'
+ dest = *src;
+}
+
+void test_null_dest() {
+ AlignedStruct src = {1, 2, 3, 4};
+ AlignedStruct *dest = nullptr;
+ // CHECK-NULL-DEST: runtime error: store to null pointer of type 'AlignedStruct'
+ *dest = src;
+}
+
+void test_misaligned_src() {
+ char buffer[sizeof(AlignedStruct) + 16];
+ // Create a misaligned pointer (not 16-byte aligned)
+ AlignedStruct *src = (AlignedStruct *)(buffer + 1);
+ AlignedStruct dest;
+ // CHECK-ALIGN-SRC: runtime error: load of misaligned address {{0x[0-9a-f]+}} for type 'AlignedStruct', which requires 16 byte alignment
+ dest = *src;
+}
+
+void test_misaligned_dest() {
+ AlignedStruct src = {1, 2, 3, 4};
+ char buffer[sizeof(AlignedStruct) + 16];
+ // Create a misaligned pointer (not 16-byte aligned)
+ AlignedStruct *dest = (AlignedStruct *)(buffer + 1);
+ // CHECK-ALIGN-DEST: runtime error: store to misaligned address {{0x[0-9a-f]+}} for type 'AlignedStruct', which requires 16 byte alignment
+ *dest = src;
+}
+
+void test_normal_copy() {
+ // This should work fine - properly aligned, non-null pointers
+ AlignedStruct src = {1, 2, 3, 4};
+ AlignedStruct dest;
+ dest = src;
+}
+
+int main() {
+#ifdef TEST_NULL_SRC
+ test_null_src();
+#elif defined(TEST_NULL_DEST)
+ test_null_dest();
+#elif defined(TEST_MISALIGN_SRC)
+ test_misaligned_src();
+#elif defined(TEST_MISALIGN_DEST)
+ test_misaligned_dest();
+#else
+ test_normal_copy();
+#endif
+ return 0;
+}
>From 2bfcdf54cf04a300429112f40dcb699ac1ba8403 Mon Sep 17 00:00:00 2001
From: Vasu Sharma <vasusharma at Vasus-MacBook-Pro.local>
Date: Tue, 28 Oct 2025 16:14:33 +0530
Subject: [PATCH 3/3] add codegen test
---
clang/lib/CodeGen/CGExprAgg.cpp | 26 ++--
.../CodeGen/ubsan-aggregate-null-align.cpp | 134 ++++++++++++++++++
.../Misc/Posix/aggregate_null_alignment.cpp | 79 -----------
3 files changed, 146 insertions(+), 93 deletions(-)
create mode 100644 clang/test/CodeGen/ubsan-aggregate-null-align.cpp
delete mode 100644 compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 2e5456233c711..cf215da08fbf1 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -2252,20 +2252,18 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
// Sanitizer checks to verify source and destination pointers are
// non-null and properly aligned before copying.
// Without these checks, undefined behavior from invalid pointers goes undetected.
- if (SanOpts.hasOneOf(SanitizerKind::Null | SanitizerKind::Alignment)) {
- Address SrcAddr = Src.getAddress();
- Address DestAddr = Dest.getAddress();
-
- // Check source pointer for null and alignment violations
- EmitTypeCheck(TCK_Load, SourceLocation(),
- SrcAddr.emitRawPointer(*this), Ty, SrcAddr.getAlignment(),
- SanitizerSet());
-
- // Check destination pointer for null and alignment violations
- EmitTypeCheck(TCK_Store, SourceLocation(),
- DestAddr.emitRawPointer(*this), Ty, DestAddr.getAlignment(),
- SanitizerSet());
- }
+ Address SrcAddr = Src.getAddress();
+ Address DestAddr = Dest.getAddress();
+
+ // Check source pointer for null and alignment violations
+ EmitTypeCheck(TCK_Load, SourceLocation(),
+ SrcAddr.emitRawPointer(*this), Ty, SrcAddr.getAlignment(),
+ SanitizerSet());
+
+ // Check destination pointer for null and alignment violations
+ EmitTypeCheck(TCK_Store, SourceLocation(),
+ DestAddr.emitRawPointer(*this), Ty, DestAddr.getAlignment(),
+ SanitizerSet());
Address DestPtr = Dest.getAddress();
Address SrcPtr = Src.getAddress();
diff --git a/clang/test/CodeGen/ubsan-aggregate-null-align.cpp b/clang/test/CodeGen/ubsan-aggregate-null-align.cpp
new file mode 100644
index 0000000000000..d623ea0d1e701
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-aggregate-null-align.cpp
@@ -0,0 +1,134 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=alignment,null \
+// RUN: -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-UBSAN
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - \
+// RUN: | FileCheck %s --check-prefix=CHECK-NO-UBSAN
+
+// Test that EmitAggregateCopy emits null and alignment checks when sanitizers
+// are enabled for aggregate copy operations with pointers.
+
+struct alignas(16) AlignedStruct {
+ int a;
+ int b;
+ int c;
+ int d;
+};
+
+struct NormalStruct {
+ int x;
+ int y;
+ int z;
+};
+
+// Stack-to-stack copies are optimized away (compiler knows they're valid)
+// CHECK-UBSAN-LABEL: define {{.*}}void @_Z19test_aligned_structv()
+// CHECK-NO-UBSAN-LABEL: define {{.*}}void @_Z19test_aligned_structv()
+void test_aligned_struct() {
+ AlignedStruct src = {1, 2, 3, 4};
+ AlignedStruct dest;
+
+ // CHECK-UBSAN: call void @llvm.memcpy
+ // CHECK-NO-UBSAN: call void @llvm.memcpy
+
+ dest = src;
+}
+
+// CHECK-UBSAN-LABEL: define {{.*}}void @_Z18test_normal_structv()
+// CHECK-NO-UBSAN-LABEL: define {{.*}}void @_Z18test_normal_structv()
+void test_normal_struct() {
+ NormalStruct src = {10, 20, 30};
+ NormalStruct dest;
+
+ // CHECK-UBSAN: call void @llvm.memcpy
+ // CHECK-NO-UBSAN: call void @llvm.memcpy
+
+ dest = src;
+}
+
+// This is the key test - copying through pointers requires runtime checks
+// CHECK-UBSAN-LABEL: define {{.*}}void @_Z19test_pointer_to_ptrP13AlignedStructS0_(
+// CHECK-NO-UBSAN-LABEL: define {{.*}}void @_Z19test_pointer_to_ptrP13AlignedStructS0_(
+void test_pointer_to_ptr(AlignedStruct *src, AlignedStruct *dest) {
+ // CHECK-UBSAN: %[[SRC_LOAD:.*]] = load ptr, ptr %src.addr
+ // CHECK-UBSAN: %[[DEST_LOAD:.*]] = load ptr, ptr %dest.addr
+
+ // Check source pointer is non-null and aligned
+ // CHECK-UBSAN: %[[SRC_NONNULL:.*]] = icmp ne ptr %[[SRC_LOAD]], null
+ // CHECK-UBSAN: %[[SRC_INT:.*]] = ptrtoint ptr %[[SRC_LOAD]] to i64
+ // CHECK-UBSAN: %[[SRC_MASK:.*]] = and i64 %[[SRC_INT]], 15
+ // CHECK-UBSAN: %[[SRC_ALIGNED:.*]] = icmp eq i64 %[[SRC_MASK]], 0
+ // CHECK-UBSAN: %[[SRC_OK:.*]] = and i1 %[[SRC_NONNULL]], %[[SRC_ALIGNED]]
+ // CHECK-UBSAN: br i1 %[[SRC_OK]], label %cont, label %handler.type_mismatch
+
+ // CHECK-UBSAN: handler.type_mismatch:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+ // CHECK-UBSAN: unreachable
+
+ // CHECK-UBSAN: cont:
+ // Check destination pointer is non-null and aligned
+ // CHECK-UBSAN: %[[DEST_NONNULL:.*]] = icmp ne ptr %[[DEST_LOAD]], null
+ // CHECK-UBSAN: %[[DEST_INT:.*]] = ptrtoint ptr %[[DEST_LOAD]] to i64
+ // CHECK-UBSAN: %[[DEST_MASK:.*]] = and i64 %[[DEST_INT]], 15
+ // CHECK-UBSAN: %[[DEST_ALIGNED:.*]] = icmp eq i64 %[[DEST_MASK]], 0
+ // CHECK-UBSAN: %[[DEST_OK:.*]] = and i1 %[[DEST_NONNULL]], %[[DEST_ALIGNED]]
+ // CHECK-UBSAN: br i1 %[[DEST_OK]], label %cont{{.*}}, label %handler.type_mismatch
+
+ // CHECK-UBSAN: handler.type_mismatch{{.*}}:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+ // CHECK-UBSAN: unreachable
+
+ // CHECK-UBSAN: cont{{.*}}:
+ // CHECK-UBSAN: call void @llvm.memcpy
+
+ // Without sanitizers, no checks - just direct memcpy
+ // CHECK-NO-UBSAN-NOT: @__ubsan_handle
+ // CHECK-NO-UBSAN: call void @llvm.memcpy
+
+ *dest = *src;
+}
+
+// Array copies also need checks for non-constant indices
+// CHECK-UBSAN-LABEL: define {{.*}}void @_Z15test_array_copyv()
+// CHECK-NO-UBSAN-LABEL: define {{.*}}void @_Z15test_array_copyv()
+void test_array_copy() {
+ AlignedStruct src[3] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
+ AlignedStruct dest[3];
+
+ // First element - no checks needed (compiler knows it's aligned)
+ // CHECK-UBSAN: %arrayidx = getelementptr inbounds [3 x %struct.AlignedStruct]
+ // CHECK-UBSAN: %arrayidx1 = getelementptr inbounds [3 x %struct.AlignedStruct]
+ // CHECK-UBSAN: call void @llvm.memcpy.p0.p0.i64(ptr align 16 %arrayidx1, ptr align 16 %arrayidx, i64 16, i1 false)
+ dest[0] = src[0];
+
+ // Second element - needs runtime checks
+ // CHECK-UBSAN: %arrayidx{{.*}} = getelementptr inbounds [3 x %struct.AlignedStruct]
+ // CHECK-UBSAN: %arrayidx{{.*}} = getelementptr inbounds [3 x %struct.AlignedStruct]
+ // CHECK-UBSAN: icmp ne ptr %arrayidx{{.*}}, null
+ // CHECK-UBSAN: ptrtoint ptr %arrayidx{{.*}} to i64
+ // CHECK-UBSAN: and i64 %{{.*}}, 15
+ // CHECK-UBSAN: icmp eq i64 %{{.*}}, 0
+ // CHECK-UBSAN: br i1 %{{.*}}, label %cont, label %handler.type_mismatch
+ // CHECK-UBSAN: handler.type_mismatch:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+ dest[1] = src[1];
+
+ // Third element - also needs checks
+ // CHECK-UBSAN: icmp ne ptr %arrayidx{{.*}}, null
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+ dest[2] = src[2];
+
+ // Without sanitizers, no checks
+ // CHECK-NO-UBSAN-NOT: @__ubsan_handle
+}
+
+// Test with normal struct through pointers
+// CHECK-UBSAN-LABEL: define {{.*}}void @_Z23test_normal_struct_ptrsP12NormalStructS0_
+// CHECK-NO-UBSAN-LABEL: define {{.*}}void @_Z23test_normal_struct_ptrsP12NormalStructS0_
+void test_normal_struct_ptrs(NormalStruct *src, NormalStruct *dest) {
+ // Should still check for null even with normal alignment
+ // CHECK-UBSAN: icmp ne ptr %{{.*}}, null
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+
+ // CHECK-NO-UBSAN-NOT: @__ubsan_handle
+
+ *dest = *src;
+}
diff --git a/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp b/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
deleted file mode 100644
index de62c3d5a4df7..0000000000000
--- a/compiler-rt/test/ubsan/TestCases/Misc/Posix/aggregate_null_alignment.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-// RUN: %clangxx -fsanitize=alignment,null -O0 %s -o %t && %run %t
-// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_NULL_SRC %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL-SRC
-// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_NULL_DEST %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL-DEST
-// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_MISALIGN_SRC %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALIGN-SRC
-// RUN: %clangxx -fsanitize=alignment,null -O0 -DTEST_MISALIGN_DEST %s -o %t && not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-ALIGN-DEST
-
-// Tests for null pointer and alignment checks in aggregate copy operations.
-// This validates the sanitizer checks added to EmitAggregateCopy for both
-// source and destination pointers with null and alignment violations.
-
-#include <stdlib.h>
-#include <string.h>
-
-struct alignas(16) AlignedStruct {
- int a;
- int b;
- int c;
- int d;
-};
-
-struct NormalStruct {
- int x;
- int y;
- int z;
-};
-
-void test_null_src() {
- AlignedStruct dest;
- AlignedStruct *src = nullptr;
- // CHECK-NULL-SRC: runtime error: load of null pointer of type 'AlignedStruct'
- dest = *src;
-}
-
-void test_null_dest() {
- AlignedStruct src = {1, 2, 3, 4};
- AlignedStruct *dest = nullptr;
- // CHECK-NULL-DEST: runtime error: store to null pointer of type 'AlignedStruct'
- *dest = src;
-}
-
-void test_misaligned_src() {
- char buffer[sizeof(AlignedStruct) + 16];
- // Create a misaligned pointer (not 16-byte aligned)
- AlignedStruct *src = (AlignedStruct *)(buffer + 1);
- AlignedStruct dest;
- // CHECK-ALIGN-SRC: runtime error: load of misaligned address {{0x[0-9a-f]+}} for type 'AlignedStruct', which requires 16 byte alignment
- dest = *src;
-}
-
-void test_misaligned_dest() {
- AlignedStruct src = {1, 2, 3, 4};
- char buffer[sizeof(AlignedStruct) + 16];
- // Create a misaligned pointer (not 16-byte aligned)
- AlignedStruct *dest = (AlignedStruct *)(buffer + 1);
- // CHECK-ALIGN-DEST: runtime error: store to misaligned address {{0x[0-9a-f]+}} for type 'AlignedStruct', which requires 16 byte alignment
- *dest = src;
-}
-
-void test_normal_copy() {
- // This should work fine - properly aligned, non-null pointers
- AlignedStruct src = {1, 2, 3, 4};
- AlignedStruct dest;
- dest = src;
-}
-
-int main() {
-#ifdef TEST_NULL_SRC
- test_null_src();
-#elif defined(TEST_NULL_DEST)
- test_null_dest();
-#elif defined(TEST_MISALIGN_SRC)
- test_misaligned_src();
-#elif defined(TEST_MISALIGN_DEST)
- test_misaligned_dest();
-#else
- test_normal_copy();
-#endif
- return 0;
-}
More information about the llvm-commits
mailing list