Adds a test suite for KCFI (Kernel Control Flow Integrity) ABI, covering core functionality, optimization and code generation, addressing, architecture-specific KCFI sequence emission, and integration with patchable function entry.
Tests can be run via: make check-c RUNTESTFLAGS='kcfi.exp' gcc/testsuite/ChangeLog: * gcc.dg/kcfi/kcfi-adjacency.c: New test. * gcc.dg/kcfi/kcfi-basics.c: New test. * gcc.dg/kcfi/kcfi-call-sharing.c: New test. * gcc.dg/kcfi/kcfi-cold-partition.c: New test. * gcc.dg/kcfi/kcfi-complex-addressing.c: New test. * gcc.dg/kcfi/kcfi-ipa-robustness.c: New test. * gcc.dg/kcfi/kcfi-move-preservation.c: New test. * gcc.dg/kcfi/kcfi-no-sanitize-inline.c: New test. * gcc.dg/kcfi/kcfi-no-sanitize.c: New test. * gcc.dg/kcfi/kcfi-offset-validation.c: New test. * gcc.dg/kcfi/kcfi-patchable-basic.c: New test. * gcc.dg/kcfi/kcfi-patchable-entry-only.c: New test. * gcc.dg/kcfi/kcfi-patchable-large.c: New test. * gcc.dg/kcfi/kcfi-patchable-medium.c: New test. * gcc.dg/kcfi/kcfi-patchable-prefix-only.c: New test. * gcc.dg/kcfi/kcfi-pic-addressing.c: New test. * gcc.dg/kcfi/kcfi-retpoline-r11.c: New test. * gcc.dg/kcfi/kcfi-runtime.c: New test. * gcc.dg/kcfi/kcfi-tail-calls.c: New test. * gcc.dg/kcfi/kcfi-trap-encoding.c: New test. * gcc.dg/kcfi/kcfi-trap-section.c: New test. * gcc.dg/kcfi/kcfi.exp: New test. Signed-off-by: Kees Cook <k...@kernel.org> --- gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c | 72 +++++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c | 108 +++++++++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c | 84 ++++++++++ .../gcc.dg/kcfi/kcfi-cold-partition.c | 136 ++++++++++++++++ .../gcc.dg/kcfi/kcfi-complex-addressing.c | 135 ++++++++++++++++ .../gcc.dg/kcfi/kcfi-ipa-robustness.c | 54 +++++++ .../gcc.dg/kcfi/kcfi-move-preservation.c | 55 +++++++ .../gcc.dg/kcfi/kcfi-no-sanitize-inline.c | 100 ++++++++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c | 39 +++++ .../gcc.dg/kcfi/kcfi-offset-validation.c | 48 ++++++ .../gcc.dg/kcfi/kcfi-patchable-basic.c | 70 ++++++++ .../gcc.dg/kcfi/kcfi-patchable-entry-only.c | 62 +++++++ .../gcc.dg/kcfi/kcfi-patchable-large.c | 51 ++++++ .../gcc.dg/kcfi/kcfi-patchable-medium.c | 60 +++++++ .../gcc.dg/kcfi/kcfi-patchable-prefix-only.c | 60 +++++++ .../gcc.dg/kcfi/kcfi-pic-addressing.c | 104 ++++++++++++ .../gcc.dg/kcfi/kcfi-retpoline-r11.c | 50 ++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c | 151 ++++++++++++++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c | 142 ++++++++++++++++ .../gcc.dg/kcfi/kcfi-trap-encoding.c | 54 +++++++ gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c | 41 +++++ gcc/testsuite/gcc.dg/kcfi/kcfi.exp | 64 ++++++++ 22 files changed, 1740 insertions(+) create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c create mode 100644 gcc/testsuite/gcc.dg/kcfi/kcfi.exp diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c new file mode 100644 index 000000000000..becb47678df0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-adjacency.c @@ -0,0 +1,72 @@ +/* Test KCFI check/transfer adjacency - regression test for instruction + insertion. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +/* This test ensures that KCFI security checks remain immediately adjacent + to their corresponding indirect calls/jumps, with no executable instructions + between the type ID check and the control flow transfer. */ + +/* External function pointers to prevent optimization. */ +extern void (*complex_func_ptr)(int, int, int, int); +extern int (*return_func_ptr)(int, int); + +/* Function with complex argument preparation that could tempt + the optimizer to insert instructions between KCFI check and call. */ +__attribute__((noinline)) void test_complex_args(int a, int b, int c, int d) { + /* Complex argument expressions that might cause instruction scheduling. */ + complex_func_ptr(a * 2, b + c, d - a, (a << 1) | b); +} + +/* Function with return value handling. */ +__attribute__((noinline)) int test_return_value(int x, int y) { + /* Return value handling that shouldn't interfere with adjacency. */ + int result = return_func_ptr(x + 1, y * 2); + return result + 1; +} + +/* Test struct field access that caused issues in try-catch.c. */ +struct call_info { + void (*handler)(void); + int status; + int data; +}; + +extern struct call_info *global_call_info; + +__attribute__((noinline)) void test_struct_field_call(void) { + /* This pattern caused adjacency issues before the fix. */ + global_call_info->handler(); +} + +/* Test conditional indirect call. */ +__attribute__((noinline)) void test_conditional_call(int flag) { + if (flag) { + global_call_info->handler(); + } +} + +/* Should have KCFI instrumentation for all indirect calls. */ + +/* x86_64: Complete KCFI check sequence should be present. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r1[01]d\n\taddl\t[^,]+, %r1[01]d\n\tje\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tud2} { target x86_64-*-* } } } */ + +/* AArch64: Complete KCFI check sequence should be present. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-[0-9]+\]\n\tmov\tw17, #[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, w17\n\tb\.eq\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tbrk\t#[0-9]+\n\1:\n\tblr\tx[0-9]+} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Complete KCFI check sequence should be present with stack + spilling. */ +/* { dg-final { scan-assembler {push\t\{r0, r1\}\n\tldr\tr0, \[r[0-9]+, #-[0-9]+\]\n\tmovw\tr1, #[0-9]+\n\tmovt\tr1, #[0-9]+\n\tcmp\tr0, r1\n\tpop\t\{r0, r1\}\n\tbeq\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\t#[0-9]+\n\.Lkcfi_call[0-9]+:\n\tblx\tr[0-9]+} { target arm32 } } } */ + +/* RISC-V: Complete KCFI check sequence should be present. */ +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, \.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tebreak} { target riscv*-*-* } } } */ + +/* Should have trap section with entries. */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */ + +/* AArch64 should NOT have trap section (uses brk immediate instead) */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */ + +/* ARM 32-bit should NOT have trap section (uses udf immediate instead) */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c new file mode 100644 index 000000000000..b0a9e11f1f3c --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-basics.c @@ -0,0 +1,108 @@ +/* Test basic KCFI functionality - preamble generation. */ +/* { dg-do compile } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +/* Extern function declarations - should NOT get KCFI preambles. */ +extern void external_func(void); +extern int external_func_int(int x); + +void regular_function(int x) { + /* This should get KCFI preamble. */ +} + +void static_target_function(int x) { + /* Target function that can be called indirectly. */ +} + +__attribute__((nocf_check)) +void nocf_check_function(int x) { + /* This function has nocf_check attribute - should NOT get KCFI preamble. */ +} + +static void static_caller(void) { + /* Static function that makes an indirect call + Should NOT get KCFI preamble (not address-taken) + But must generate KCFI check for the indirect call. */ + void (*local_ptr)(int) = static_target_function; + local_ptr(42); /* This should generate KCFI check. */ +} + +/* Make external_func address-taken. */ +void (*func_ptr)(int) = regular_function; +void (*ext_ptr)(void) = external_func; +void (__attribute__((nocf_check)) *nocf_ptr)(int) = nocf_check_function; + +int main() { + func_ptr(42); + ext_ptr(); /* Indirect call to external_func. */ + external_func_int(10); /* Direct call to external_func_int. */ + static_caller(); /* Direct call to static function. */ + return 0; +} + +/* Verify KCFI preamble exists for regular_function. */ +/* { dg-final { scan-assembler {__cfi_regular_function:} } } */ + +/* Verify KCFI preamble symbol comes before main function symbol. */ +/* { dg-final { scan-assembler {__cfi_regular_function:.*regular_function:} } } */ + +/* Target function should have preamble (address-taken). */ +/* { dg-final { scan-assembler {__cfi_static_target_function:} } } */ + +/* Static caller should NOT have preamble (it's only called directly, + not address-taken). */ +/* { dg-final { scan-assembler-not {__cfi_static_caller:} } } */ + +/* Function with nocf_check attribute should NOT have preamble. */ +/* { dg-final { scan-assembler-not {__cfi_nocf_check_function:} } } */ + +/* x86_64: Verify type ID in preamble (after NOPs, before function label) */ +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t+nop\n.*\n\t+movl\t+\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */ + +/* AArch64: Verify type ID word in preamble. */ +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Verify type ID word in preamble. */ +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target arm32 } } } */ + +/* RISC-V: Verify type ID word in preamble */ +/* { dg-final { scan-assembler {__cfi_regular_function:\n\t\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */ + +/* x86_64: Static function should generate complete KCFI check sequence. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d\n\tje\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tud2\n.*\n\1:\n\tcall} { target x86_64-*-* } } } */ + +/* AArch64: Static function should generate complete KCFI check sequence. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]\n\tmov\tw17, #[0-9]+\n\tmovk\tw17, #[0-9]+, lsl #16\n\tcmp\tw16, w17\n\tb\.eq\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tbrk\t#[0-9]+\n\1:\n\tblr} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Static function should generate complete KCFI check sequence + with stack spilling. */ +/* { dg-final { scan-assembler {push\t\{r0, r1\}\n\tldr\tr0, \[r[0-9]+, #-4\]\n\tmovw\tr1, #[0-9]+\n\tmovt\tr1, #[0-9]+\n\tcmp\tr0, r1\n\tpop\t\{r0, r1\}\n\tbeq\t\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\t#[0-9]+\n\.Lkcfi_call[0-9]+:\n\tblx\tr[0-9]+} { target arm32 } } } */ + +/* RISC-V: Static function should generate KCFI check for indirect call. */ +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, (\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tebreak\n\t\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry[0-9]+:\n\t\.4byte\t\.Lkcfi_trap[0-9]+-\.Lkcfi_entry[0-9]+\n\t\.text\n\1:\n\tjalr} { target riscv*-*-* } } } */ + +/* Extern functions should NOT get KCFI preambles. */ +/* { dg-final { scan-assembler-not {__cfi_external_func:} } } */ +/* { dg-final { scan-assembler-not {__cfi_external_func_int:} } } */ + +/* Local functions should NOT get __kcfi_typeid_ symbols. */ +/* Only external declarations that are address-taken should get __kcfi_typeid_ */ +/* { dg-final { scan-assembler-not {__kcfi_typeid_regular_function} } } */ +/* { dg-final { scan-assembler-not {__kcfi_typeid_main} } } */ + +/* External address-taken functions should get __kcfi_typeid_ symbols. */ +/* { dg-final { scan-assembler {__kcfi_typeid_external_func} } } */ + +/* External functions that are only called directly should NOT get + __kcfi_typeid_ symbols. */ +/* { dg-final { scan-assembler-not {__kcfi_typeid_external_func_int} } } */ + +/* Should have trap section for KCFI checks. */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */ + +/* AArch64 should NOT have trap section (uses brk immediate instead). */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */ + +/* ARM 32-bit should NOT have trap section (uses udf immediate instead). */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c new file mode 100644 index 000000000000..f34d5f88547f --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-call-sharing.c @@ -0,0 +1,84 @@ +/* Test KCFI check sharing bug - optimizer incorrectly shares KCFI checks + between different function types. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +/* Reproduce the pattern from Linux kernel internal_create_group where: + - Two different function pointer types (is_visible vs is_bin_visible). + - Both get loaded into the same register (%rcx). + - Optimizer creates shared KCFI check with wrong type ID. + - This causes CFI failures in production kernel. */ + +struct kobject { int dummy; }; +struct attribute { int dummy; }; +struct bin_attribute { int dummy; }; + +struct attribute_group { + const char *name; + // Type ID A + int (*is_visible)(struct kobject *, struct attribute *, int); + // Type ID B + int (*is_bin_visible)(struct kobject *, const struct bin_attribute *, int); + struct attribute **attrs; + const struct bin_attribute **bin_attrs; +}; + +/* Function that mimics __first_visible from kernel - gets inlined into + caller. */ +static int __first_visible(const struct attribute_group *grp, struct kobject *kobj) +{ + /* Path 1: Call is_visible function pointer. */ + if (grp->attrs && grp->attrs[0] && grp->is_visible) + return grp->is_visible(kobj, grp->attrs[0], 0); + + /* Path 2: Call is_bin_visible function pointer. */ + if (grp->bin_attrs && grp->bin_attrs[0] && grp->is_bin_visible) + return grp->is_bin_visible(kobj, grp->bin_attrs[0], 0); + + return 0; +} + +/* Main function that triggers the optimization bug. */ +int test_kcfi_check_sharing(struct kobject *kobj, const struct attribute_group *grp) +{ + /* This should inline __first_visible and create the problematic pattern where: + 1. Both function pointers get loaded into same register. + 2. Optimizer shares KCFI check between them. + 3. Uses wrong type ID for one of the calls. */ + return __first_visible(grp, kobj); +} + +/* Each indirect call should have its own KCFI check with correct type ID. + + Should see: + 1. KCFI check for is_visible call with is_visible type ID. + 2. KCFI check for is_bin_visible call with is_bin_visible type ID. */ + +/* Verify we have TWO different KCFI check sequences. */ +/* Each check should have different type ID constants. */ +/* x86: { dg-final { scan-assembler-times {movl\s+\$-?[0-9]+,\s+%r10d} 2 { target i?86-*-* x86_64-*-* } } } */ +/* AArch64: { dg-final { scan-assembler-times {mov\s+w17, #[0-9]+} 2 { target aarch64*-*-* } } } */ +/* ARM 32-bit: { dg-final { scan-assembler-times {movw\s+r1, #[0-9]+} 2 { target arm32 } } } */ +/* RISC-V: { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 2 { target riscv*-*-* } } } */ + +/* Verify the checks use DIFFERENT type IDs (not shared). + We should NOT see the same type ID used twice - that would indicate + sharing bug. */ +/* x86: { dg-final { scan-assembler-not {movl\s+\$(-?[0-9]+),\s+%r10d.*movl\s+\$\1,\s+%r10d} { target i?86-*-* x86_64-*-* } } } */ +/* AArch64: { dg-final { scan-assembler-not {mov\s+w17, #([0-9]+).*mov\s+w17, #\1} { target aarch64*-*-* } } } */ +/* ARM 32-bit: { dg-final { scan-assembler-not {movw\s+r1, #([0-9]+).*movw\s+r1, #\1} { target arm32 } } } */ +/* RISC-V: { dg-final { scan-assembler-not {lui\s+t2, ([0-9]+)\s.*lui\s+t2, \1\s} { target riscv*-*-* } } } */ + +/* Verify each call follows its own check (not shared) */ +/* Should have 2 separate trap instructions. */ +/* x86: { dg-final { scan-assembler-times {ud2} 2 { target i?86-*-* x86_64-*-* } } } */ +/* AArch64: { dg-final { scan-assembler-times {brk\s+#[0-9]+} 2 { target aarch64*-*-* } } } */ +/* ARM 32-bit: { dg-final { scan-assembler-times {udf\s+#[0-9]+} 2 { target arm32 } } } */ +/* RISC-V: { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } } } */ + +/* Verify 2 separate call sites. */ +/* x86: { dg-final { scan-assembler-times {jmp\s+\*%[a-z0-9]+} 2 { target i?86-*-* x86_64-*-* } } } */ +/* AArch64: Allow both blr (regular call) and br (tail call) */ +/* AArch64: { dg-final { scan-assembler-times {br\tx[0-9]+} 2 { target aarch64*-*-* } } } */ +/* ARM 32-bit: { dg-final { scan-assembler-times {bx\s+(?:r[0-9]+|ip)} 2 { target arm32 } } } */ +/* RISC-V: { dg-final { scan-assembler-times {jalr\t[a-z0-9]+} 2 { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c new file mode 100644 index 000000000000..17def558ada4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-cold-partition.c @@ -0,0 +1,136 @@ +/* Test KCFI cold function and cold partition behavior. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ +/* { dg-additional-options "-freorder-blocks-and-partition" { target freorder } } */ + +void regular_function(void) { + /* Regular function should get preamble. */ +} + +/* Cold-attributed function should STILL get preamble (it's a regular + function, just marked cold). */ +__attribute__((cold)) +void cold_attributed_function(void) { + /* This function has cold attribute but should still get KCFI preamble. */ +} + +/* Hot-attributed function should get preamble. */ +__attribute__((hot)) +void hot_attributed_function(void) { + /* This function is explicitly hot and should get KCFI preamble. */ +} + +/* Global to prevent optimization from eliminating cold paths. */ +extern void abort(void); + +/* Additional function to test that normal functions still get preambles. */ +__attribute__((noinline)) +int another_regular_function(int x) { + return x + 42; +} + +/* Function designed to generate cold partitions under optimization. */ +__attribute__((noinline)) +void function_with_cold_partition(int condition) { + /* Hot path - very likely to execute. */ + if (__builtin_expect(condition == 42, 1)) { + /* Simple hot path that optimizer will keep inline. */ + return; + } + + /* Cold paths that actually do something to prevent elimination. */ + if (__builtin_expect(condition < 0, 0)) { + /* Error path 1 - call abort to prevent elimination. */ + abort(); + } + + if (__builtin_expect(condition > 1000000, 0)) { + /* Error path 2 - call abort to prevent elimination. */ + abort(); + } + + if (__builtin_expect(condition == 999999, 0)) { + /* Error path 3 - more substantial cold code. */ + volatile int sum = 0; + for (volatile int i = 0; i < 100; i++) { + sum += i * condition; + } + if (sum > 0) + abort(); + } + + /* More cold paths - switch with many unlikely cases. */ + switch (condition) { + case 1000001: case 1000002: case 1000003: case 1000004: case 1000005: + case 1000006: case 1000007: case 1000008: case 1000009: case 1000010: + /* Each case does some work before abort. */ + volatile int work = condition * 2; + if (work > 0) abort(); + break; + default: + if (condition != 42) { + /* Fallback cold path - substantial work. */ + volatile int result = 0; + for (volatile int j = 0; j < condition % 50; j++) { + result += j; + } + if (result >= 0) abort(); + } + } +} + +/* Test function pointers to ensure address-taken detection works. */ +void test_function_pointers(void) { + void (*regular_ptr)(void) = regular_function; + void (*cold_ptr)(void) = cold_attributed_function; + void (*hot_ptr)(void) = hot_attributed_function; + + regular_ptr(); + cold_ptr(); + hot_ptr(); +} + +int main() { + regular_function(); + cold_attributed_function(); + hot_attributed_function(); + function_with_cold_partition(42); /* Normal case - stay in hot path. */ + another_regular_function(5); + test_function_pointers(); + return 0; +} + +/* Regular function should have preamble. */ +/* { dg-final { scan-assembler "__cfi_regular_function:" } } */ + +/* Cold-attributed function should STILL have preamble (it's a legitimate function) */ +/* { dg-final { scan-assembler "__cfi_cold_attributed_function:" } } */ + +/* Hot-attributed function should have preamble. */ +/* { dg-final { scan-assembler "__cfi_hot_attributed_function:" } } */ + +/* Function that generates cold partitions should have preamble for main entry. */ +/* { dg-final { scan-assembler "__cfi_function_with_cold_partition:" } } */ + +/* Address-taken functions should have preambles. */ +/* { dg-final { scan-assembler "__cfi_test_function_pointers:" } } */ + +/* The function should generate a .cold partition (only on targets that support freorder) */ +/* { dg-final { scan-assembler "function_with_cold_partition\\.cold:" { target freorder } } } */ + +/* The .cold partition should NOT get a __cfi_ preamble since it's never + reached via indirect calls. */ +/* { dg-final { scan-assembler-not "__cfi_function_with_cold_partition\\.cold:" { target freorder } } } */ + +/* Additional regular function should get preamble. */ +/* { dg-final { scan-assembler "__cfi_another_regular_function:" } } */ + +/* Test coverage summary: + 1. Cold-attributed function (__attribute__((cold))): SHOULD get preamble + 2. Cold partition (-freorder-blocks-and-partition): should NOT get preamble + 3. IPA split .part function (split_part=true): Logic in place, would skip if triggered + + Note: IPA function splitting (creating .part functions with split_part=true) requires + specific optimization conditions that are difficult to trigger reliably in tests. + The KCFI logic correctly handles this case using the split_part flag check. +*/ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c new file mode 100644 index 000000000000..b9a8955b0899 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-complex-addressing.c @@ -0,0 +1,135 @@ +/* Test KCFI with complex addressing modes (structure members, array + elements). This is a regression test for the change_address_1 RTL + error that occurred when target_addr was PLUS(reg, offset) instead + of a simple register. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +struct function_table { + int (*callback1)(int); + int (*callback2)(int, int); + void (*callback3)(void); + int (*callback4)(void *, void *, void *, void *, void *, void *); + int data; +}; + +static int handler1(int x) { + return x * 2; +} + +static int handler2(int x, int y) { + return x + y; +} + +static void handler3(void) { + /* Empty handler. */ +} + +/* Test indirect calls through structure members - this creates + PLUS(reg, offset) addressing. */ +int test_struct_members(struct function_table *table) { + int result = 0; + + /* These indirect calls will generate complex addressing modes: + * call *(%rdi) - callback1 at offset 0 + * call *8(%rdi) - callback2 at offset 8 + * call *16(%rdi) - callback3 at offset 16 + * KCFI must handle PLUS(reg, struct_offset) + kcfi_offset. */ + + result += table->callback1(10); + result += table->callback2(5, 7); + table->callback3(); + + return result; +} + +/* Test indirect calls through array elements - another source of + complex addressing. */ +typedef int (*func_array_t)(int); + +int test_array_elements(func_array_t functions[], int index) { + /* This creates addressing like MEM[PLUS(PLUS(reg, index*8), 0)] + which should be simplified to MEM[PLUS(reg, index*8)]. */ + return functions[index](42); +} + +/* Test with global structure. */ +static struct function_table global_table = { + .callback1 = handler1, + .callback2 = handler2, + .callback3 = handler3, + .data = 100 +}; + +int test_global_struct(void) { + /* Access through global structure - may generate different + addressing patterns. */ + return global_table.callback1(20) + global_table.callback2(3, 4); +} + +/* Test nested structure access. */ +struct nested_table { + struct function_table inner; + int extra_data; +}; + +int test_nested_struct(struct nested_table *nested) { + /* Even more complex addressing: nested structure member access. */ + return nested->inner.callback1(15); +} + +int test_many_args(void *one, void *two, void *three, void *four, void *five, void *six) +{ + return (unsigned long)one + (unsigned long)two + (unsigned long)three + + (unsigned long)four + (unsigned long)five + (unsigned long)six; +} + +int main() { + struct function_table local_table = { + .callback1 = handler1, + .callback2 = handler2, + .callback3 = handler3, + .callback4 = test_many_args, + .data = 50 + }; + + func_array_t func_array[] = { handler1, handler1, handler1 }; + + int result = 0; + result += test_struct_members(&local_table); + result += test_array_elements(func_array, 1); + result += test_global_struct(); + + struct nested_table nested = { .inner = local_table, .extra_data = 200 }; + result += test_nested_struct(&nested); + + result += local_table.callback4(handler1, handler2, handler3, &result, main, &local_table); + + return result; +} + +/* Verify that all address-taken functions get KCFI preambles. */ +/* { dg-final { scan-assembler {__cfi_handler1:} } } */ +/* { dg-final { scan-assembler {__cfi_handler2:} } } */ +/* { dg-final { scan-assembler {__cfi_handler3:} } } */ +/* { dg-final { scan-assembler {__cfi_test_many_args:} } } */ + +/* x86_64: Verify KCFI checks are generated for indirect calls through + complex addressing. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */ + +/* AArch64: Verify KCFI checks for complex addressing. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Verify KCFI checks for complex addressing with stack spilling. */ +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */ +/* { dg-final { scan-assembler {udf} { target arm32 } } } */ + +/* RISC-V: Verify KCFI check sequence for complex addressing. */ +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+\n\tbeq\tt1, t2, \.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tebreak} { target riscv*-*-* } } } */ + +/* Should have trap section for x86 and RISC-V only. */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c new file mode 100644 index 000000000000..a43bcd4f3e3f --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-ipa-robustness.c @@ -0,0 +1,54 @@ +/* Test KCFI IPA pass robustness with compiler-generated constructs. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +#include <stddef.h> + +/* Test various compiler-generated constructs that could confuse IPA pass. */ + +/* static_assert - this was causing the original crash. */ +typedef struct { + int field1; + char field2; +} test_struct_t; + +static_assert(offsetof(test_struct_t, field1) == 0, "layout check 1"); +static_assert(offsetof(test_struct_t, field2) == 4, "layout check 2"); +static_assert(sizeof(test_struct_t) >= 5, "size check"); + +/* Regular functions that should get KCFI analysis. */ +void regular_function(void) { + /* Should get KCFI preamble. */ +} + +static void static_function(void) { + /* With -O2: correctly identified as not address-taken, no preamble. */ +} + +void address_taken_function(void) { + /* Should get KCFI preamble (address taken below) */ +} + +/* Function pointer to create address-taken scenario. */ +void (*func_ptr)(void) = address_taken_function; + +/* More static_asserts mixed with function definitions. */ +static_assert(sizeof(void*) >= 4, "pointer size check"); + +int main(void) { + regular_function(); /* Direct call. */ + static_function(); /* Direct call to static. */ + func_ptr(); /* Indirect call. */ + + static_assert(sizeof(int) == 4, "int size check"); + + return 0; +} + +/* Verify KCFI preambles are generated appropriately. */ +/* { dg-final { scan-assembler "__cfi_regular_function:" } } */ +/* { dg-final { scan-assembler "__cfi_address_taken_function:" } } */ +/* { dg-final { scan-assembler "__cfi_main:" } } */ + +/* With -O2: static_function correctly identified as not address-taken. */ +/* { dg-final { scan-assembler-not "__cfi_static_function:" } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c new file mode 100644 index 000000000000..50029d136716 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-move-preservation.c @@ -0,0 +1,55 @@ +/* Test that KCFI preserves function pointer moves at -O2 optimization. + This test ensures that the combine pass doesn't incorrectly optimize away + the move instruction needed to transfer function pointers from argument + registers to the target registers used by KCFI patterns. */ + +/* { dg-do compile } */ +/* { dg-additional-options "-O2 -std=gnu11" } */ + +static int called_count = 0; + +/* Function taking one argument, returning void. */ +static __attribute__((noinline)) void increment_void(int *counter) +{ + (*counter)++; +} + +/* Function taking one argument, returning int. */ +static __attribute__((noinline)) int increment_int(int *counter) +{ + (*counter)++; + return *counter; +} + +/* Don't allow the compiler to inline the calls. */ +static __attribute__((noinline)) void indirect_call(void (*func)(int *)) +{ + func(&called_count); +} + +int main(void) +{ + /* This should work - matching prototype. */ + indirect_call(increment_void); + + /* This should trap - mismatched prototype. */ + indirect_call((void *)increment_int); + + return 0; +} + +/* Verify complete KCFI check sequence with preserved move instruction. At + -O2, the combine pass previously optimized away the move from %rdi to %rax, + breaking KCFI. Verify the full sequence is preserved. */ + +/* x86_64: Complete KCFI sequence with move preservation and indirect jump. */ +/* { dg-final { scan-assembler {(indirect_call):.*\n.*movq\s+%rdi,\s+(%rax)\n.*movl\s+\$[0-9]+,\s+%r10d\n\taddl\s+-4\(\2\),\s+%r10d\n\tje\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tud2.*\.Lkcfi_call[0-9]+:\n\tjmp\s+\*\2.*\.size\s+\1,\s+\.-\1} { target x86_64-*-* } } } */ + +/* AArch64: Complete KCFI sequence with move preservation and indirect branch. */ +/* { dg-final { scan-assembler {(indirect_call):.*\n.*mov\s+(x[0-9]+),\s+x0\n.*ldur\s+w16,\s+\[\2,\s+#-4\]\n\tmov\s+w17,\s+#[0-9]+\n\tmovk\s+w17,\s+#[0-9]+,\s+lsl\s+#16\n\tcmp\s+w16,\s+w17\n\tb\.eq\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tbrk\s+#[0-9]+.*\.Lkcfi_call[0-9]+:\n\tbr\s+\2.*\.size\s+\1,\s+\.-\1} { target aarch64*-*-* } } } */ + +/* ARM32: Complete KCFI sequence with move preservation and indirect branch. */ +/* { dg-final { scan-assembler {(indirect_call):.*\n.*mov\s+(r[0-9]+),\s+r0\n.*push\s+\{r0,\s+r1\}\n\tldr\s+r0,\s+\[\2,\s+#-4\]\n\tmovw\s+r1,\s+#[0-9]+\n\tmovt\s+r1,\s+#[0-9]+\n\tcmp\s+r0,\s+r1\n\tpop\s+\{r0,\s+r1\}\n\tbeq\s+\.Lkcfi_call[0-9]+\n\.Lkcfi_trap[0-9]+:\n\tudf\s+#[0-9]+.*\.Lkcfi_call[0-9]+:\n\tbx\s+\2.*\.size\s+\1,\s+\.-\1} { target arm32 } } } */ + +/* RISC-V: Complete KCFI sequence with move preservation and indirect jump. */ +/* { dg-final { scan-assembler {(indirect_call):.*mv\s+(a[0-9]+),a0.*lw\s+t1,\s+-4\(\2\).*lui\s+t2,\s+[0-9]+.*addiw\s+t2,\s+t2,\s+-?[0-9]+.*beq\s+t1,\s+t2,\s+\.Lkcfi_call[0-9]+.*ebreak.*jalr\s+zero,\s+\2,\s+0.*\.size\s+\1,\s+\.-\1} { target riscv64-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c new file mode 100644 index 000000000000..c43d8014ff2d --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize-inline.c @@ -0,0 +1,100 @@ +/* Test that no_sanitize("kcfi") attribute is preserved during inlining. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +extern void external_side_effect(int value); + +/* Regular function (should get KCFI checks) */ +__attribute__((noinline)) +void normal_function(void (*callback)(int)) +{ + /* This indirect call must generate KCFI checks. */ + callback(300); + external_side_effect(300); +} + +/* Regular function marked with no_sanitize("kcfi") (positive control) */ +__attribute__((noinline, no_sanitize("kcfi"))) +void sensitive_non_inline_function(void (*callback)(int)) +{ + /* This indirect call should NOT generate KCFI checks. */ + callback(100); + external_side_effect(100); +} + +/* Function marked with both no_sanitize("kcfi") and always_inline. */ +__attribute__((always_inline, no_sanitize("kcfi"))) +static inline void sensitive_inline_function(void (*callback)(int)) +{ + /* This indirect call should NOT generate KCFI checks when inlined. */ + callback(42); + external_side_effect(42); +} + +/* Explicit wrapper for testing sensitive_inline_function behavior. */ +__attribute__((noinline)) +void wrap_sensitive_inline(void (*callback)(int)) +{ + sensitive_inline_function(callback); +} + +/* Function marked with only always_inline (should get KCFI checks) */ +__attribute__((always_inline)) +static inline void normal_inline_function(void (*callback)(int)) +{ + /* This indirect call must generate KCFI checks when inlined. */ + callback(200); + external_side_effect(200); +} + +/* Explicit wrapper for testing normal_inline_function behavior. */ +__attribute__((noinline)) +void wrap_normal_inline(void (*callback)(int)) +{ + normal_inline_function(callback); +} + +void test_callback(int value) +{ + external_side_effect(value); +} + +static void (*volatile function_pointer)(int) = test_callback; + +int main(void) +{ + void (*fn_ptr)(int) = function_pointer; + + normal_function(fn_ptr); + wrap_normal_inline(fn_ptr); + sensitive_non_inline_function(fn_ptr); + wrap_sensitive_inline(fn_ptr); + + return 0; +} + +/* Verify correct number of KCFI checks: exactly 2 */ +/* { dg-final { scan-assembler-times {ud2} 2 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times {brk\s+#[0-9]+} 2 { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-times {udf\s+#[0-9]+} 2 { target arm32 } } } */ +/* { dg-final { scan-assembler-times {ebreak} 2 { target riscv*-*-* } } } */ + +/* Positive controls: these should have KCFI checks. */ +/* { dg-final { scan-assembler {normal_function:.*ud2.*\.size\s+normal_function} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {wrap_normal_inline:.*ud2.*\.size\s+wrap_normal_inline} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {normal_function:.*brk\s+#[0-9]+.*\.size\s+normal_function} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler {wrap_normal_inline:.*brk\s+#[0-9]+.*\.size\s+wrap_normal_inline} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler {normal_function:.*udf\t#[0-9]+.*\.size\s+normal_function} { target arm32 } } } */ +/* { dg-final { scan-assembler {wrap_normal_inline:.*udf\t#[0-9]+.*\.size\s+wrap_normal_inline} { target arm32 } } } */ +/* { dg-final { scan-assembler {normal_function:.*ebreak.*\.size\s+normal_function} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler {wrap_normal_inline:.*ebreak.*\.size\s+wrap_normal_inline} { target riscv*-*-* } } } */ + +/* Negative controls: these should NOT have KCFI checks. */ +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*ud2.*\.size\s+sensitive_non_inline_function} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*ud2.*\.size\s+wrap_sensitive_inline} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*brk\s+#[0-9]+.*\.size\s+sensitive_non_inline_function} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*brk\s+#[0-9]+.*\.size\s+wrap_sensitive_inline} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:[^\n]*udf\t#[0-9]+[^\n]*\.size\tsensitive_non_inline_function} { target arm32 } } } */ +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:[^\n]*udf\t#[0-9]+[^\n]*\.size\twrap_sensitive_inline} { target arm32 } } } */ +/* { dg-final { scan-assembler-not {sensitive_non_inline_function:.*ebreak.*\.size\s+sensitive_non_inline_function} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler-not {wrap_sensitive_inline:.*ebreak.*\.size\s+wrap_sensitive_inline} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c new file mode 100644 index 000000000000..6f1a558c0820 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-no-sanitize.c @@ -0,0 +1,39 @@ +/* Test KCFI with no_sanitize attribute. */ +/* { dg-do compile } */ + +void target_function(void) { + /* This should get KCFI preamble. */ +} + +void caller_with_checks(void) { + /* This function should generate KCFI checks. */ + void (*func_ptr)(void) = target_function; + func_ptr(); +} + +__attribute__((no_sanitize("kcfi"))) +void caller_no_checks(void) { + /* This function should NOT generate KCFI checks due to no_sanitize. */ + void (*func_ptr)(void) = target_function; + func_ptr(); +} + +int main() { + caller_with_checks(); /* This should generate checks inside. */ + caller_no_checks(); /* This should NOT generate checks inside. */ + return 0; +} + +/* All functions should get preambles regardless of no_sanitize. */ +/* { dg-final { scan-assembler "__cfi_target_function:" } } */ +/* { dg-final { scan-assembler "__cfi_caller_with_checks:" } } */ +/* { dg-final { scan-assembler "__cfi_caller_no_checks:" } } */ +/* { dg-final { scan-assembler "__cfi_main:" } } */ + +/* caller_with_checks() should generate KCFI check. + caller_no_checks() should NOT generate KCFI check (no_sanitize). + So a total of exactly 1 KCFI check in the entire program. */ +/* { dg-final { scan-assembler-times {addl\t-4\(%r[ad]x\), %r1[01]d} 1 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 1 { target aarch64-*-* } } } */ +/* { dg-final { scan-assembler-times {ldr\tr0, \[r[0-9]+, #-4\]} 1 { target arm32 } } } */ +/* { dg-final { scan-assembler-times {lw\tt1, -[0-9]+\(} 1 { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c new file mode 100644 index 000000000000..f93a042d9752 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-offset-validation.c @@ -0,0 +1,48 @@ +/* Test KCFI call-site offset validation across architectures. */ +/* { dg-do compile } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void target_func_a(void) { } +void target_func_b(int x) { } +void target_func_c(int x, int y) { } + +int main() { + void (*ptr_a)(void) = target_func_a; + void (*ptr_b)(int) = target_func_b; + void (*ptr_c)(int, int) = target_func_c; + + /* Multiple indirect calls. */ + ptr_a(); + ptr_b(1); + ptr_c(1, 2); + + return 0; +} + +/* Should have KCFI preambles for all functions. */ +/* { dg-final { scan-assembler "__cfi_target_func_a:" } } */ +/* { dg-final { scan-assembler "__cfi_target_func_b:" } } */ +/* { dg-final { scan-assembler "__cfi_target_func_c:" } } */ + +/* x86_64: All call sites should use -4 offset for KCFI type ID loads, even + with -falign-functions=16 (we're not using patchable entries here). */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */ + +/* AArch64: All call sites should use -4 offset. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: All call sites should use -4 offset with stack spilling. */ +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */ + +/* RISC-V: All call sites should use -4 offset. */ +/* { dg-final { scan-assembler {lw\tt1, -4\(} { target riscv*-*-* } } } */ + +/* Should have trap section. */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */ + +/* AArch64 should NOT have trap section (uses brk immediate instead) */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target aarch64*-*-* } } } */ + +/* ARM 32-bit should NOT have trap section (uses udf immediate instead) */ +/* { dg-final { scan-assembler-not {\.kcfi_traps} { target arm32 } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c new file mode 100644 index 000000000000..a2d0ef0c6ff6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-basic.c @@ -0,0 +1,70 @@ +/* Test KCFI with patchable function entries - basic case. */ +/* { dg-do compile } */ +/* { dg-additional-options "-fpatchable-function-entry=5,2" } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void test_function(int x) { + /* Function should get both KCFI preamble and patchable entries. */ +} + +int main() { + test_function(42); + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_test_function:" } } */ + +/* Should have patchable function entry section. */ +/* { dg-final { scan-assembler "__patchable_function_entries" } } */ + +/* x86_64: Should have exactly 2 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */ + +/* x86_64: Should have exactly 3 entry NOPs between .cfi_startproc and + pushq. */ +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */ + +/* x86_64: KCFI should have exactly 9 NOPs between __cfi_ and movl. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl} { target x86_64-*-* } } } */ + +/* x86_64: Validate KCFI type ID is present. */ +/* { dg-final { scan-assembler {movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */ + +/* AArch64: Should have exactly 2 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */ + +/* AArch64: Should have exactly 3 entry NOPs between .cfi_startproc and + stack manipulation. */ +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*sub\t*sp} { target aarch64*-*-* } } } */ + +/* AArch64: KCFI should have alignment NOPs then .word immediate. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */ + +/* AArch64: Validate clean KCFI boundary - .word then immediate end/size. */ +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Should have exactly 2 prefix NOPs between .LPFE and .syntax. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.syntax} { target arm32 } } } */ + +/* ARM 32-bit: Should have exactly 3 entry NOPs after function label. */ +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */ + +/* ARM 32-bit: KCFI should have alignment NOPs then .word immediate. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target arm32 } } } */ + +/* ARM 32-bit: Validate clean KCFI boundary - .word then immediate end/size. */ +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target arm32 } } } */ + +/* RISC-V: Should have exactly 2 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */ + +/* RISC-V: Should have exactly 3 entry NOPs before .cfi_startproc followed + by addi sp. */ +/* { dg-final { scan-assembler {nop\n\t*nop\n\t*nop\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*addi\t*sp} { target riscv*-*-* } } } */ + +/* RISC-V: KCFI should have alignment NOPs then .word immediate. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */ + +/* RISC-V: Validate clean KCFI boundary - .word then immediate end/size. */ +/* { dg-final { scan-assembler {\.word\t0x[0-9a-f]+\n\.Lcfi_func_end_test_function:\n\t\.size\t__cfi_test_function, \.-__cfi_test_function} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c new file mode 100644 index 000000000000..62e1926e107e --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-entry-only.c @@ -0,0 +1,62 @@ +/* Test KCFI with patchable function entries - entry NOPs only. */ +/* { dg-do compile } */ +/* { dg-additional-options "-fpatchable-function-entry=4,0" } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void test_function(void) { +} + +static void caller(void) { + /* Make an indirect call to test callsite offset calculation. */ + void (*func_ptr)(void) = test_function; + func_ptr(); +} + +int main() { + test_function(); /* Direct call. */ + caller(); /* Indirect call via static function. */ + return 0; +} + +/* x86_64: Should have KCFI preamble with architecture alignment NOPs (11). */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+nop\n\t+movl\t+\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */ + +/* AArch64: Should have KCFI preamble with no alignment NOPs. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Should have KCFI preamble with no alignment NOPs. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { target arm32 } } } */ + +/* RISC-V: Should have KCFI preamble with no alignment NOPs. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */ + +/* x86_64: Indirect call should use original prefix NOPs (0) for offset + calculation: -4 offset. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d\n\tje\t(\.Lkcfi_call[0-9]+)\n\.Lkcfi_trap[0-9]+:\n\tud2\n.*\n\1:\n\tcall} { target x86_64-*-* } } } */ + +/* x86_64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */ + +/* AArch64: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*stp} { target aarch64*-*-* } } } */ + +/* AArch64: No alignment NOPs - function type should come immediately before + function. */ +/* { dg-final { scan-assembler {\.type\t*test_function, %function\n*test_function:} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: All 4 NOPs are entry NOPs - should have exactly 4 entry NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */ + +/* ARM 32-bit: No alignment NOPs - function type should come immediately + before function. */ +/* { dg-final { scan-assembler {\.type\t*test_function, %function\n*test_function:} { target arm32 } } } */ + +/* RISC-V: All 4 NOPs are entry NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } } */ + +/* RISC-V: No alignment NOPs - function type should come immediately + before function. */ +/* { dg-final { scan-assembler {\.type\t*test_function, @function\n*test_function:} { target riscv*-*-* } } } */ + +/* Should have patchable function entry section. */ +/* { dg-final { scan-assembler "__patchable_function_entries" } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c new file mode 100644 index 000000000000..3d5618847840 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-large.c @@ -0,0 +1,51 @@ +/* Test KCFI with large patchable function entries. */ +/* { dg-do compile } */ +/* { dg-additional-options "-fpatchable-function-entry=11,11" } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void test_function(void) { +} + +int main() { + void (*func_ptr)(void) = test_function; + func_ptr(); + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_test_function:" } } */ + +/* Should have patchable function entry section. */ +/* { dg-final { scan-assembler "__patchable_function_entries" } } */ + +/* x86_64: Should have exactly 11 alignment NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */ + +/* x86_64: Should have 0 entry NOPs - function starts immediately with + pushq. */ +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target x86_64-*-* } } } */ + +/* x86_64: KCFI should have 0 entry NOPs - goes directly to typeid movl. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */ + +/* x86_64: Call site should use -15 offset. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-15\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */ + +/* AArch64: Should have exactly 11 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Should have exactly 11 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */ + +/* AArch64: Call site should use -15 offset. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-15\]} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Call site should use -15 offset. */ +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-15\]} { target arm32 } } } */ + +/* RISC-V: Should have 11 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */ + +/* RISC-V: Call site should use -15 offset (same as x86/AArch64). */ +/* { dg-final { scan-assembler {lw\tt1, -15\(} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c new file mode 100644 index 000000000000..4f00a86dbcb7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-medium.c @@ -0,0 +1,60 @@ +/* Test KCFI with medium patchable function entries. */ +/* { dg-do compile } */ +/* { dg-additional-options "-fpatchable-function-entry=8,4" } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void test_function(void) { +} + +int main() { + void (*func_ptr)(void) = test_function; + func_ptr(); + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_test_function:" } } */ + +/* Should have patchable function entry section. */ +/* { dg-final { scan-assembler "__patchable_function_entries" } } */ + +/* x86_64: Should have exactly 4 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target x86_64-*-* } } } */ + +/* x86_64: Should have exactly 4 entry NOPs between .cfi_startproc and + pushq. */ +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*pushq} { target x86_64-*-* } } } */ + +/* x86_64: KCFI should have exactly 7 alignment NOPs between __cfi_ and + typeid movl. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl\t\$0x[0-9a-f]+, %eax} { target x86_64-*-* } } } */ + +/* x86_64: Call site should use -8 offset. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-8\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */ + +/* AArch64: Should have exactly 4 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target aarch64*-*-* } } } */ + +/* AArch64: Should have exactly 4 entry NOPs after .cfi_startproc. */ +/* { dg-final { scan-assembler {\.cfi_startproc\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Should have exactly 4 prefix NOPs between .LPFE and .syntax. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.syntax} { target arm32 } } } */ + +/* ARM 32-bit: Should have exactly 4 entry NOPs after function label. */ +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */ + +/* AArch64: Call site should use -8 offset. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-8\]} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Call site should use -8 offset. */ +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-8\]} { target arm32 } } } */ + +/* RISC-V: Should have exactly 4 prefix NOPs between .LPFE and .type. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*\.type} { target riscv*-*-* } } } */ + +/* RISC-V: Should have 4 entry NOPs. */ +/* { dg-final { scan-assembler {test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\.LFB} { target riscv*-*-* } } } */ + +/* RISC-V: Call site should use -8 offset (same as x86/AArch64) */ +/* { dg-final { scan-assembler {lw\tt1, -8\(} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c new file mode 100644 index 000000000000..98c53ef52989 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-patchable-prefix-only.c @@ -0,0 +1,60 @@ +/* Test KCFI with patchable function entries - prefix NOPs only. */ +/* { dg-do compile } */ +/* { dg-additional-options "-fpatchable-function-entry=3,3" } */ +/* { dg-additional-options "-falign-functions=16" { target x86_64-*-* } } */ + +void test_function(void) { +} + +int main() { + test_function(); + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_test_function:" } } */ + +/* x86_64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target x86_64-*-* } } } */ + +/* x86_64: No entry NOPs - function should start immediately with prologue. */ +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*pushq\t*%rbp} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target x86_64-*-* } } } */ + +/* x86_64: should have exactly 8 alignment NOPs. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*nop\n\t*movl} { target x86_64-*-* } } } */ + +/* AArch64: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target aarch64*-*-* } } } */ + +/* AArch64: No entry NOPs - function should start immediately with prologue. */ +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc\n\t*nop\n\t*ret} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target aarch64*-*-* } } } */ + +/* AArch64: KCFI type ID should have 1 alignment NOP then word. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop} { target arm32 } } } */ + +/* ARM 32-bit: No entry NOPs - function should start immediately with + prologue. */ +/* { dg-final { scan-assembler {test_function:} { target arm32 } } } */ +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target arm32 } } } */ + +/* ARM 32-bit: KCFI type ID should have 1 alignment NOP then word. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target arm32 } } } */ + +/* RISC-V: All 3 NOPs are prefix NOPs - should have exactly 3 prefix NOPs. */ +/* { dg-final { scan-assembler {\.LPFE[0-9]+:\n\t*nop\n\t*nop\n\t*nop\n\t*\.type\t*test_function} { target riscv*-*-* } } } */ + +/* RISC-V: No entry NOPs - function should start immediately with + .cfi_startproc. */ +/* { dg-final { scan-assembler {test_function:\n\.LFB[0-9]+:\n\t*\.cfi_startproc} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler-not {\t*\.weak\t*__kcfi_typeid_test_function\n} { target riscv*-*-* } } } */ + +/* RISC-V: KCFI type ID should have 1 alignment NOP then word. */ +/* { dg-final { scan-assembler {__cfi_test_function:\n\t*nop\n\t*\.word\t0x[0-9a-f]+} { target riscv*-*-* } } } */ + +/* Should have patchable function entry section. */ +/* { dg-final { scan-assembler "__patchable_function_entries" } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c new file mode 100644 index 000000000000..26323db4572f --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-pic-addressing.c @@ -0,0 +1,104 @@ +/* Test KCFI with position-independent code addressing modes. + This is a regression test for complex addressing like + PLUS(PLUS(...), symbol_ref) which can occur with PIC and caused + change_address_1 RTL errors. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2 -fpic" } */ + +/* Global function pointer table that creates PIC addressing. */ +struct callbacks { + int (*handler1)(int); + void (*handler2)(void); + int (*handler3)(int, int); +}; + +static int simple_handler(int x) { + return x * 2; +} + +static void void_handler(void) { + /* Empty handler. */ +} + +static int complex_handler(int a, int b) { + return a + b; +} + +/* Global structure that will require PIC addressing. */ +struct callbacks global_callbacks = { + .handler1 = simple_handler, + .handler2 = void_handler, + .handler3 = complex_handler +}; + +/* Function that uses PIC addressing to access global callbacks. */ +int test_pic_addressing(int value) { + /* These indirect calls through global structure create complex + addressing like PLUS(PLUS(GOT_base, symbol_offset), struct_offset) + which previously caused RTL errors in KCFI instrumentation. */ + + int result = 0; + result += global_callbacks.handler1(value); + + global_callbacks.handler2(); + + result += global_callbacks.handler3(value, result); + + return result; +} + +/* Test with function pointer arrays. */ +static int (*func_array[])(int) = { + simple_handler, + simple_handler, + simple_handler +}; + +int test_pic_array(int index, int value) { + /* Array access with PIC can also create complex addressing. */ + return func_array[index % 3](value); +} + +/* Test with dynamic PIC addressing. */ +struct callbacks *get_callbacks(void) { + return &global_callbacks; +} + +int test_dynamic_pic(int value) { + /* Dynamic access through function call creates very complex addressing. */ + struct callbacks *cb = get_callbacks(); + return cb->handler1(value) + cb->handler3(value, value); +} + +int main() { + int result = 0; + result += test_pic_addressing(10); + result += test_pic_array(1, 20); + result += test_dynamic_pic(5); + return result; +} + +/* Verify that all address-taken functions get KCFI preambles. */ +/* { dg-final { scan-assembler {__cfi_simple_handler:} } } */ +/* { dg-final { scan-assembler {__cfi_void_handler:} } } */ +/* { dg-final { scan-assembler {__cfi_complex_handler:} } } */ + +/* x86_64: Verify KCFI checks are generated. */ +/* { dg-final { scan-assembler {movl\t\$-?[0-9]+, %r10d\n\taddl\t-4\(%r[a-z0-9]+\), %r10d} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {ud2} { target x86_64-*-* } } } */ + +/* AArch64: Verify KCFI checks. */ +/* { dg-final { scan-assembler {ldur\tw16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler {brk} { target aarch64*-*-* } } } */ + +/* ARM 32-bit: Verify KCFI checks with PIC addressing and stack spilling. */ +/* { dg-final { scan-assembler {ldr\tr0, \[r[0-9]+, #-4\]} { target arm32 } } } */ +/* { dg-final { scan-assembler {udf} { target arm32 } } } */ + +/* RISC-V: Verify KCFI checks are generated. */ +/* { dg-final { scan-assembler {lw\tt1, -4\([a-z0-9]+\)\n\tlui\tt2, [0-9]+\n\taddiw\tt2, t2, -?[0-9]+} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler {ebreak} { target riscv*-*-* } } } */ + +/* Should have trap section. */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler {\.kcfi_traps} { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c new file mode 100644 index 000000000000..79e5ca61cdc2 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-retpoline-r11.c @@ -0,0 +1,50 @@ +/* Test KCFI with retpoline thunk-extern flag forces r11 usage. */ +/* { dg-do compile { target x86_64-*-* } } */ +/* { dg-additional-options "-O2 -mindirect-branch=thunk-extern" } */ + +extern int external_target(void); + +/* Test regular call (not tail call) */ +__attribute__((noinline)) +int call_test(int (*func_ptr)(void)) { + /* This indirect call should use r11 when both KCFI and + -mindirect-branch=thunk-extern are enabled. */ + int result = func_ptr(); /* Function parameter prevents direct optimization. */ + return result + 1; /* Prevent tail call optimization. */ +} + +/* Reference external_target to generate the required symbol. */ +int (*external_func_ptr)(void) = external_target; + +/* Test function for sibcalls (tail calls) */ +__attribute__((noinline)) +void sibcall_test(int (**func_ptr)(void)) { + /* This sibcall should use r11 when both KCFI and + -mindirect-branch=thunk-extern are enabled. */ + (*func_ptr)(); /* Tail call - should be optimized to sibcall. */ +} + +/* Should have weak symbol for external function. */ +/* { dg-final { scan-assembler "__kcfi_typeid_external_target" } } */ + +/* When both KCFI and -mindirect-branch=thunk-extern are enabled, + indirect calls should always use r11 register and convert to extern thunks. */ +/* { dg-final { scan-assembler-times {call\s+__x86_indirect_thunk_r11} 1 } } */ + +/* Sibcalls should also use r11 register and convert to extern thunks. */ +/* { dg-final { scan-assembler-times {jmp\s+__x86_indirect_thunk_r11} 1 } } */ + +/* Should have exactly 2 KCFI traps (one per function) */ +/* { dg-final { scan-assembler-times {ud2} 2 } } */ + +/* Should NOT use other registers for indirect calls. */ +/* { dg-final { scan-assembler-not {call\s+\*%rax} } } */ +/* { dg-final { scan-assembler-not {call\s+\*%rcx} } } */ +/* { dg-final { scan-assembler-not {call\s+\*%rdx} } } */ +/* { dg-final { scan-assembler-not {call\s+\*%rdi} } } */ + +/* Should NOT use other registers for sibcalls. */ +/* { dg-final { scan-assembler-not {jmp\s+\*%rax} } } */ +/* { dg-final { scan-assembler-not {jmp\s+\*%rcx} } } */ +/* { dg-final { scan-assembler-not {jmp\s+\*%rdx} } } */ +/* { dg-final { scan-assembler-not {jmp\s+\*%rdi} } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c new file mode 100644 index 000000000000..6ad8fab5da80 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-runtime.c @@ -0,0 +1,151 @@ +/* Test KCFI runtime behavior: working calls and type mismatch trapping. + { dg-do run { target native } } + { dg-options "-fsanitize=kcfi" } */ + +#include <stdio.h> +#include <signal.h> +#include <setjmp.h> +#include <stdlib.h> +#include <string.h> + +/* Test functions with different signatures */ +static int func_int_void(void) +{ + return 42; +} + +__attribute__((nocf_check)) +static int func_int_void_nocf_check(void) +{ + return 42; +} + +static int func_int_int(int x) +{ + return x * 4; +} + +/* Global state for signal handling */ +static volatile int trap_occurred = 0; +static jmp_buf trap_env; + +/* Signal handler for KCFI traps */ +static void trap_handler(int sig) +{ + trap_occurred = 1; + longjmp(trap_env, 1); +} + +/* Compatible indirect call should work */ +static int test_compatible_call(void) +{ + typedef int (*int_void_ptr)(void); + int_void_ptr ptr = func_int_void; + + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n", + __builtin_typeinfo_name(typeof(func_int_void)), + __builtin_typeinfo_hash(typeof(func_int_void)), + __builtin_typeinfo_name(typeof(*ptr)), + __builtin_typeinfo_hash(typeof(*ptr))); + + trap_occurred = 0; + /* This should work - same signature */ + int result = ptr(); + + return (trap_occurred == 0 && result == 42) ? 1 : 0; +} + +/* Compatible indirect call to nocf_check should not work */ +static int test_nocf_check_trap(void) +{ + trap_occurred = 0; + + if (setjmp(trap_env) == 0) { + typedef int (__attribute__((nocf_check)) *int_void_ptr_nocf)(void); + int_void_ptr_nocf ptr = func_int_void_nocf_check; + + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n", + __builtin_typeinfo_name(typeof(func_int_void_nocf_check)), + __builtin_typeinfo_hash(typeof(func_int_void_nocf_check)), + __builtin_typeinfo_name(typeof(*ptr)), + __builtin_typeinfo_hash(typeof(*ptr))); + + int result = ptr(); + + /* If we get here, the trap didn't occur */ + return 0; + } else { + /* We caught the trap - this is expected */ + return trap_occurred; + } +} + +/* Type mismatch should trap */ +static int test_type_mismatch_trap(void) +{ + trap_occurred = 0; + + if (setjmp(trap_env) == 0) { + /* Cast func_int_void to incompatible void(*)(void) type */ + typedef void (*void_void_ptr)(void); + void_void_ptr ptr = (void_void_ptr)func_int_void; + + fprintf(stderr, "Calling %s(0x%08x) through %s(0x%08x) ...\n", + __builtin_typeinfo_name(typeof(func_int_void)), + __builtin_typeinfo_hash(typeof(func_int_void)), + __builtin_typeinfo_name(typeof(*ptr)), + __builtin_typeinfo_hash(typeof(*ptr))); + + /* This should trap because type IDs don't match: + - func_int_void has type ID for int(void) + - but we're calling through void(void) pointer type */ + ptr(); + + /* If we get here, the trap didn't occur */ + return 0; + } else { + /* We caught the trap - this is expected */ + return trap_occurred; + } +} + +int main(void) +{ + struct sigaction sa = { + .sa_handler = trap_handler, + .sa_flags = SA_NODEFER, + }; + int failed = 3; + + /* Install trap handler. */ + if (sigaction(SIGILL, &sa, NULL)) { + perror("sigaction"); + return 1; + } + + /* Compatible call should work */ + if (test_compatible_call()) { + printf("OK: matched indirect call succeeded\n"); + failed--; + } else { + printf("FAIL\n"); + } + + /* Using nocf_check should trap */ + if (test_nocf_check_trap()) { + printf("OK: indirect call to nocf_check correctly trapped\n"); + failed--; + } else { + printf("FAIL\n"); + } + + /* Type mismatch should trap */ + if (test_type_mismatch_trap()) { + printf("OK: mismatched indirect call correctly trapped\n"); + failed--; + } else { + printf("FAIL\n"); + } + + return failed; +} diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c new file mode 100644 index 000000000000..e2e3912fffa3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-tail-calls.c @@ -0,0 +1,142 @@ +/* Test KCFI protection when indirect calls get converted to tail calls. */ +/* { dg-do compile } */ +/* { dg-additional-options "-O2" } */ + +typedef int (*func_ptr_t)(int); +typedef void (*void_func_ptr_t)(void); + +struct function_table { + func_ptr_t process; + void_func_ptr_t cleanup; +}; + +/* Target functions. */ +int process_data(int x) { return x * 2; } +void cleanup_data(void) {} + +/* Initialize function table. */ +volatile struct function_table vtable = { + .process = &process_data, + .cleanup = &cleanup_data +}; + +/* Indirect call through struct member that should become tail call. */ +int test_struct_indirect_call(int x) { + /* This is an indirect call that should be converted to tail call: + Without -fno-optimize-sibling-calls should become "jmp *vtable+0(%rip)" + With -fno-optimize-sibling-calls should become "call *vtable+0(%rip)" */ + return vtable.process(x); +} + +/* Indirect call through function pointer parameter. */ +int test_param_indirect_call(func_ptr_t handler, int x) { + /* This is an indirect call that should be converted to tail call: + Without -fno-optimize-sibling-calls should become "jmp *%rdi" + With -fno-optimize-sibling-calls should be "call *%rdi" */ + return handler(x); +} + +/* Void indirect call through struct member. */ +void test_void_indirect_call(void) { + /* This is an indirect call that should be converted to tail call: + * Without -fno-optimize-sibling-calls: should become "jmp *vtable+8(%rip)" + * With -fno-optimize-sibling-calls: should be "call *vtable+8(%rip)" */ + vtable.cleanup(); +} + +/* Non-tail call for comparison (should always be call). */ +int test_non_tail_indirect_call(func_ptr_t handler, int x) { + /* This should never become a tail call - always "call *%rdi" */ + int result = handler(x); + return result + 1; /* Prevents tail call optimization. */ +} + +/* Should have KCFI preambles for all functions. */ +/* { dg-final { scan-assembler-times "__cfi_process_data:" 1 } } */ +/* { dg-final { scan-assembler-times "__cfi_cleanup_data:" 1 } } */ +/* { dg-final { scan-assembler-times "__cfi_test_struct_indirect_call:" 1 } } */ +/* { dg-final { scan-assembler-times "__cfi_test_param_indirect_call:" 1 } } */ +/* { dg-final { scan-assembler-times "__cfi_test_void_indirect_call:" 1 } } */ +/* { dg-final { scan-assembler-times "__cfi_test_non_tail_indirect_call:" 1 } } */ + +/* Should have exactly 4 KCFI checks for indirect calls as + (load type ID + compare). */ +/* { dg-final { scan-assembler-times {movl\t\$-?[0-9]+, %r10d} 4 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times {addl\t-4\(%r[a-z0-9]+\), %r10d} 4 { target x86_64-*-* } } } */ + +/* Should have exactly 4 trap sections and 4 trap instructions. */ +/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times "ud2" 4 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times "\\.kcfi_traps" 4 { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler-times "ebreak" 4 { target riscv*-*-* } } } */ + +/* Should NOT have unprotected direct jumps to vtable. */ +/* { dg-final { scan-assembler-not {jmp\t\*vtable\(%rip\)} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-not {jmp\t\*vtable\+8\(%rip\)} { target x86_64-*-* } } } */ + +/* Should have exactly 3 protected tail calls (jmp through register after + KCFI check). */ +/* { dg-final { scan-assembler-times {jmp\t\*%[a-z0-9]+} 3 { target x86_64-*-* } } } */ + +/* Should have exactly 1 regular call (non-tail call case). */ +/* { dg-final { scan-assembler-times {call\t\*%[a-z0-9]+} 1 { target x86_64-*-* } } } */ + +/* RISC-V: Should have exactly 4 KCFI checks for indirect calls + (comparison instruction). */ +/* { dg-final { scan-assembler-times {beq\tt1, t2, \.Lkcfi_call[0-9]+} 4 { target riscv*-*-* } } } */ + +/* RISC-V: Should have exactly 4 KCFI checks for indirect calls as + (load type ID + compare). */ +/* { dg-final { scan-assembler-times {lw\tt1, -4\([a-z0-9]+\)} 4 { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler-times {lui\tt2, [0-9]+} 4 { target riscv*-*-* } } } */ + +/* RISC-V: Should have exactly 3 protected tail calls (jr after + KCFI check - no return address save). */ +/* { dg-final { scan-assembler-times {jalr\t(x0|zero), [a-z0-9]+, 0} 3 { target riscv*-*-* } } } */ + +/* RISC-V: Should have exactly 1 regular call (non-tail call case - saves + return address). */ +/* { dg-final { scan-assembler-times {jalr\t(x1|ra), [a-z0-9]+, 0} 1 { target riscv*-*-* } } } */ + +/* Type ID loading should use lui + addiw pattern for 32-bit constants. */ +/* { dg-final { scan-assembler {lui\tt2, [0-9]+} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler {addiw\tt2, t2, -?[0-9]+} { target riscv*-*-* } } } */ + +/* Should have exactly 4 KCFI checks for indirect calls (load type ID from + -4 offset + compare). */ +/* { dg-final { scan-assembler-times {ldur\tw16, \[x[0-9]+, #-4\]} 4 { target aarch64-*-* } } } */ +/* { dg-final { scan-assembler-times {cmp\tw16, w17} 4 { target aarch64-*-* } } } */ + +/* Should have exactly 4 trap instructions. */ +/* { dg-final { scan-assembler-times {brk\t#[0-9]+} 4 { target aarch64-*-* } } } */ + +/* Should have exactly 3 protected tail calls (br through register after + KCFI check). */ +/* { dg-final { scan-assembler-times {br\tx[0-9]+} 3 { target aarch64-*-* } } } */ + +/* Should have exactly 1 regular call (non-tail call case). */ +/* { dg-final { scan-assembler-times {blr\tx[0-9]+} 1 { target aarch64-*-* } } } */ + +/* Type ID loading should use mov + movk pattern for 32-bit constants. */ +/* { dg-final { scan-assembler {mov\tw17, #[0-9]+} { target aarch64-*-* } } } */ +/* { dg-final { scan-assembler {movk\tw17, #[0-9]+, lsl #16} { target aarch64-*-* } } } */ + +/* Should have exactly 4 KCFI checks for indirect calls (load type ID from + -4 offset + compare). */ +/* { dg-final { scan-assembler-times {ldr\tr0, \[r[0-9]+, #-4\]} 4 { target arm32 } } } */ +/* { dg-final { scan-assembler-times {cmp\tr0, r1} 4 { target arm32 } } } */ + +/* Should have exactly 4 trap instructions. */ +/* { dg-final { scan-assembler-times {udf\t#[0-9]+} 4 { target arm32 } } } */ + +/* Should have exactly 3 protected tail calls (bx through register after + KCFI check). */ +/* { dg-final { scan-assembler-times {bx\tr[0-9]+} 3 { target arm32 } } } */ + +/* Should have exactly 1 regular call (non-tail call case). */ +/* { dg-final { scan-assembler-times {blx\tr[0-9]+} 1 { target arm32 } } } */ + +/* Type ID loading should use movw + movt pattern for 32-bit constants + into r1. */ +/* { dg-final { scan-assembler {movw\tr1, #[0-9]+} { target arm32 } } } */ +/* { dg-final { scan-assembler {movt\tr1, #[0-9]+} { target arm32 } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c new file mode 100644 index 000000000000..f2226fa58ac9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-encoding.c @@ -0,0 +1,54 @@ +/* Test AArch64 and ARM32 KCFI trap encoding in BRK/UDF instructions. */ +/* { dg-do compile { target { aarch64*-*-* || arm32 } } } */ + +void target_function(int x, char y) { +} + +int main() { + void (*func_ptr)(int, char) = target_function; + + /* This should generate trap with immediate encoding. */ + func_ptr(42, 'a'); + + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_target_function:" } } */ + +/* AArch64 specific: Should have BRK instruction with proper ESR encoding + ESR format: 0x8000 | ((type_reg & 31) << 5) | (addr_reg & 31) + + Test the ESR encoding by checking for the expected value. + Since we know this test uses x2, we expect ESR = 0x8000 | (17<<5) | 2 = 33314 + + A truly dynamic test would need to extract the register from blr and compute + the corresponding ESR, but DejaGnu's regex limitations make this complex. + This test validates the specific case and documents the encoding. + */ +/* { dg-final { scan-assembler "blr\\s+x2" { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler "brk\\s+#33314" { target aarch64*-*-* } } } */ + +/* Should have KCFI check with type comparison. */ +/* { dg-final { scan-assembler {ldur\t*w16, \[x[0-9]+, #-4\]} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler {cmp\t*w16, w17} { target aarch64*-*-* } } } */ + +/* ARM32 specific: Should have UDF instruction with proper encoding + UDF format: 0x8000 | ((type_reg & 31) << 5) | (addr_reg & 31) + + Since ARM32 spills and restores r0/r1 before the trap, the type_reg + field uses 0x1F (31) to indicate "register was spilled" rather than + pointing to a live register. The addr_reg field contains the actual + target register number. + + For this test case using r3, we expect: + UDF = 0x8000 | (31 << 5) | 3 = 0x8000 | 0x3E0 | 3 = 33763 + */ +/* { dg-final { scan-assembler "blx\\s+r3" { target arm32 } } } */ +/* { dg-final { scan-assembler "udf\\s+#33763" { target arm32 } } } */ + +/* Should have register spilling and restoration around type check. */ +/* { dg-final { scan-assembler {push\t*\{r0, r1\}} { target arm32 } } } */ +/* { dg-final { scan-assembler {pop\t*\{r0, r1\}} { target arm32 } } } */ +/* { dg-final { scan-assembler {ldr\t*r0, \[r[0-9]+, #-4\]} { target arm32 } } } */ +/* { dg-final { scan-assembler {cmp\t*r0, r1} { target arm32 } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c new file mode 100644 index 000000000000..7f5f8a82f3dc --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi-trap-section.c @@ -0,0 +1,41 @@ +/* Test KCFI trap section generation. */ +/* { dg-do compile } */ + +void target_function(void) {} + +int main() { + void (*func_ptr)(void) = target_function; + + /* Multiple indirect calls to generate multiple trap entries. */ + func_ptr(); + func_ptr(); + + return 0; +} + +/* Should have KCFI preamble. */ +/* { dg-final { scan-assembler "__cfi_target_function:" } } */ + +/* Should have exactly 2 trap labels in code. */ +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ud2} 2 { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*brk} 2 { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*udf} 2 { target arm32 } } } */ +/* { dg-final { scan-assembler-times {\.L[^:]+:\n\s*ebreak} 2 { target riscv*-*-* } } } */ + +/* x86_64: Should have complete .kcfi_traps section sequence with relative + offset and 2 entries. */ +/* { dg-final { scan-assembler {\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry([^:]+):\n\t\.long\t\.Lkcfi_trap([^\s\n]+)-\.Lkcfi_entry\1\n\t\.text} { target x86_64-*-* } } } */ +/* { dg-final { scan-assembler-times {\.section\t\.kcfi_traps,"ao",@progbits,\.text} 2 { target x86_64-*-* } } } */ + +/* AArch64 should NOT have .kcfi_traps section (uses brk immediate instead) */ +/* { dg-final { scan-assembler-not {\.section\t+\.kcfi_traps} { target aarch64*-*-* } } } */ +/* { dg-final { scan-assembler-not {\.long.*-\.L} { target aarch64*-*-* } } } */ + +/* ARM 32-bit should NOT have .kcfi_traps section (uses udf immediate instead) */ +/* { dg-final { scan-assembler-not {\.section\t+\.kcfi_traps} { target arm32 } } } */ +/* { dg-final { scan-assembler-not {\.long.*-\.L} { target arm32 } } } */ + +/* RISC-V: Should have complete .kcfi_traps section sequence with relative + offset and 2 entries. */ +/* { dg-final { scan-assembler {\.section\t\.kcfi_traps,"ao",@progbits,\.text\n\.Lkcfi_entry([^:]+):\n\t\.4byte\t\.L([^\s\n]+)-\.Lkcfi_entry\1\n\t\.text} { target riscv*-*-* } } } */ +/* { dg-final { scan-assembler-times {\.section\t\.kcfi_traps,"ao",@progbits,\.text} 2 { target riscv*-*-* } } } */ diff --git a/gcc/testsuite/gcc.dg/kcfi/kcfi.exp b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp new file mode 100644 index 000000000000..0bbba196c82f --- /dev/null +++ b/gcc/testsuite/gcc.dg/kcfi/kcfi.exp @@ -0,0 +1,64 @@ +# Copyright (C) 2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + +# GCC testsuite for KCFI (Kernel Control Flow Integrity) tests. + +# Load support procs. +load_lib gcc-dg.exp + +# KCFI is only supported on specific targets +if { ![istarget "x86_64-*-*"] \ + && ![istarget "aarch64-*-*"] && ![istarget "arm*-*-*"] \ + && ![istarget "riscv*-*-*"] } { + return +} + +# Skip tests if x86_64 is running in 32-bit mode (-m32) +if { [istarget "x86_64-*-*"] && ![check_effective_target_lp64] } { + return +} + +# Skip tests if AArch64 is running in ILP32 mode (-mabi=ilp32) +if { [istarget "aarch64-*-*"] && ![check_effective_target_lp64] } { + return +} + +# Skip tests if RISC-V is running in 32-bit mode (riscv32-*) +if { [istarget "riscv*-*-*"] && ![check_effective_target_lp64] } { + return +} + +# Add KCFI-specific flags to any existing DEFAULT_CFLAGS +global DEFAULT_CFLAGS +if ![info exists DEFAULT_CFLAGS] then { + set DEFAULT_CFLAGS "" +} +set DEFAULT_CFLAGS "$DEFAULT_CFLAGS -fsanitize=kcfi" + +# Add ARM32-specific flags for arm32 targets +if [check_effective_target_arm32] { + set DEFAULT_CFLAGS "$DEFAULT_CFLAGS -march=armv7-a -mfloat-abi=soft" +} + +# Initialize `dg'. +dg-init + +# Main loop. +dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] \ + "" $DEFAULT_CFLAGS + +# All done. +dg-finish -- 2.34.1