2022-08-15 04:33:19

by Kirill A. Shutemov

[permalink] [raw]
Subject: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test cases for linear-address masking

From: Weihong Zhang <[email protected]>

LAM is supported only in 64-bit mode and applies only addresses used for data
accesses. In 64-bit mode, linear address have 64 bits. LAM is applied to 64-bit
linear address and allow software to use high bits for metadata.
LAM supports configurations that differ regarding which pointer bits are masked
and can be used for metadata.

LAM includes following mode:

- LAM_U57, pointer bits in positions 62:57 are masked (LAM width 6),
allows bits 62:57 of a user pointer to be used as metadata.

There are two arch_prctls:
ARCH_ENABLE_TAGGED_ADDR: enable LAM mode, mask high bits of a user pointer.
ARCH_GET_UNTAG_MASK: get current untagged mask.
ARCH_GET_MAX_TAG_BITS: the maximum tag bits user can request. zero if LAM
is not supported.

The LAM mode is for pre-process, a process has only one chance to set LAM mode.
But there is no API to disable LAM mode. So all of test cases are run under
child process.

Functions of this test:

MALLOC

- LAM_U57 masks bits 57:62 of a user pointer. Process on user space
can dereference such pointers.

- Disable LAM, dereference a pointer with metadata above 48 bit or 57 bit
lead to trigger SIGSEGV.

TAG_BITS

- Max tag bits of LAM_U57 is 6.

Signed-off-by: Weihong Zhang <[email protected]>
Signed-off-by: Kirill A. Shutemov <[email protected]>
---
tools/testing/selftests/x86/Makefile | 2 +-
tools/testing/selftests/x86/lam.c | 317 +++++++++++++++++++++++++++
2 files changed, 318 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/x86/lam.c

diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile
index 0388c4d60af0..c1a16a9d4f2f 100644
--- a/tools/testing/selftests/x86/Makefile
+++ b/tools/testing/selftests/x86/Makefile
@@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
- corrupt_xstate_header amx
+ corrupt_xstate_header amx lam
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall

