[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:10:51 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