[llvm-branch-commits] [compiler-rt] [ubsan] Report specific CFI checks in UBSan summaries (PR #203341)
Jakob Koschel via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jun 29 02:16:29 PDT 2026
https://github.com/jakos-sec updated https://github.com/llvm/llvm-project/pull/203341
>From 5244b87126de980a68aa91e260770f195cf11794 Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Thu, 11 Jun 2026 16:52:59 +0000
Subject: [PATCH 1/4] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
=?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.7
---
compiler-rt/lib/ubsan/ubsan_checks.inc | 7 +-
compiler-rt/lib/ubsan/ubsan_handlers.cpp | 12 +-
compiler-rt/lib/ubsan/ubsan_handlers_cxx.cpp | 21 +++-
compiler-rt/test/cfi/lit.cfg.py | 10 +-
compiler-rt/test/cfi/summary.cpp | 111 +++++++++++++++++++
5 files changed, 155 insertions(+), 6 deletions(-)
create mode 100644 compiler-rt/test/cfi/summary.cpp
diff --git a/compiler-rt/lib/ubsan/ubsan_checks.inc b/compiler-rt/lib/ubsan/ubsan_checks.inc
index b1d09a9024e7e..ebcd5df872d69 100644
--- a/compiler-rt/lib/ubsan/ubsan_checks.inc
+++ b/compiler-rt/lib/ubsan/ubsan_checks.inc
@@ -69,4 +69,9 @@ UBSAN_CHECK(InvalidNullArgument, "invalid-null-argument", "nonnull-attribute")
UBSAN_CHECK(InvalidNullArgumentWithNullability, "invalid-null-argument",
"nullability-arg")
UBSAN_CHECK(DynamicTypeMismatch, "dynamic-type-mismatch", "vptr")
-UBSAN_CHECK(CFIBadType, "cfi-bad-type", "cfi")
+UBSAN_CHECK(CFIVCall, "cfi-vcall", "cfi")
+UBSAN_CHECK(CFINVCall, "cfi-nvcall", "cfi")
+UBSAN_CHECK(CFIDerivedCast, "cfi-derived-cast", "cfi")
+UBSAN_CHECK(CFIUnrelatedCast, "cfi-unrelated-cast", "cfi")
+UBSAN_CHECK(CFIICall, "cfi-icall", "cfi")
+UBSAN_CHECK(CFIMFCall, "cfi-mfcall", "cfi")
diff --git a/compiler-rt/lib/ubsan/ubsan_handlers.cpp b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
index 2614d49bf238b..0cd8a437adf4d 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers.cpp
@@ -883,11 +883,19 @@ static SymbolizedStack *removeArtificialFiles(SymbolizedStack *FS) {
static void handleCFIBadIcall(CFICheckFailData *Data, ValueHandle Function,
ReportOptions Opts) {
- if (Data->CheckKind != CFITCK_ICall && Data->CheckKind != CFITCK_NVMFCall)
+ ErrorType ET;
+ switch (Data->CheckKind) {
+ case CFITCK_ICall:
+ ET = ErrorType::CFIICall;
+ break;
+ case CFITCK_NVMFCall:
+ ET = ErrorType::CFIMFCall;
+ break;
+ default:
Die();
+ }
SourceLocation Loc = Data->Loc.acquire();
- ErrorType ET = ErrorType::CFIBadType;
if (ignoreReport(Loc, Opts, ET))
return;
diff --git a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cpp b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cpp
index 206a0bb485a9c..a1ae0347a6a57 100644
--- a/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cpp
+++ b/compiler-rt/lib/ubsan/ubsan_handlers_cxx.cpp
@@ -98,7 +98,26 @@ namespace __ubsan {
void __ubsan_handle_cfi_bad_type(CFICheckFailData *Data, ValueHandle Vtable,
bool ValidVtable, ReportOptions Opts) {
SourceLocation Loc = Data->Loc.acquire();
- ErrorType ET = ErrorType::CFIBadType;
+ ErrorType ET;
+ switch (Data->CheckKind) {
+ case CFITCK_VCall:
+ ET = ErrorType::CFIVCall;
+ break;
+ case CFITCK_NVCall:
+ ET = ErrorType::CFINVCall;
+ break;
+ case CFITCK_DerivedCast:
+ ET = ErrorType::CFIDerivedCast;
+ break;
+ case CFITCK_UnrelatedCast:
+ ET = ErrorType::CFIUnrelatedCast;
+ break;
+ case CFITCK_VMFCall:
+ ET = ErrorType::CFIMFCall;
+ break;
+ default:
+ Die();
+ }
if (ignoreReport(Loc, Opts, ET))
return;
diff --git a/compiler-rt/test/cfi/lit.cfg.py b/compiler-rt/test/cfi/lit.cfg.py
index c879d7f46e6be..7c60d2a464364 100644
--- a/compiler-rt/test/cfi/lit.cfg.py
+++ b/compiler-rt/test/cfi/lit.cfg.py
@@ -63,8 +63,14 @@ def build_invocation(compile_flags):
else:
config.unsupported = True
-if config.default_sanitizer_opts:
- config.environment["UBSAN_OPTIONS"] = ":".join(config.default_sanitizer_opts)
+default_ubsan_opts = list(config.default_sanitizer_opts) if config.default_sanitizer_opts else []
+default_ubsan_opts_str = ":".join(default_ubsan_opts)
+if default_ubsan_opts_str:
+ config.environment["UBSAN_OPTIONS"] = default_ubsan_opts_str
+ default_ubsan_opts_str += ":"
+config.substitutions.append(
+ ("%env_ubsan_opts=", "env UBSAN_OPTIONS=" + default_ubsan_opts_str)
+)
if lit_config.params.get("check_supported", None) and config.unsupported:
raise BaseException("Tests unsupported")
diff --git a/compiler-rt/test/cfi/summary.cpp b/compiler-rt/test/cfi/summary.cpp
new file mode 100644
index 0000000000000..492d25fe7b58c
--- /dev/null
+++ b/compiler-rt/test/cfi/summary.cpp
@@ -0,0 +1,111 @@
+// RUN: %clangxx_cfi_diag -g -o %t %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t vcall 2>&1 | FileCheck --check-prefix=VCALL %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t nvcall 2>&1 | FileCheck --check-prefix=NVCALL %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t ucast 2>&1 | FileCheck --check-prefix=UCAST %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t dcast 2>&1 | FileCheck --check-prefix=DCAST %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t icall 2>&1 | FileCheck --check-prefix=ICALL %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t nvmfcall 2>&1 | FileCheck --check-prefix=NVMFCALL %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t vmfcall 2>&1 | FileCheck --check-prefix=VMFCALL %s
+
+// REQUIRES: cxxabi
+
+#include <stdio.h>
+#include <string.h>
+#include "utils.h"
+
+struct A {
+ virtual void f();
+};
+void A::f() {}
+
+struct B {
+ virtual void f();
+};
+void B::f() {}
+
+struct C : A {
+ virtual void f();
+};
+void C::f() {}
+
+struct D {
+ void f(); // non-virtual
+ virtual void g();
+};
+void D::f() {}
+void D::g() {}
+
+struct S {
+ void f(); // non-virtual
+ virtual void g(); // virtual
+};
+void S::f() {}
+void S::g() {}
+
+struct T {
+ void f();
+ virtual void g();
+};
+void T::f() {}
+void T::g() {}
+
+typedef void (S::*S_void)();
+typedef int (S::*S_int)();
+
+template <typename To, typename From>
+To bitcast(From f) {
+ To t;
+ memcpy(&t, &f, sizeof(f));
+ return t;
+}
+
+void g() {}
+
+int main(int argc, char **argv) {
+ if (argc < 2) return 0;
+
+ create_derivers<B>();
+ create_derivers<D>();
+ create_derivers<T>();
+
+ if (strcmp(argv[1], "vcall") == 0) {
+ A *a = new A;
+ break_optimization(a);
+ // VCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-vcall {{.*}}summary.cpp:[[@LINE+1]]
+ ((B *)a)->f();
+ } else if (strcmp(argv[1], "nvcall") == 0) {
+ A *a = new A;
+ break_optimization(a);
+ // NVCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-nvcall {{.*}}summary.cpp:[[@LINE+1]]
+ ((D *)a)->f();
+ } else if (strcmp(argv[1], "ucast") == 0) {
+ A *a = new A;
+ break_optimization(a);
+ // UCAST: SUMMARY: UndefinedBehaviorSanitizer: cfi-unrelated-cast {{.*}}summary.cpp:[[@LINE+1]]
+ B *b = (B *)a;
+ break_optimization(b);
+ } else if (strcmp(argv[1], "dcast") == 0) {
+ A *a = new A;
+ break_optimization(a);
+ // DCAST: SUMMARY: UndefinedBehaviorSanitizer: cfi-derived-cast {{.*}}summary.cpp:[[@LINE+1]]
+ C *c = (C *)a;
+ break_optimization(c);
+ } else if (strcmp(argv[1], "icall") == 0) {
+ void (*fp)() = g;
+ break_optimization(&fp);
+ // ICALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-icall {{.*}}summary.cpp:[[@LINE+1]]
+ ((void (*)(int))fp)(42);
+ } else if (strcmp(argv[1], "nvmfcall") == 0) {
+ S s;
+ break_optimization(&s);
+ // NVMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary.cpp:[[@LINE+1]]
+ (s.*bitcast<S_void>(&T::f))();
+ } else if (strcmp(argv[1], "vmfcall") == 0) {
+ S s;
+ break_optimization(&s);
+ // VMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary.cpp:[[@LINE+1]]
+ (s.*bitcast<S_int>(&S::g))();
+ }
+
+ return 0;
+}
>From 2e757b8f83c14666f4a681261b2e25aed064a546 Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Thu, 11 Jun 2026 17:06:48 +0000
Subject: [PATCH 2/4] fix formatting issues
Created using spr 1.3.7
---
compiler-rt/test/cfi/lit.cfg.py | 4 +++-
compiler-rt/test/cfi/summary.cpp | 10 +++++-----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/compiler-rt/test/cfi/lit.cfg.py b/compiler-rt/test/cfi/lit.cfg.py
index 7c60d2a464364..532521bd9ec02 100644
--- a/compiler-rt/test/cfi/lit.cfg.py
+++ b/compiler-rt/test/cfi/lit.cfg.py
@@ -63,7 +63,9 @@ def build_invocation(compile_flags):
else:
config.unsupported = True
-default_ubsan_opts = list(config.default_sanitizer_opts) if config.default_sanitizer_opts else []
+default_ubsan_opts = (
+ list(config.default_sanitizer_opts) if config.default_sanitizer_opts else []
+)
default_ubsan_opts_str = ":".join(default_ubsan_opts)
if default_ubsan_opts_str:
config.environment["UBSAN_OPTIONS"] = default_ubsan_opts_str
diff --git a/compiler-rt/test/cfi/summary.cpp b/compiler-rt/test/cfi/summary.cpp
index 492d25fe7b58c..211419d1eafd6 100644
--- a/compiler-rt/test/cfi/summary.cpp
+++ b/compiler-rt/test/cfi/summary.cpp
@@ -9,9 +9,9 @@
// REQUIRES: cxxabi
+#include "utils.h"
#include <stdio.h>
#include <string.h>
-#include "utils.h"
struct A {
virtual void f();
@@ -36,7 +36,7 @@ void D::f() {}
void D::g() {}
struct S {
- void f(); // non-virtual
+ void f(); // non-virtual
virtual void g(); // virtual
};
void S::f() {}
@@ -52,8 +52,7 @@ void T::g() {}
typedef void (S::*S_void)();
typedef int (S::*S_int)();
-template <typename To, typename From>
-To bitcast(From f) {
+template <typename To, typename From> To bitcast(From f) {
To t;
memcpy(&t, &f, sizeof(f));
return t;
@@ -62,7 +61,8 @@ To bitcast(From f) {
void g() {}
int main(int argc, char **argv) {
- if (argc < 2) return 0;
+ if (argc < 2)
+ return 0;
create_derivers<B>();
create_derivers<D>();
>From beb2021c4b136b044e87c5cb136e63f7491d8176 Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Thu, 11 Jun 2026 17:17:52 +0000
Subject: [PATCH 3/4] fix python formatting
Created using spr 1.3.7
---
compiler-rt/test/cfi/lit.cfg.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/compiler-rt/test/cfi/lit.cfg.py b/compiler-rt/test/cfi/lit.cfg.py
index 532521bd9ec02..ad80772e4b936 100644
--- a/compiler-rt/test/cfi/lit.cfg.py
+++ b/compiler-rt/test/cfi/lit.cfg.py
@@ -64,7 +64,7 @@ def build_invocation(compile_flags):
config.unsupported = True
default_ubsan_opts = (
- list(config.default_sanitizer_opts) if config.default_sanitizer_opts else []
+ list(config.default_sanitizer_opts) if config.default_sanitizer_opts else []
)
default_ubsan_opts_str = ":".join(default_ubsan_opts)
if default_ubsan_opts_str:
>From 4f0187951d42ce839a5c62337379b17e441336a2 Mon Sep 17 00:00:00 2001
From: Jakob Koschel <jakobkoschel at google.com>
Date: Fri, 12 Jun 2026 07:03:06 +0000
Subject: [PATCH 4/4] move mfcall to separate test to exclude windows
Created using spr 1.3.7
---
compiler-rt/test/cfi/summary-mfcall.cpp | 54 +++++++++++++++++++++++++
compiler-rt/test/cfi/summary.cpp | 36 -----------------
2 files changed, 54 insertions(+), 36 deletions(-)
create mode 100644 compiler-rt/test/cfi/summary-mfcall.cpp
diff --git a/compiler-rt/test/cfi/summary-mfcall.cpp b/compiler-rt/test/cfi/summary-mfcall.cpp
new file mode 100644
index 0000000000000..ad5f706383624
--- /dev/null
+++ b/compiler-rt/test/cfi/summary-mfcall.cpp
@@ -0,0 +1,54 @@
+// RUN: %clangxx_cfi_diag -g -o %t %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t nvmfcall 2>&1 | FileCheck --check-prefix=NVMFCALL %s
+// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t vmfcall 2>&1 | FileCheck --check-prefix=VMFCALL %s
+
+// UNSUPPORTED: target={{.*windows-msvc.*}}
+// REQUIRES: cxxabi
+
+#include "utils.h"
+#include <stdio.h>
+#include <string.h>
+
+struct S {
+ void f(); // non-virtual
+ virtual void g(); // virtual
+};
+void S::f() {}
+void S::g() {}
+
+struct T {
+ void f();
+ virtual void g();
+};
+void T::f() {}
+void T::g() {}
+
+typedef void (S::*S_void)();
+typedef int (S::*S_int)();
+
+template <typename To, typename From> To bitcast(From f) {
+ To t;
+ memcpy(&t, &f, sizeof(f));
+ return t;
+}
+
+int main(int argc, char **argv) {
+ if (argc < 2)
+ return 0;
+
+ create_derivers<T>();
+
+ if (strcmp(argv[1], "nvmfcall") == 0) {
+ S s;
+ break_optimization(&s);
+ // NVMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary-mfcall.cpp:[[@LINE+1]]
+ (s.*bitcast<S_void>(&T::f))();
+ } else if (strcmp(argv[1], "vmfcall") == 0) {
+ S s;
+ break_optimization(&s);
+ // VMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary-mfcall.cpp:[[@LINE+1]]
+ (s.*bitcast<S_int>(&S::g))();
+ }
+
+ return 0;
+}
diff --git a/compiler-rt/test/cfi/summary.cpp b/compiler-rt/test/cfi/summary.cpp
index 211419d1eafd6..e5d47f81ffa5b 100644
--- a/compiler-rt/test/cfi/summary.cpp
+++ b/compiler-rt/test/cfi/summary.cpp
@@ -4,8 +4,6 @@
// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t ucast 2>&1 | FileCheck --check-prefix=UCAST %s
// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t dcast 2>&1 | FileCheck --check-prefix=DCAST %s
// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t icall 2>&1 | FileCheck --check-prefix=ICALL %s
-// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t nvmfcall 2>&1 | FileCheck --check-prefix=NVMFCALL %s
-// RUN: %env_ubsan_opts=print_summary=1:report_error_type=1 %run %t vmfcall 2>&1 | FileCheck --check-prefix=VMFCALL %s
// REQUIRES: cxxabi
@@ -35,29 +33,6 @@ struct D {
void D::f() {}
void D::g() {}
-struct S {
- void f(); // non-virtual
- virtual void g(); // virtual
-};
-void S::f() {}
-void S::g() {}
-
-struct T {
- void f();
- virtual void g();
-};
-void T::f() {}
-void T::g() {}
-
-typedef void (S::*S_void)();
-typedef int (S::*S_int)();
-
-template <typename To, typename From> To bitcast(From f) {
- To t;
- memcpy(&t, &f, sizeof(f));
- return t;
-}
-
void g() {}
int main(int argc, char **argv) {
@@ -66,7 +41,6 @@ int main(int argc, char **argv) {
create_derivers<B>();
create_derivers<D>();
- create_derivers<T>();
if (strcmp(argv[1], "vcall") == 0) {
A *a = new A;
@@ -95,16 +69,6 @@ int main(int argc, char **argv) {
break_optimization(&fp);
// ICALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-icall {{.*}}summary.cpp:[[@LINE+1]]
((void (*)(int))fp)(42);
- } else if (strcmp(argv[1], "nvmfcall") == 0) {
- S s;
- break_optimization(&s);
- // NVMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary.cpp:[[@LINE+1]]
- (s.*bitcast<S_void>(&T::f))();
- } else if (strcmp(argv[1], "vmfcall") == 0) {
- S s;
- break_optimization(&s);
- // VMFCALL: SUMMARY: UndefinedBehaviorSanitizer: cfi-mfcall {{.*}}summary.cpp:[[@LINE+1]]
- (s.*bitcast<S_int>(&S::g))();
}
return 0;
More information about the llvm-branch-commits
mailing list