diff --git a/tools/testing/selftests/x86/lam.c b/tools/testing/selftests/x86/lam.c
new file mode 100644
index 000000000000..4c6c6dbf7db6
--- /dev/null
+++ b/tools/testing/selftests/x86/lam.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syscall.h>
+#include <time.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <inttypes.h>
+
+#include "../kselftest.h"
+
+#ifndef __x86_64__
+# error This test is 64-bit only
+#endif
+
+/* LAM modes, these definitions were copied from kernel code */
+#define LAM_NONE 0
+#define LAM_U57_BITS 6
+/* arch prctl for LAM */
+#define ARCH_GET_UNTAG_MASK 0x4001
+#define ARCH_ENABLE_TAGGED_ADDR 0x4002
+#define ARCH_GET_MAX_TAG_BITS 0x4003
+
+/* Specified test function bits */
+#define FUNC_MALLOC 0x1
+#define FUNC_BITS 0x2
+
+#define TEST_MASK 0x3
+
+#define MALLOC_LEN 32
+
+struct testcases {
+ unsigned int later;
+ int expected; /* 2: SIGSEGV Error; 1: other errors */
+ unsigned long lam;
+ uint64_t addr;
+ int (*test_func)(struct testcases *test);
+ const char *msg;
+};
+
+int tests_cnt;
+jmp_buf segv_env;
+
+static void segv_handler(int sig)
+{
+ ksft_print_msg("Get segmentation fault(%d).", sig);
+ siglongjmp(segv_env, 1);
+}
+
+static inline int cpu_has_lam(void)
+{
+ unsigned int cpuinfo[4];
+
+ __cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
+
+ return (cpuinfo[0] & (1 << 26));
+}
+
+/*
+ * Set tagged address and read back untag mask.
+ * check if the untagged mask is expected.
+ */
+static int set_lam(unsigned long lam)
+{
+ int ret = 0;
+ uint64_t ptr = 0;
+
+ if (lam != LAM_U57_BITS && lam != LAM_NONE)
+ return -1;
+
+ /* Skip check return */
+ syscall(SYS_arch_prctl, ARCH_ENABLE_TAGGED_ADDR, lam);
+
+ /* Get untagged mask */
+ syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr);
+
+ /* Check mask returned is expected */
+ if (lam == LAM_U57_BITS)
+ ret = (ptr != ~(0x3fULL << 57));
+ else if (lam == LAM_NONE)
+ ret = (ptr != -1ULL);
+
+ return ret;
+}
+
+static unsigned long get_default_tag_bits(void)
+{
+ pid_t pid;
+ int lam = LAM_NONE;
+ int ret = 0;
+
+ pid = fork();
+ if (pid < 0) {
+ perror("Fork failed.");
+ ret = 1;
+ } else if (pid == 0) {
+ /* Set LAM mode in parent process */
+ if (set_lam(LAM_U57_BITS) == 0)
+ lam = LAM_U57_BITS;
+ else
+ lam = LAM_NONE;
+ exit(lam);
+ } else {
+ wait(&ret);
+ lam = WEXITSTATUS(ret);
+ }
+
+ return lam;
+}
+
+/* According to LAM mode, set metadata in high bits */
+static uint64_t get_metadata(uint64_t src, unsigned long lam)
+{
+ uint64_t metadata;
+
+ srand(time(NULL));
+ /* Get a random value as metadata */
+ metadata = rand();
+
+ switch (lam) {
+ case LAM_U57_BITS: /* Set metadata in bits 62:57 */
+ metadata = (src & ~(0x3fULL << 57)) | ((metadata & 0x3f) << 57);
+ break;
+ default:
+ metadata = src;
+ break;
+ }
+
+ return metadata;
+}
+
+/*
+ * Set metadata in user pointer, compare new pointer with original pointer.
+ * both pointers should point to the same address.
+ */
+static int handle_lam_test(void *src, unsigned int lam)
+{
+ char *ptr;
+
+ strcpy((char *)src, "USER POINTER");
+
+ ptr = (char *)get_metadata((uint64_t)src, lam);
+ if (src == ptr)
+ return 0;
+
+ /* Copy a string into the pointer with metadata */
+ strcpy((char *)ptr, "METADATA POINTER");
+
+ return (!!strcmp((char *)src, (char *)ptr));
+}
+
+
+int handle_max_bits(struct testcases *test)
+{
+ unsigned long exp_bits = get_default_tag_bits();
+ unsigned long bits = 0;
+
+ if (exp_bits != LAM_NONE)
+ exp_bits = LAM_U57_BITS;
+
+ /* Get LAM max tag bits */
+ if (syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits) == -1)
+ return 1;
+
+ return (exp_bits != bits);
+}
+
+/*
+ * Test lam feature through dereference pointer get from malloc.
+ * @return 0: Pass test. 1: Get failure during test 2: Get SIGSEGV
+ */
+static int handle_malloc(struct testcases *test)
+{
+ char *ptr = NULL;
+ int ret = 0;
+
+ if (test->later == 0 && test->lam != 0)
+ if (set_lam(test->lam) == -1)
+ return 1;
+
+ ptr = (char *)malloc(MALLOC_LEN);
+ if (ptr == NULL) {
+ perror("malloc() failure\n");
+ return 1;
+ }
+
+ /* Set signal handler */
+ if (sigsetjmp(segv_env, 1) == 0) {
+ signal(SIGSEGV, segv_handler);
+ ret = handle_lam_test(ptr, test->lam);
+ } else {
+ ret = 2;
+ }
+
+ if (test->later != 0 && test->lam != 0)
+ if (set_lam(test->lam) == -1 && ret == 0)
+ ret = 1;
+
+ free(ptr);
+
+ return ret;
+}
+
+static int fork_test(struct testcases *test)
+{
+ int ret, child_ret;
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ perror("Fork failed.");
+ ret = 1;
+ } else if (pid == 0) {
+ ret = test->test_func(test);
+ exit(ret);
+ } else {
+ wait(&child_ret);
+ ret = WEXITSTATUS(child_ret);
+ }
+
+ return ret;
+}
+
+static void run_test(struct testcases *test, int count)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < count; i++) {
+ struct testcases *t = test + i;
+
+ /* fork a process to run test case */
+ ret = fork_test(t);
+ if (ret != 0)
+ ret = (t->expected == ret);
+ else
+ ret = !(t->expected);
+
+ tests_cnt++;
+ ksft_test_result(ret, t->msg);
+ }
+}
+
+static struct testcases malloc_cases[] = {
+ {
+ .later = 0,
+ .lam = LAM_U57_BITS,
+ .test_func = handle_malloc,
+ .msg = "MALLOC: LAM_U57. Dereferencing pointer with metadata\n",
+ },
+ {
+ .later = 1,
+ .expected = 2,
+ .lam = LAM_U57_BITS,
+ .test_func = handle_malloc,
+ .msg = "MALLOC:[Negtive] Disable LAM. Dereferencing pointer with metadata.\n",
+ },
+};
+
+
+static struct testcases bits_cases[] = {
+ {
+ .test_func = handle_max_bits,
+ .msg = "BITS: Check default tag bits\n",
+ },
+};
+
+static void cmd_help(void)
+{
+ printf("usage: lam [-h] [-t test list]\n");
+ printf("\t-t test list: run tests specified in the test list, default:0x%x\n", TEST_MASK);
+ printf("\t\t0x1:malloc; 0x2:max_bits;\n");
+ printf("\t-h: help\n");
+}
+
+int main(int argc, char **argv)
+{
+ int c = 0;
+ unsigned int tests = TEST_MASK;
+
+ tests_cnt = 0;
+
+ if (!cpu_has_lam()) {
+ ksft_print_msg("Unsupported LAM feature!\n");
+ return -1;
+ }
+
+ while ((c = getopt(argc, argv, "ht:")) != -1) {
+ switch (c) {
+ case 't':
+ tests = strtoul(optarg, NULL, 16);
+ if (!(tests & TEST_MASK)) {
+ ksft_print_msg("Invalid argument!\n");
+ return -1;
+ }
+ break;
+ case 'h':
+ cmd_help();
+ return 0;
+ default:
+ ksft_print_msg("Invalid argument\n");
+ return -1;
+ }
+ }
+
+ if (tests & FUNC_MALLOC)
+ run_test(malloc_cases, ARRAY_SIZE(malloc_cases));
+
+ if (tests & FUNC_BITS)
+ run_test(bits_cases, ARRAY_SIZE(bits_cases));
+
+ ksft_set_plan(tests_cnt);
+
+ return ksft_exit_pass();
+}
--
2.35.1


