Message ID | 20200831110450.30188-2-boyan.karatotev@arm.com |
---|---|
State | Superseded |
Headers | show |
Series | kselftests/arm64: add PAuth tests | expand |
Hi Boyan, On 8/31/20 4:34 PM, Boyan Karatotev wrote: > PAuth signs and verifies return addresses on the stack. It does so by > inserting a Pointer Authentication code (PAC) into some of the unused top > bits of an address. This is achieved by adding paciasp/autiasp instructions > at the beginning and end of a function. > > This feature is partially backwards compatible with earlier versions of the > ARM architecture. To coerce the compiler into emitting fully backwards > compatible code the main file is compiled to target an earlier ARM version. > This allows the tests to check for the feature and print meaningful error > messages instead of crashing. > > Add a test to verify that corrupting the return address results in a > SIGSEGV on return. > > Cc: Shuah Khan <shuah@kernel.org> > Cc: Catalin Marinas <catalin.marinas@arm.com> > Cc: Will Deacon <will@kernel.org> > Reviewed-by: Vincenzo Frascino <Vincenzo.Frascino@arm.com> > Reviewed-by: Amit Daniel Kachhap <amit.kachhap@arm.com> > Signed-off-by: Boyan Karatotev <boyan.karatotev@arm.com> > --- > tools/testing/selftests/arm64/Makefile | 2 +- [...] > + > +/* check that a corrupted PAC results in SIGSEGV */ > +TEST_SIGNAL(corrupt_pac, SIGSEGV) > +{ > + ASSERT_PAUTH_ENABLED(); > + > + pac_corruptor(); With 8.6-Pauth extension merged in arm tree [1]. It makes sense to verify PAC corruption for both SIGSEGV and SIGILL signals. Code something like below handles both the cases. ----------------------------------------------------------------------------------- int exec_sign_all(struct signatures *signed_vals, size_t val) @@ -187,12 +188,29 @@ int exec_sign_all(struct signatures *signed_vals, size_t val) return 0; } -/* check that a corrupted PAC results in SIGSEGV */ -TEST_SIGNAL(corrupt_pac, SIGSEGV) +sigjmp_buf jmpbuf; +void pac_signal_handler(int signum, siginfo_t *si, void *uc) { - ASSERT_PAUTH_ENABLED(); + if (signum == SIGSEGV || signum == SIGILL) { + siglongjmp(jmpbuf, 1); + } +} + +/* check that a corrupted PAC results in SIGSEGV or SIGILL */ +TEST(corrupt_pac) +{ + struct sigaction sa; - pac_corruptor(); + ASSERT_PAUTH_ENABLED(); + if (sigsetjmp(jmpbuf, 1) == 0) { + sa.sa_sigaction = pac_signal_handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGILL, &sa, NULL); + pac_corruptor(); + ASSERT_TRUE(0) TH_LOG("SIGSEGV/SIGILL signal did not occur"); + } } /* @@ -265,7 +283,7 @@ TEST(single_thread_different_keys) tmp = n_same_single_set(&signed_vals, nkeys); --------------------------------------------------------------------------------------- Thanks, Amit Daniel [1]: https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git/log/?h=for-next/ptrauth Regards, Amit Daniel > +} > + > +TEST_HARNESS_MAIN [...]
On 16/09/2020 1:11 pm, Amit Kachhap wrote: > On 8/31/20 4:34 PM, Boyan Karatotev wrote: >> PAuth signs and verifies return addresses on the stack. It does so by >> + >> +/* check that a corrupted PAC results in SIGSEGV */ >> +TEST_SIGNAL(corrupt_pac, SIGSEGV) >> +{ >> + ASSERT_PAUTH_ENABLED(); >> + >> + pac_corruptor(); > > With 8.6-Pauth extension merged in arm tree [1]. It makes sense to > verify PAC corruption for both SIGSEGV and SIGILL signals. > > Code something like below handles both the cases. > > ----------------------------------------------------------------------------------- > > int exec_sign_all(struct signatures *signed_vals, size_t val) > @@ -187,12 +188,29 @@ int exec_sign_all(struct signatures *signed_vals, > size_t val) > return 0; > } > > -/* check that a corrupted PAC results in SIGSEGV */ > -TEST_SIGNAL(corrupt_pac, SIGSEGV) > +sigjmp_buf jmpbuf; > +void pac_signal_handler(int signum, siginfo_t *si, void *uc) > { > - ASSERT_PAUTH_ENABLED(); > + if (signum == SIGSEGV || signum == SIGILL) { > + siglongjmp(jmpbuf, 1); > + } > +} > + > +/* check that a corrupted PAC results in SIGSEGV or SIGILL */ > +TEST(corrupt_pac) > +{ > + struct sigaction sa; > > - pac_corruptor(); > + ASSERT_PAUTH_ENABLED(); > + if (sigsetjmp(jmpbuf, 1) == 0) { > + sa.sa_sigaction = pac_signal_handler; > + sa.sa_flags = SA_SIGINFO; > + sigemptyset(&sa.sa_mask); > + sigaction(SIGSEGV, &sa, NULL); > + sigaction(SIGILL, &sa, NULL); > + pac_corruptor(); > + ASSERT_TRUE(0) TH_LOG("SIGSEGV/SIGILL signal did not > occur"); > + } > } > > /* > @@ -265,7 +283,7 @@ TEST(single_thread_different_keys) > > tmp = n_same_single_set(&signed_vals, nkeys); > --------------------------------------------------------------------------------------- > > > > Thanks, > Amit Daniel > > [1]: > https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git/log/?h=for-next/ptrauth Okay, I will add this and post it with the next version. Regards, Boyan
diff --git a/tools/testing/selftests/arm64/Makefile b/tools/testing/selftests/arm64/Makefile index 93b567d23c8b..525506fd97b9 100644 --- a/tools/testing/selftests/arm64/Makefile +++ b/tools/testing/selftests/arm64/Makefile @@ -4,7 +4,7 @@ ARCH ?= $(shell uname -m 2>/dev/null || echo not) ifneq (,$(filter $(ARCH),aarch64 arm64)) -ARM64_SUBTARGETS ?= tags signal +ARM64_SUBTARGETS ?= tags signal pauth else ARM64_SUBTARGETS := endif diff --git a/tools/testing/selftests/arm64/pauth/.gitignore b/tools/testing/selftests/arm64/pauth/.gitignore new file mode 100644 index 000000000000..b557c916720a --- /dev/null +++ b/tools/testing/selftests/arm64/pauth/.gitignore @@ -0,0 +1 @@ +pac diff --git a/tools/testing/selftests/arm64/pauth/Makefile b/tools/testing/selftests/arm64/pauth/Makefile new file mode 100644 index 000000000000..01d35aaa610a --- /dev/null +++ b/tools/testing/selftests/arm64/pauth/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2020 ARM Limited + +# preserve CC value from top level Makefile +ifeq ($(CC),cc) +CC := $(CROSS_COMPILE)gcc +endif + +CFLAGS += -mbranch-protection=pac-ret +# check if the compiler supports ARMv8.3 and branch protection with PAuth +pauth_cc_support := $(shell if ($(CC) $(CFLAGS) -march=armv8.3-a -E -x c /dev/null -o /dev/null 2>&1) then echo "1"; fi) + +ifeq ($(pauth_cc_support),1) +TEST_GEN_PROGS := pac +TEST_GEN_FILES := pac_corruptor.o +endif + +include ../../lib.mk + +ifeq ($(pauth_cc_support),1) +# pac* and aut* instructions are not available on architectures berfore +# ARMv8.3. Therefore target ARMv8.3 wherever they are used directly +$(OUTPUT)/pac_corruptor.o: pac_corruptor.S + $(CC) -c $^ -o $@ $(CFLAGS) -march=armv8.3-a + +# when -mbranch-protection is enabled and the target architecture is ARMv8.3 or +# greater, gcc emits pac* instructions which are not in HINT NOP space, +# preventing the tests from occurring at all. Compile for ARMv8.2 so tests can +# run on earlier targets and print a meaningful error messages +$(OUTPUT)/pac: pac.c $(OUTPUT)/pac_corruptor.o + $(CC) $^ -o $@ $(CFLAGS) -march=armv8.2-a +endif diff --git a/tools/testing/selftests/arm64/pauth/helper.h b/tools/testing/selftests/arm64/pauth/helper.h new file mode 100644 index 000000000000..3e0a2a404bf4 --- /dev/null +++ b/tools/testing/selftests/arm64/pauth/helper.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 ARM Limited */ + +#ifndef _HELPER_H_ +#define _HELPER_H_ + +void pac_corruptor(void); + +#endif diff --git a/tools/testing/selftests/arm64/pauth/pac.c b/tools/testing/selftests/arm64/pauth/pac.c new file mode 100644 index 000000000000..7fc02b02dede --- /dev/null +++ b/tools/testing/selftests/arm64/pauth/pac.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2020 ARM Limited + +#include <sys/auxv.h> +#include <signal.h> + +#include "../../kselftest_harness.h" +#include "helper.h" + +/* + * Tests are ARMv8.3 compliant. They make no provisions for features present in + * future version of the arm architecture + */ + +#define ASSERT_PAUTH_ENABLED() \ +do { \ + unsigned long hwcaps = getauxval(AT_HWCAP); \ + /* data key instructions are not in NOP space. This prevents a SIGILL */ \ + ASSERT_NE(0, hwcaps & HWCAP_PACA) TH_LOG("PAUTH not enabled"); \ +} while (0) + + +/* check that a corrupted PAC results in SIGSEGV */ +TEST_SIGNAL(corrupt_pac, SIGSEGV) +{ + ASSERT_PAUTH_ENABLED(); + + pac_corruptor(); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/arm64/pauth/pac_corruptor.S b/tools/testing/selftests/arm64/pauth/pac_corruptor.S new file mode 100644 index 000000000000..0780052ac3b5 --- /dev/null +++ b/tools/testing/selftests/arm64/pauth/pac_corruptor.S @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 ARM Limited */ + +.global pac_corruptor + +.text +/* + * Corrupting a single bit of the PAC ensures the authentication will fail. It + * also guarantees no possible collision. TCR_EL1.TBI0 is set by default so no + * top byte PAC is tested + */ + pac_corruptor: + paciasp + + /* make stack frame */ + sub sp, sp, #16 + stp x29, lr, [sp] + mov x29, sp + + /* prepare mask for bit to be corrupted (bit 54) */ + mov x1, xzr + add x1, x1, #1 + lsl x1, x1, #54 + + /* get saved lr, corrupt selected bit, put it back */ + ldr x0, [sp, #8] + eor x0, x0, x1 + str x0, [sp, #8] + + /* remove stack frame */ + ldp x29, lr, [sp] + add sp, sp, #16 + + autiasp + ret