From: Kees Cook <keescook@chromium.org>
To: Dan Li <ashimida@linux.alibaba.com>
Cc: akpm@linux-foundation.org, arnd@arndb.de,
catalin.marinas@arm.com, gregkh@linuxfoundation.org,
linux@roeck-us.net, luc.vanoostenryck@gmail.com,
elver@google.com, mark.rutland@arm.com, masahiroy@kernel.org,
ojeda@kernel.org, nathan@kernel.org, npiggin@gmail.com,
ndesaulniers@google.com, samitolvanen@google.com,
shuah@kernel.org, tglx@linutronix.de, will@kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
llvm@lists.linux.dev, linux-hardening@vger.kernel.org
Subject: Re: [PATCH v3 2/2] lkdtm: Add Shadow Call Stack tests
Date: Thu, 3 Mar 2022 10:42:45 -0800 [thread overview]
Message-ID: <202203031010.0A492D114@keescook> (raw)
In-Reply-To: <20220303074339.86337-1-ashimida@linux.alibaba.com>
On Wed, Mar 02, 2022 at 11:43:39PM -0800, Dan Li wrote:
> Add tests for SCS (Shadow Call Stack) based
> backward CFI (as implemented by Clang and GCC).
Cool; thanks for writing these!
>
> Signed-off-by: Dan Li <ashimida@linux.alibaba.com>
> ---
> drivers/misc/lkdtm/Makefile | 1 +
> drivers/misc/lkdtm/core.c | 2 +
> drivers/misc/lkdtm/lkdtm.h | 4 ++
> drivers/misc/lkdtm/scs.c | 67 +++++++++++++++++++++++++
> tools/testing/selftests/lkdtm/tests.txt | 2 +
> 5 files changed, 76 insertions(+)
> create mode 100644 drivers/misc/lkdtm/scs.c
>
> diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile
> index 2e0aa74ac185..e2fb17868af2 100644
> --- a/drivers/misc/lkdtm/Makefile
> +++ b/drivers/misc/lkdtm/Makefile
> @@ -10,6 +10,7 @@ lkdtm-$(CONFIG_LKDTM) += rodata_objcopy.o
> lkdtm-$(CONFIG_LKDTM) += usercopy.o
> lkdtm-$(CONFIG_LKDTM) += stackleak.o
> lkdtm-$(CONFIG_LKDTM) += cfi.o
> +lkdtm-$(CONFIG_LKDTM) += scs.o
I'd expect these to be in cfi.c, rather than making a new source file.
> lkdtm-$(CONFIG_LKDTM) += fortify.o
> lkdtm-$(CONFIG_PPC_64S_HASH_MMU) += powerpc.o
>
> diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
> index f69b964b9952..d0ce0bec117c 100644
> --- a/drivers/misc/lkdtm/core.c
> +++ b/drivers/misc/lkdtm/core.c
> @@ -178,6 +178,8 @@ static const struct crashtype crashtypes[] = {
> CRASHTYPE(USERCOPY_KERNEL),
> CRASHTYPE(STACKLEAK_ERASING),
> CRASHTYPE(CFI_FORWARD_PROTO),
> + CRASHTYPE(CFI_BACKWARD_SHADOW),
> + CRASHTYPE(CFI_BACKWARD_SHADOW_WITH_NOSCS),
> CRASHTYPE(FORTIFIED_OBJECT),
> CRASHTYPE(FORTIFIED_SUBOBJECT),
> CRASHTYPE(FORTIFIED_STRSCPY),
> diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
> index d6137c70ebbe..a23d32dfc10b 100644
> --- a/drivers/misc/lkdtm/lkdtm.h
> +++ b/drivers/misc/lkdtm/lkdtm.h
> @@ -158,6 +158,10 @@ void lkdtm_STACKLEAK_ERASING(void);
> /* cfi.c */
> void lkdtm_CFI_FORWARD_PROTO(void);
>
> +/* scs.c */
> +void lkdtm_CFI_BACKWARD_SHADOW(void);
> +void lkdtm_CFI_BACKWARD_SHADOW_WITH_NOSCS(void);
> +
> /* fortify.c */
> void lkdtm_FORTIFIED_OBJECT(void);
> void lkdtm_FORTIFIED_SUBOBJECT(void);
> diff --git a/drivers/misc/lkdtm/scs.c b/drivers/misc/lkdtm/scs.c
> new file mode 100644
> index 000000000000..5922a55a8844
> --- /dev/null
> +++ b/drivers/misc/lkdtm/scs.c
> @@ -0,0 +1,67 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This is for all the tests relating directly to Shadow Call Stack.
> + */
> +#include "lkdtm.h"
> +
> +#ifdef CONFIG_ARM64
> +/* Function clears its return address. */
> +static noinline void lkdtm_scs_clear_lr(void)
> +{
> + unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
> +
> + asm volatile("str xzr, [%0]\n\t" : : "r"(lr) : "x30");
Is the asm needed here? Why not:
unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
*lr = 0;
> +}
> +
> +/* Function with __noscs attribute clears its return address. */
> +static noinline void __noscs lkdtm_noscs_clear_lr(void)
> +{
> + unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
> +
> + asm volatile("str xzr, [%0]\n\t" : : "r"(lr) : "x30");
> +}
> +#endif
> +
> +/*
> + * This tries to call a function protected by Shadow Call Stack,
> + * which corrupts its own return address during execution.
> + * Due to the protection, the corruption will not take effect
> + * when the function returns.
> + */
> +void lkdtm_CFI_BACKWARD_SHADOW(void)
I think these two tests should be collapsed into a single one.
> +{
> +#ifdef CONFIG_ARM64
> + if (!IS_ENABLED(CONFIG_SHADOW_CALL_STACK)) {
> + pr_err("FAIL: kernel not built with CONFIG_SHADOW_CALL_STACK\n");
> + return;
> + }
> +
> + pr_info("Trying to corrupt lr in a function with scs protection ...\n");
> + lkdtm_scs_clear_lr();
> +
> + pr_err("ok: scs takes effect.\n");
> +#else
> + pr_err("XFAIL: this test is arm64-only\n");
> +#endif
This is slightly surprising -- we have no detection when a function has
its non-shadow-stack return address corrupted: it just _ignores_ the
value stored there. That seems like a missed opportunity for warning
about an unexpected state.
> +}
> +
> +/*
> + * This tries to call a function not protected by Shadow Call Stack,
> + * which corrupts its own return address during execution.
> + */
> +void lkdtm_CFI_BACKWARD_SHADOW_WITH_NOSCS(void)
> +{
> +#ifdef CONFIG_ARM64
> + if (!IS_ENABLED(CONFIG_SHADOW_CALL_STACK)) {
> + pr_err("FAIL: kernel not built with CONFIG_SHADOW_CALL_STACK\n");
> + return;
Other tests try to give some hints about failures, e.g.:
pr_err("FAIL: cannot change for SCS\n");
pr_expected_config(CONFIG_SHADOW_CALL_STACK);
Though, having the IS_ENABLED in there makes me wonder if this test
should instead be made _survivable_ on failure. Something like this,
completely untested:
#ifdef CONFIG_ARM64
static noinline void lkdtm_scs_set_lr(unsigned long *addr)
{
unsigned long **lr = (unsigned long **)__builtin_frame_address(0) + 1;
*lr = addr;
}
/* Function with __noscs attribute clears its return address. */
static noinline void __noscs lkdtm_noscs_set_lr(unsigned long *addr)
{
unsigned long **lr = (unsigned long **)__builtin_frame_address(0) + 1;
*lr = addr;
}
#endif
void lkdtm_CFI_BACKWARD_SHADOW(void)
{
#ifdef CONFIG_ARM64
/* Verify the "normal" condition of LR corruption working. */
do {
/* Keep label in scope to avoid compiler warning. */
if ((volatile int)0)
goto unexpected;
pr_info("Trying to corrupt lr in a function without scs protection ...\n");
lkdtm_noscs_set_lr(&&expected);
unexpected:
pr_err("XPASS: Unexpectedly survived lr corruption without scs?!\n");
break;
expected:
pr_err("ok: lr corruption redirected without scs.\n");
} while (0);
do {
/* Keep labe in scope to avoid compiler warning. */
if ((volatile int)0)
goto good_scs;
pr_info("Trying to corrupt lr in a function with scs protection ...\n");
lkdtm_scs_set_lr(&&bad_scs);
good_scs:
pr_info("ok: scs takes effect.\n");
break;
bad_scs:
pr_err("FAIL: return address rewritten!\n");
pr_expected_config(CONFIG_SHADOW_CALL_STACK);
} while (0);
#else
pr_err("XFAIL: this test is arm64-only\n");
#endif
}
And we should, actually, be able to make the "set_lr" functions be
arch-specific, leaving the test itself arch-agnostic....
--
Kees Cook
WARNING: multiple messages have this Message-ID (diff)
From: Kees Cook <keescook@chromium.org>
To: Dan Li <ashimida@linux.alibaba.com>
Cc: akpm@linux-foundation.org, arnd@arndb.de,
catalin.marinas@arm.com, gregkh@linuxfoundation.org,
linux@roeck-us.net, luc.vanoostenryck@gmail.com,
elver@google.com, mark.rutland@arm.com, masahiroy@kernel.org,
ojeda@kernel.org, nathan@kernel.org, npiggin@gmail.com,
ndesaulniers@google.com, samitolvanen@google.com,
shuah@kernel.org, tglx@linutronix.de, will@kernel.org,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
llvm@lists.linux.dev, linux-hardening@vger.kernel.org
Subject: Re: [PATCH v3 2/2] lkdtm: Add Shadow Call Stack tests
Date: Thu, 3 Mar 2022 10:42:45 -0800 [thread overview]
Message-ID: <202203031010.0A492D114@keescook> (raw)
In-Reply-To: <20220303074339.86337-1-ashimida@linux.alibaba.com>
On Wed, Mar 02, 2022 at 11:43:39PM -0800, Dan Li wrote:
> Add tests for SCS (Shadow Call Stack) based
> backward CFI (as implemented by Clang and GCC).
Cool; thanks for writing these!
>
> Signed-off-by: Dan Li <ashimida@linux.alibaba.com>
> ---
> drivers/misc/lkdtm/Makefile | 1 +
> drivers/misc/lkdtm/core.c | 2 +
> drivers/misc/lkdtm/lkdtm.h | 4 ++
> drivers/misc/lkdtm/scs.c | 67 +++++++++++++++++++++++++
> tools/testing/selftests/lkdtm/tests.txt | 2 +
> 5 files changed, 76 insertions(+)
> create mode 100644 drivers/misc/lkdtm/scs.c
>
> diff --git a/drivers/misc/lkdtm/Makefile b/drivers/misc/lkdtm/Makefile
> index 2e0aa74ac185..e2fb17868af2 100644
> --- a/drivers/misc/lkdtm/Makefile
> +++ b/drivers/misc/lkdtm/Makefile
> @@ -10,6 +10,7 @@ lkdtm-$(CONFIG_LKDTM) += rodata_objcopy.o
> lkdtm-$(CONFIG_LKDTM) += usercopy.o
> lkdtm-$(CONFIG_LKDTM) += stackleak.o
> lkdtm-$(CONFIG_LKDTM) += cfi.o
> +lkdtm-$(CONFIG_LKDTM) += scs.o
I'd expect these to be in cfi.c, rather than making a new source file.
> lkdtm-$(CONFIG_LKDTM) += fortify.o
> lkdtm-$(CONFIG_PPC_64S_HASH_MMU) += powerpc.o
>
> diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
> index f69b964b9952..d0ce0bec117c 100644
> --- a/drivers/misc/lkdtm/core.c
> +++ b/drivers/misc/lkdtm/core.c
> @@ -178,6 +178,8 @@ static const struct crashtype crashtypes[] = {
> CRASHTYPE(USERCOPY_KERNEL),
> CRASHTYPE(STACKLEAK_ERASING),
> CRASHTYPE(CFI_FORWARD_PROTO),
> + CRASHTYPE(CFI_BACKWARD_SHADOW),
> + CRASHTYPE(CFI_BACKWARD_SHADOW_WITH_NOSCS),
> CRASHTYPE(FORTIFIED_OBJECT),
> CRASHTYPE(FORTIFIED_SUBOBJECT),
> CRASHTYPE(FORTIFIED_STRSCPY),
> diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
> index d6137c70ebbe..a23d32dfc10b 100644
> --- a/drivers/misc/lkdtm/lkdtm.h
> +++ b/drivers/misc/lkdtm/lkdtm.h
> @@ -158,6 +158,10 @@ void lkdtm_STACKLEAK_ERASING(void);
> /* cfi.c */
> void lkdtm_CFI_FORWARD_PROTO(void);
>
> +/* scs.c */
> +void lkdtm_CFI_BACKWARD_SHADOW(void);
> +void lkdtm_CFI_BACKWARD_SHADOW_WITH_NOSCS(void);
> +
> /* fortify.c */
> void lkdtm_FORTIFIED_OBJECT(void);
> void lkdtm_FORTIFIED_SUBOBJECT(void);
> diff --git a/drivers/misc/lkdtm/scs.c b/drivers/misc/lkdtm/scs.c
> new file mode 100644
> index 000000000000..5922a55a8844
> --- /dev/null
> +++ b/drivers/misc/lkdtm/scs.c
> @@ -0,0 +1,67 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This is for all the tests relating directly to Shadow Call Stack.
> + */
> +#include "lkdtm.h"
> +
> +#ifdef CONFIG_ARM64
> +/* Function clears its return address. */
> +static noinline void lkdtm_scs_clear_lr(void)
> +{
> + unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
> +
> + asm volatile("str xzr, [%0]\n\t" : : "r"(lr) : "x30");
Is the asm needed here? Why not:
unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
*lr = 0;
> +}
> +
> +/* Function with __noscs attribute clears its return address. */
> +static noinline void __noscs lkdtm_noscs_clear_lr(void)
> +{
> + unsigned long *lr = (unsigned long *)__builtin_frame_address(0) + 1;
> +
> + asm volatile("str xzr, [%0]\n\t" : : "r"(lr) : "x30");
> +}
> +#endif
> +
> +/*
> + * This tries to call a function protected by Shadow Call Stack,
> + * which corrupts its own return address during execution.
> + * Due to the protection, the corruption will not take effect
> + * when the function returns.
> + */
> +void lkdtm_CFI_BACKWARD_SHADOW(void)
I think these two tests should be collapsed into a single one.
> +{
> +#ifdef CONFIG_ARM64
> + if (!IS_ENABLED(CONFIG_SHADOW_CALL_STACK)) {
> + pr_err("FAIL: kernel not built with CONFIG_SHADOW_CALL_STACK\n");
> + return;
> + }
> +
> + pr_info("Trying to corrupt lr in a function with scs protection ...\n");
> + lkdtm_scs_clear_lr();
> +
> + pr_err("ok: scs takes effect.\n");
> +#else
> + pr_err("XFAIL: this test is arm64-only\n");
> +#endif
This is slightly surprising -- we have no detection when a function has
its non-shadow-stack return address corrupted: it just _ignores_ the
value stored there. That seems like a missed opportunity for warning
about an unexpected state.
> +}
> +
> +/*
> + * This tries to call a function not protected by Shadow Call Stack,
> + * which corrupts its own return address during execution.
> + */
> +void lkdtm_CFI_BACKWARD_SHADOW_WITH_NOSCS(void)
> +{
> +#ifdef CONFIG_ARM64
> + if (!IS_ENABLED(CONFIG_SHADOW_CALL_STACK)) {
> + pr_err("FAIL: kernel not built with CONFIG_SHADOW_CALL_STACK\n");
> + return;
Other tests try to give some hints about failures, e.g.:
pr_err("FAIL: cannot change for SCS\n");
pr_expected_config(CONFIG_SHADOW_CALL_STACK);
Though, having the IS_ENABLED in there makes me wonder if this test
should instead be made _survivable_ on failure. Something like this,
completely untested:
#ifdef CONFIG_ARM64
static noinline void lkdtm_scs_set_lr(unsigned long *addr)
{
unsigned long **lr = (unsigned long **)__builtin_frame_address(0) + 1;
*lr = addr;
}
/* Function with __noscs attribute clears its return address. */
static noinline void __noscs lkdtm_noscs_set_lr(unsigned long *addr)
{
unsigned long **lr = (unsigned long **)__builtin_frame_address(0) + 1;
*lr = addr;
}
#endif
void lkdtm_CFI_BACKWARD_SHADOW(void)
{
#ifdef CONFIG_ARM64
/* Verify the "normal" condition of LR corruption working. */
do {
/* Keep label in scope to avoid compiler warning. */
if ((volatile int)0)
goto unexpected;
pr_info("Trying to corrupt lr in a function without scs protection ...\n");
lkdtm_noscs_set_lr(&&expected);
unexpected:
pr_err("XPASS: Unexpectedly survived lr corruption without scs?!\n");
break;
expected:
pr_err("ok: lr corruption redirected without scs.\n");
} while (0);
do {
/* Keep labe in scope to avoid compiler warning. */
if ((volatile int)0)
goto good_scs;
pr_info("Trying to corrupt lr in a function with scs protection ...\n");
lkdtm_scs_set_lr(&&bad_scs);
good_scs:
pr_info("ok: scs takes effect.\n");
break;
bad_scs:
pr_err("FAIL: return address rewritten!\n");
pr_expected_config(CONFIG_SHADOW_CALL_STACK);
} while (0);
#else
pr_err("XFAIL: this test is arm64-only\n");
#endif
}
And we should, actually, be able to make the "set_lr" functions be
arch-specific, leaving the test itself arch-agnostic....
--
Kees Cook
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
next prev parent reply other threads:[~2022-03-03 18:42 UTC|newest]
Thread overview: 32+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-03-03 7:33 [PATCH v3 0/2] AARCH64: Enable GCC-based Shadow Call Stack Dan Li
2022-03-03 7:33 ` Dan Li
2022-03-03 7:43 ` [PATCH v3 1/2] AARCH64: Add gcc Shadow Call Stack support Dan Li
2022-03-03 7:43 ` Dan Li
2022-03-10 18:15 ` (subset) " Kees Cook
2022-03-10 18:15 ` Kees Cook
2022-03-03 7:43 ` [PATCH v3 2/2] lkdtm: Add Shadow Call Stack tests Dan Li
2022-03-03 7:43 ` Dan Li
2022-03-03 18:42 ` Kees Cook [this message]
2022-03-03 18:42 ` Kees Cook
2022-03-03 19:09 ` Kees Cook
2022-03-03 19:09 ` Kees Cook
2022-03-04 14:54 ` Dan Li
2022-03-04 14:54 ` Dan Li
2022-03-04 15:01 ` Dan Li
2022-03-04 15:01 ` Dan Li
2022-03-07 15:16 ` Dan Li
2022-03-07 15:16 ` Dan Li
2022-03-09 20:16 ` Kees Cook
2022-03-09 20:16 ` Kees Cook
2022-03-11 2:46 ` Dan Li
2022-03-11 2:46 ` Dan Li
2022-03-04 14:34 ` Dan Li
2022-03-04 14:34 ` Dan Li
2022-03-14 13:53 ` [PATCH v4 " Dan Li
2022-03-14 13:53 ` Dan Li
2022-03-14 14:02 ` Dan Li
2022-03-14 14:02 ` Dan Li
2022-04-06 1:28 ` Dan Li
2022-04-06 1:28 ` Dan Li
2022-04-06 1:48 ` Dan Li
2022-04-06 1:48 ` Dan Li
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=202203031010.0A492D114@keescook \
--to=keescook@chromium.org \
--cc=akpm@linux-foundation.org \
--cc=arnd@arndb.de \
--cc=ashimida@linux.alibaba.com \
--cc=catalin.marinas@arm.com \
--cc=elver@google.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux@roeck-us.net \
--cc=llvm@lists.linux.dev \
--cc=luc.vanoostenryck@gmail.com \
--cc=mark.rutland@arm.com \
--cc=masahiroy@kernel.org \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=npiggin@gmail.com \
--cc=ojeda@kernel.org \
--cc=samitolvanen@google.com \
--cc=shuah@kernel.org \
--cc=tglx@linutronix.de \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.