2022-08-19 05:33:02

by Hu, Robert

[permalink] [raw]
Subject: RE: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test cases for linear-address masking

> -----Original Message-----
> From: Kirill A. Shutemov <[email protected]>
> Sent: Monday, August 15, 2022 12:18
> To: Dave Hansen <[email protected]>; Lutomirski, Andy
> <[email protected]>; Peter Zijlstra <[email protected]>
> Cc: [email protected]; Kostya Serebryany <[email protected]>; Andrey Ryabinin
> <[email protected]>; Andrey Konovalov <[email protected]>;
> Alexander Potapenko <[email protected]>; Taras Madan
> <[email protected]>; Dmitry Vyukov <[email protected]>; H . J . Lu
> <[email protected]>; Andi Kleen <[email protected]>; Edgecombe, Rick P
> <[email protected]>; [email protected]; linux-
> [email protected]; Zhang, Weihong <[email protected]>; Kirill A .
> Shutemov <[email protected]>
> Subject: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test cases
> for linear-address masking
>
> From: Weihong Zhang <[email protected]>
>
> LAM is supported only in 64-bit mode and applies only addresses used for data
> accesses. In 64-bit mode, linear address have 64 bits. LAM is applied to 64-bit
> linear address and allow software to use high bits for metadata.
> LAM supports configurations that differ regarding which pointer bits are
> masked and can be used for metadata.
>
> LAM includes following mode:
>
> - LAM_U57, pointer bits in positions 62:57 are masked (LAM width 6),
> allows bits 62:57 of a user pointer to be used as metadata.
>
> There are two arch_prctls:
> ARCH_ENABLE_TAGGED_ADDR: enable LAM mode, mask high bits of a user
> pointer.
> ARCH_GET_UNTAG_MASK: get current untagged mask.
> ARCH_GET_MAX_TAG_BITS: the maximum tag bits user can request. zero if
> LAM is not supported.
>
> The LAM mode is for pre-process, a process has only one chance to set LAM
> mode.
> But there is no API to disable LAM mode. So all of test cases are run under child
> process.
>
> Functions of this test:
>
> MALLOC
>
> - LAM_U57 masks bits 57:62 of a user pointer. Process on user space
> can dereference such pointers.
>
> - Disable LAM, dereference a pointer with metadata above 48 bit or 57 bit
> lead to trigger SIGSEGV.
>
> TAG_BITS
>
> - Max tag bits of LAM_U57 is 6.
>
> Signed-off-by: Weihong Zhang <[email protected]>
> Signed-off-by: Kirill A. Shutemov <[email protected]>
> ---
> tools/testing/selftests/x86/Makefile | 2 +-
> tools/testing/selftests/x86/lam.c | 317 +++++++++++++++++++++++++++
> 2 files changed, 318 insertions(+), 1 deletion(-) create mode 100644
> tools/testing/selftests/x86/lam.c
>
> diff --git a/tools/testing/selftests/x86/Makefile
> b/tools/testing/selftests/x86/Makefile
> index 0388c4d60af0..c1a16a9d4f2f 100644
> --- a/tools/testing/selftests/x86/Makefile
> +++ b/tools/testing/selftests/x86/Makefile
> @@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86
> test_syscall_vdso unwind_vdso \
> test_FCMOV test_FCOMI test_FISTTP \
> vdso_restorer
> TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
> - corrupt_xstate_header amx
> + corrupt_xstate_header amx lam
> # Some selftests require 32bit support enabled also on 64bit systems
> TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
>
> diff --git a/tools/testing/selftests/x86/lam.c b/tools/testing/selftests/x86/lam.c
> new file mode 100644
> index 000000000000..4c6c6dbf7db6
> --- /dev/null
> +++ b/tools/testing/selftests/x86/lam.c
> @@ -0,0 +1,317 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/syscall.h>
> +#include <time.h>
> +#include <signal.h>
> +#include <setjmp.h>
> +#include <sys/mman.h>
> +#include <sys/wait.h>
> +#include <inttypes.h>
> +
> +#include "../kselftest.h"
> +
> +#ifndef __x86_64__
> +# error This test is 64-bit only
> +#endif
> +
> +/* LAM modes, these definitions were copied from kernel code */
> +#define LAM_NONE 0
> +#define LAM_U57_BITS 6
> +/* arch prctl for LAM */
> +#define ARCH_GET_UNTAG_MASK 0x4001
> +#define ARCH_ENABLE_TAGGED_ADDR 0x4002
> +#define ARCH_GET_MAX_TAG_BITS 0x4003
> +
> +/* Specified test function bits */
> +#define FUNC_MALLOC 0x1
> +#define FUNC_BITS 0x2
> +
> +#define TEST_MASK 0x3
> +
> +#define MALLOC_LEN 32
> +
> +struct testcases {
> + unsigned int later;
> + int expected; /* 2: SIGSEGV Error; 1: other errors */
> + unsigned long lam;
> + uint64_t addr;
> + int (*test_func)(struct testcases *test);
> + const char *msg;
> +};
> +
> +int tests_cnt;
> +jmp_buf segv_env;
> +
> +static void segv_handler(int sig)
> +{
> + ksft_print_msg("Get segmentation fault(%d).", sig);
> + siglongjmp(segv_env, 1);
> +}
> +
> +static inline int cpu_has_lam(void)
> +{
> + unsigned int cpuinfo[4];
> +
> + __cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]);
> +
> + return (cpuinfo[0] & (1 << 26));
> +}
> +
> +/*
> + * Set tagged address and read back untag mask.
> + * check if the untagged mask is expected.
> + */
> +static int set_lam(unsigned long lam)
> +{
> + int ret = 0;
> + uint64_t ptr = 0;
> +
> + if (lam != LAM_U57_BITS && lam != LAM_NONE)
> + return -1;
> +
> + /* Skip check return */
> + syscall(SYS_arch_prctl, ARCH_ENABLE_TAGGED_ADDR, lam);
> +
> + /* Get untagged mask */
> + syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr);
> +
> + /* Check mask returned is expected */
> + if (lam == LAM_U57_BITS)
> + ret = (ptr != ~(0x3fULL << 57));

[Hu, Robert]
Any special reason not "bool ret"?

> + else if (lam == LAM_NONE)
> + ret = (ptr != -1ULL);
> +
> + return ret;
> +}
> +
> +static unsigned long get_default_tag_bits(void) {
> + pid_t pid;
> + int lam = LAM_NONE;
> + int ret = 0;
> +
> + pid = fork();
> + if (pid < 0) {
> + perror("Fork failed.");
> + ret = 1;
[Hu, Robert]
In this fault case, at last you "return lam", which inited as 0, not your intended "ret".

> + } else if (pid == 0) {
> + /* Set LAM mode in parent process */
[Hu, Robert]
pid == 0 is child process?

> + if (set_lam(LAM_U57_BITS) == 0)
> + lam = LAM_U57_BITS;
> + else
> + lam = LAM_NONE;
> + exit(lam);
> + } else {
> + wait(&ret);
> + lam = WEXITSTATUS(ret);
> + }
> +
> + return lam;
> +}
> +
> +/* According to LAM mode, set metadata in high bits */ static uint64_t
> +get_metadata(uint64_t src, unsigned long lam) {
[Hu, Robert]
This function looks like not "get metadata", but "embed metadata" to canonical address.
Naming looks confusing. Perhaps embed_metadata()?

> + uint64_t metadata;
> +
> + srand(time(NULL));
> + /* Get a random value as metadata */
> + metadata = rand();
> +
> + switch (lam) {
> + case LAM_U57_BITS: /* Set metadata in bits 62:57 */
> + metadata = (src & ~(0x3fULL << 57)) | ((metadata & 0x3f) <<
[Hu, Robert]
Looks like "0x3fULL << 57" is frequently used across LAM selftests, why not name a macro
for it?

> 57);
> + break;
> + default:
> + metadata = src;
> + break;
> + }
> +
> + return metadata;
> +}
> +
> +/*
> + * Set metadata in user pointer, compare new pointer with original pointer.
> + * both pointers should point to the same address.
> + */
> +static int handle_lam_test(void *src, unsigned int lam) {
> + char *ptr;
> +
> + strcpy((char *)src, "USER POINTER");
> +
> + ptr = (char *)get_metadata((uint64_t)src, lam);
> + if (src == ptr)
> + return 0;
> +
> + /* Copy a string into the pointer with metadata */
> + strcpy((char *)ptr, "METADATA POINTER");
> +
> + return (!!strcmp((char *)src, (char *)ptr)); }
[Hu, Robert]
Why "!!" here? now that function return type is int and strcmp() returns int.

> +
> +
> +int handle_max_bits(struct testcases *test) {
> + unsigned long exp_bits = get_default_tag_bits();
> + unsigned long bits = 0;
> +
> + if (exp_bits != LAM_NONE)
> + exp_bits = LAM_U57_BITS;
> +
> + /* Get LAM max tag bits */
> + if (syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits) == -1)
> + return 1;
> +
> + return (exp_bits != bits);
> +}
> +
> +/*
> + * Test lam feature through dereference pointer get from malloc.
> + * @return 0: Pass test. 1: Get failure during test 2: Get SIGSEGV */
> +static int handle_malloc(struct testcases *test) {
> + char *ptr = NULL;
> + int ret = 0;
> +
> + if (test->later == 0 && test->lam != 0)
> + if (set_lam(test->lam) == -1)
> + return 1;
> +
> + ptr = (char *)malloc(MALLOC_LEN);
> + if (ptr == NULL) {
> + perror("malloc() failure\n");
> + return 1;
> + }
> +
> + /* Set signal handler */
> + if (sigsetjmp(segv_env, 1) == 0) {
> + signal(SIGSEGV, segv_handler);
> + ret = handle_lam_test(ptr, test->lam);
> + } else {
> + ret = 2;
> + }
> +
> + if (test->later != 0 && test->lam != 0)
> + if (set_lam(test->lam) == -1 && ret == 0)
> + ret = 1;
> +
> + free(ptr);
> +
> + return ret;
> +}
> +
> +static int fork_test(struct testcases *test) {
> + int ret, child_ret;
> + pid_t pid;
> +
> + pid = fork();
> + if (pid < 0) {
> + perror("Fork failed.");
> + ret = 1;
> + } else if (pid == 0) {
> + ret = test->test_func(test);
> + exit(ret);
> + } else {
> + wait(&child_ret);
> + ret = WEXITSTATUS(child_ret);
> + }
> +
> + return ret;
> +}
> +
> +static void run_test(struct testcases *test, int count) {
> + int i, ret = 0;
> +
> + for (i = 0; i < count; i++) {
> + struct testcases *t = test + i;
> +
> + /* fork a process to run test case */
> + ret = fork_test(t);
> + if (ret != 0)
> + ret = (t->expected == ret);
> + else
> + ret = !(t->expected);
> +
> + tests_cnt++;
> + ksft_test_result(ret, t->msg);
> + }
> +}
> +
> +static struct testcases malloc_cases[] = {
> + {
> + .later = 0,
> + .lam = LAM_U57_BITS,
> + .test_func = handle_malloc,
> + .msg = "MALLOC: LAM_U57. Dereferencing pointer with
> metadata\n",
> + },
> + {
> + .later = 1,
> + .expected = 2,
> + .lam = LAM_U57_BITS,
> + .test_func = handle_malloc,
> + .msg = "MALLOC:[Negtive] Disable LAM. Dereferencing pointer
> with metadata.\n",
> + },
> +};
> +
> +
> +static struct testcases bits_cases[] = {
> + {
> + .test_func = handle_max_bits,
> + .msg = "BITS: Check default tag bits\n",
> + },
> +};
> +
> +static void cmd_help(void)
> +{
> + printf("usage: lam [-h] [-t test list]\n");
> + printf("\t-t test list: run tests specified in the test list, default:0x%x\n",
> TEST_MASK);
> + printf("\t\t0x1:malloc; 0x2:max_bits;\n");
> + printf("\t-h: help\n");
> +}
> +
> +int main(int argc, char **argv)
> +{
> + int c = 0;
> + unsigned int tests = TEST_MASK;
> +
> + tests_cnt = 0;
> +
> + if (!cpu_has_lam()) {
> + ksft_print_msg("Unsupported LAM feature!\n");
> + return -1;
> + }
> +
> + while ((c = getopt(argc, argv, "ht:")) != -1) {
> + switch (c) {
> + case 't':
> + tests = strtoul(optarg, NULL, 16);
> + if (!(tests & TEST_MASK)) {
> + ksft_print_msg("Invalid argument!\n");
> + return -1;
> + }
> + break;
> + case 'h':
> + cmd_help();
> + return 0;
> + default:
> + ksft_print_msg("Invalid argument\n");
> + return -1;
> + }
> + }
> +
> + if (tests & FUNC_MALLOC)
> + run_test(malloc_cases, ARRAY_SIZE(malloc_cases));
> +
> + if (tests & FUNC_BITS)
> + run_test(bits_cases, ARRAY_SIZE(bits_cases));
> +
> + ksft_set_plan(tests_cnt);
> +
> + return ksft_exit_pass();
> +}
> --
> 2.35.1

2022-08-22 05:42:44

by Zhang, Weihong

[permalink] [raw]
Subject: RE: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test cases for linear-address masking



> -----Original Message-----
> From: Hu, Robert <[email protected]>
> Sent: Friday, August 19, 2022 1:17 PM
> To: Kirill A. Shutemov <[email protected]>; Dave Hansen
> <[email protected]>; Lutomirski, Andy <[email protected]>; Peter
> Zijlstra <[email protected]>
> Cc: [email protected]; Kostya Serebryany <[email protected]>; Andrey Ryabinin
> <[email protected]>; Andrey Konovalov <[email protected]>;
> Alexander Potapenko <[email protected]>; Taras Madan
> <[email protected]>; Dmitry Vyukov <[email protected]>; H . J .
> Lu <[email protected]>; Andi Kleen <[email protected]>; Edgecombe,
> Rick P <[email protected]>; [email protected]; linux-
> [email protected]; Zhang, Weihong <[email protected]>
> Subject: RE: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test
> cases for linear-address masking
>
> > -----Original Message-----
> > From: Kirill A. Shutemov <[email protected]>
> > Sent: Monday, August 15, 2022 12:18
> > To: Dave Hansen <[email protected]>; Lutomirski, Andy
> > <[email protected]>; Peter Zijlstra <[email protected]>
> > Cc: [email protected]; Kostya Serebryany <[email protected]>; Andrey
> > Ryabinin <[email protected]>; Andrey Konovalov
> > <[email protected]>; Alexander Potapenko <[email protected]>;
> Taras
> > Madan <[email protected]>; Dmitry Vyukov
> <[email protected]>; H .
> > J . Lu <[email protected]>; Andi Kleen <[email protected]>;
> > Edgecombe, Rick P <[email protected]>; [email protected];
> > linux- [email protected]; Zhang, Weihong
> <[email protected]>; Kirill A .
> > Shutemov <[email protected]>
> > Subject: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits
> > test cases for linear-address masking
> >
> > From: Weihong Zhang <[email protected]>
> >
> > LAM is supported only in 64-bit mode and applies only addresses used
> > for data accesses. In 64-bit mode, linear address have 64 bits. LAM is
> > applied to 64-bit linear address and allow software to use high bits for
> metadata.
> > LAM supports configurations that differ regarding which pointer bits
> > are masked and can be used for metadata.
> >
> > LAM includes following mode:
> >
> > - LAM_U57, pointer bits in positions 62:57 are masked (LAM width 6),
> > allows bits 62:57 of a user pointer to be used as metadata.
> >
> > There are two arch_prctls:
> > ARCH_ENABLE_TAGGED_ADDR: enable LAM mode, mask high bits of a
> user
> > pointer.
> > ARCH_GET_UNTAG_MASK: get current untagged mask.
> > ARCH_GET_MAX_TAG_BITS: the maximum tag bits user can request. zero
> if
> > LAM is not supported.
> >
> > The LAM mode is for pre-process, a process has only one chance to set
> > LAM mode.
> > But there is no API to disable LAM mode. So all of test cases are run
> > under child process.
> >
> > Functions of this test:
> >
> > MALLOC
> >
> > - LAM_U57 masks bits 57:62 of a user pointer. Process on user space
> > can dereference such pointers.
> >
> > - Disable LAM, dereference a pointer with metadata above 48 bit or 57 bit
> > lead to trigger SIGSEGV.
> >
> > TAG_BITS
> >
> > - Max tag bits of LAM_U57 is 6.
> >
> > Signed-off-by: Weihong Zhang <[email protected]>
> > Signed-off-by: Kirill A. Shutemov <[email protected]>
> > ---
> > tools/testing/selftests/x86/Makefile | 2 +-
> > tools/testing/selftests/x86/lam.c | 317
> +++++++++++++++++++++++++++
> > 2 files changed, 318 insertions(+), 1 deletion(-) create mode 100644
> > tools/testing/selftests/x86/lam.c
> >
> > diff --git a/tools/testing/selftests/x86/Makefile
> > b/tools/testing/selftests/x86/Makefile
> > index 0388c4d60af0..c1a16a9d4f2f 100644
> > --- a/tools/testing/selftests/x86/Makefile
> > +++ b/tools/testing/selftests/x86/Makefile
> > @@ -18,7 +18,7 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86
> > test_syscall_vdso unwind_vdso \
> > test_FCMOV test_FCOMI test_FISTTP \
> > vdso_restorer
> > TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
> > - corrupt_xstate_header amx
> > + corrupt_xstate_header amx lam
> > # Some selftests require 32bit support enabled also on 64bit systems
> > TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
> >
> > diff --git a/tools/testing/selftests/x86/lam.c
> > b/tools/testing/selftests/x86/lam.c
> > new file mode 100644
> > index 000000000000..4c6c6dbf7db6
> > --- /dev/null
> > +++ b/tools/testing/selftests/x86/lam.c
> > @@ -0,0 +1,317 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <sys/syscall.h>
> > +#include <time.h>
> > +#include <signal.h>
> > +#include <setjmp.h>
> > +#include <sys/mman.h>
> > +#include <sys/wait.h>
> > +#include <inttypes.h>
> > +
> > +#include "../kselftest.h"
> > +
> > +#ifndef __x86_64__
> > +# error This test is 64-bit only
> > +#endif
> > +
> > +/* LAM modes, these definitions were copied from kernel code */
> > +#define LAM_NONE 0
> > +#define LAM_U57_BITS 6
> > +/* arch prctl for LAM */
> > +#define ARCH_GET_UNTAG_MASK 0x4001
> > +#define ARCH_ENABLE_TAGGED_ADDR 0x4002
> > +#define ARCH_GET_MAX_TAG_BITS 0x4003
> > +
> > +/* Specified test function bits */
> > +#define FUNC_MALLOC 0x1
> > +#define FUNC_BITS 0x2
> > +
> > +#define TEST_MASK 0x3
> > +
> > +#define MALLOC_LEN 32
> > +
> > +struct testcases {
> > + unsigned int later;
> > + int expected; /* 2: SIGSEGV Error; 1: other errors */
> > + unsigned long lam;
> > + uint64_t addr;
> > + int (*test_func)(struct testcases *test);
> > + const char *msg;
> > +};
> > +
> > +int tests_cnt;
> > +jmp_buf segv_env;
> > +
> > +static void segv_handler(int sig)
> > +{
> > + ksft_print_msg("Get segmentation fault(%d).", sig);
> > + siglongjmp(segv_env, 1);
> > +}
> > +
> > +static inline int cpu_has_lam(void)
> > +{
> > + unsigned int cpuinfo[4];
> > +
> > + __cpuid_count(0x7, 1, cpuinfo[0], cpuinfo[1], cpuinfo[2],
> > +cpuinfo[3]);
> > +
> > + return (cpuinfo[0] & (1 << 26));
> > +}
> > +
> > +/*
> > + * Set tagged address and read back untag mask.
> > + * check if the untagged mask is expected.
> > + */
> > +static int set_lam(unsigned long lam) {
> > + int ret = 0;
> > + uint64_t ptr = 0;
> > +
> > + if (lam != LAM_U57_BITS && lam != LAM_NONE)
> > + return -1;
> > +
> > + /* Skip check return */
> > + syscall(SYS_arch_prctl, ARCH_ENABLE_TAGGED_ADDR, lam);
> > +
> > + /* Get untagged mask */
> > + syscall(SYS_arch_prctl, ARCH_GET_UNTAG_MASK, &ptr);
> > +
> > + /* Check mask returned is expected */
> > + if (lam == LAM_U57_BITS)
> > + ret = (ptr != ~(0x3fULL << 57));
>
> [Hu, Robert]
> Any special reason not "bool ret"?
>
The code didn't involve the type: bool.
error: unknown type name 'bool'

> > + else if (lam == LAM_NONE)
> > + ret = (ptr != -1ULL);
> > +
> > + return ret;
> > +}
> > +
> > +static unsigned long get_default_tag_bits(void) {
> > + pid_t pid;
> > + int lam = LAM_NONE;
> > + int ret = 0;
> > +
> > + pid = fork();
> > + if (pid < 0) {
> > + perror("Fork failed.");
> > + ret = 1;
> [Hu, Robert]
> In this fault case, at last you "return lam", which inited as 0, not your
> intended "ret".
>
Yes, "ret" in here is redundant, will be optimized.

> > + } else if (pid == 0) {
> > + /* Set LAM mode in parent process */
> [Hu, Robert]
> pid == 0 is child process?
>
> > + if (set_lam(LAM_U57_BITS) == 0)
> > + lam = LAM_U57_BITS;
> > + else
> > + lam = LAM_NONE;
> > + exit(lam);
> > + } else {
> > + wait(&ret);
> > + lam = WEXITSTATUS(ret);
> > + }
> > +
> > + return lam;
> > +}
> > +
> > +/* According to LAM mode, set metadata in high bits */ static
> > +uint64_t get_metadata(uint64_t src, unsigned long lam) {
> [Hu, Robert]
> This function looks like not "get metadata", but "embed metadata" to
> canonical address.
> Naming looks confusing. Perhaps embed_metadata()?
>
The name does not express rightly the purpose of this function.
Will rename the function.

> > + uint64_t metadata;
> > +
> > + srand(time(NULL));
> > + /* Get a random value as metadata */
> > + metadata = rand();
> > +
> > + switch (lam) {
> > + case LAM_U57_BITS: /* Set metadata in bits 62:57 */
> > + metadata = (src & ~(0x3fULL << 57)) | ((metadata & 0x3f) <<
> [Hu, Robert]
> Looks like "0x3fULL << 57" is frequently used across LAM selftests, why not
> name a macro for "0x3fULL << 57"?
>
Add a macro for (0x3fULL << 57) and (0x7fffULL << 48)?.

> > 57);
> > + break;
> > + default:
> > + metadata = src;
> > + break;
> > + }
> > +
> > + return metadata;
> > +}
> > +
> > +/*
> > + * Set metadata in user pointer, compare new pointer with original
> pointer.
> > + * both pointers should point to the same address.
> > + */
> > +static int handle_lam_test(void *src, unsigned int lam) {
> > + char *ptr;
> > +
> > + strcpy((char *)src, "USER POINTER");
> > +
> > + ptr = (char *)get_metadata((uint64_t)src, lam);
> > + if (src == ptr)
> > + return 0;
> > +
> > + /* Copy a string into the pointer with metadata */
> > + strcpy((char *)ptr, "METADATA POINTER");
> > +
> > + return (!!strcmp((char *)src, (char *)ptr)); }
> [Hu, Robert]
> Why "!!" here? now that function return type is int and strcmp() returns int.
>
The " handle_lam_test" only returns 0 and 1. "!!" used to convert return of strcmp to 0 or 1.
> > +
> > +
> > +int handle_max_bits(struct testcases *test) {
> > + unsigned long exp_bits = get_default_tag_bits();
> > + unsigned long bits = 0;
> > +
> > + if (exp_bits != LAM_NONE)
> > + exp_bits = LAM_U57_BITS;
> > +
> > + /* Get LAM max tag bits */
> > + if (syscall(SYS_arch_prctl, ARCH_GET_MAX_TAG_BITS, &bits) == -1)
> > + return 1;
> > +
> > + return (exp_bits != bits);
> > +}
> > +
> > +/*
> > + * Test lam feature through dereference pointer get from malloc.
> > + * @return 0: Pass test. 1: Get failure during test 2: Get SIGSEGV
> > +*/ static int handle_malloc(struct testcases *test) {
> > + char *ptr = NULL;
> > + int ret = 0;
> > +
> > + if (test->later == 0 && test->lam != 0)
> > + if (set_lam(test->lam) == -1)
> > + return 1;
> > +
> > + ptr = (char *)malloc(MALLOC_LEN);
> > + if (ptr == NULL) {
> > + perror("malloc() failure\n");
> > + return 1;
> > + }
> > +
> > + /* Set signal handler */
> > + if (sigsetjmp(segv_env, 1) == 0) {
> > + signal(SIGSEGV, segv_handler);
> > + ret = handle_lam_test(ptr, test->lam);
> > + } else {
> > + ret = 2;
> > + }
> > +
> > + if (test->later != 0 && test->lam != 0)
> > + if (set_lam(test->lam) == -1 && ret == 0)
> > + ret = 1;
> > +
> > + free(ptr);
> > +
> > + return ret;
> > +}
> > +
> > +static int fork_test(struct testcases *test) {
> > + int ret, child_ret;
> > + pid_t pid;
> > +
> > + pid = fork();
> > + if (pid < 0) {
> > + perror("Fork failed.");
> > + ret = 1;
> > + } else if (pid == 0) {
> > + ret = test->test_func(test);
> > + exit(ret);
> > + } else {
> > + wait(&child_ret);
> > + ret = WEXITSTATUS(child_ret);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static void run_test(struct testcases *test, int count) {
> > + int i, ret = 0;
> > +
> > + for (i = 0; i < count; i++) {
> > + struct testcases *t = test + i;
> > +
> > + /* fork a process to run test case */
> > + ret = fork_test(t);
> > + if (ret != 0)
> > + ret = (t->expected == ret);
> > + else
> > + ret = !(t->expected);
> > +
> > + tests_cnt++;
> > + ksft_test_result(ret, t->msg);
> > + }
> > +}
> > +
> > +static struct testcases malloc_cases[] = {
> > + {
> > + .later = 0,
> > + .lam = LAM_U57_BITS,
> > + .test_func = handle_malloc,
> > + .msg = "MALLOC: LAM_U57. Dereferencing pointer with
> > metadata\n",
> > + },
> > + {
> > + .later = 1,
> > + .expected = 2,
> > + .lam = LAM_U57_BITS,
> > + .test_func = handle_malloc,
> > + .msg = "MALLOC:[Negtive] Disable LAM. Dereferencing
> pointer
> > with metadata.\n",
> > + },
> > +};
> > +
> > +
> > +static struct testcases bits_cases[] = {
> > + {
> > + .test_func = handle_max_bits,
> > + .msg = "BITS: Check default tag bits\n",
> > + },
> > +};
> > +
> > +static void cmd_help(void)
> > +{
> > + printf("usage: lam [-h] [-t test list]\n");
> > + printf("\t-t test list: run tests specified in the test list,
> > +default:0x%x\n",
> > TEST_MASK);
> > + printf("\t\t0x1:malloc; 0x2:max_bits;\n");
> > + printf("\t-h: help\n");
> > +}
> > +
> > +int main(int argc, char **argv)
> > +{
> > + int c = 0;
> > + unsigned int tests = TEST_MASK;
> > +
> > + tests_cnt = 0;
> > +
> > + if (!cpu_has_lam()) {
> > + ksft_print_msg("Unsupported LAM feature!\n");
> > + return -1;
> > + }
> > +
> > + while ((c = getopt(argc, argv, "ht:")) != -1) {
> > + switch (c) {
> > + case 't':
> > + tests = strtoul(optarg, NULL, 16);
> > + if (!(tests & TEST_MASK)) {
> > + ksft_print_msg("Invalid argument!\n");
> > + return -1;
> > + }
> > + break;
> > + case 'h':
> > + cmd_help();
> > + return 0;
> > + default:
> > + ksft_print_msg("Invalid argument\n");
> > + return -1;
> > + }
> > + }
> > +
> > + if (tests & FUNC_MALLOC)
> > + run_test(malloc_cases, ARRAY_SIZE(malloc_cases));
> > +
> > + if (tests & FUNC_BITS)
> > + run_test(bits_cases, ARRAY_SIZE(bits_cases));
> > +
> > + ksft_set_plan(tests_cnt);
> > +
> > + return ksft_exit_pass();
> > +}
> > --
> > 2.35.1