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

From: Hu, Robert
Date: Fri Aug 19 2022 - 01:17:16 EST


> -----Original Message-----
> From: Kirill A. Shutemov <kirill.shutemov@xxxxxxxxxxxxxxx>
> Sent: Monday, August 15, 2022 12:18
> To: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx>; Lutomirski, Andy
> <luto@xxxxxxxxxx>; Peter Zijlstra <peterz@xxxxxxxxxxxxx>
> Cc: x86@xxxxxxxxxx; Kostya Serebryany <kcc@xxxxxxxxxx>; Andrey Ryabinin
> <ryabinin.a.a@xxxxxxxxx>; Andrey Konovalov <andreyknvl@xxxxxxxxx>;
> Alexander Potapenko <glider@xxxxxxxxxx>; Taras Madan
> <tarasmadan@xxxxxxxxxx>; Dmitry Vyukov <dvyukov@xxxxxxxxxx>; H . J . Lu
> <hjl.tools@xxxxxxxxx>; Andi Kleen <ak@xxxxxxxxxxxxxxx>; Edgecombe, Rick P
> <rick.p.edgecombe@xxxxxxxxx>; linux-mm@xxxxxxxxx; linux-
> kernel@xxxxxxxxxxxxxxx; Zhang, Weihong <weihong.zhang@xxxxxxxxx>; Kirill A .
> Shutemov <kirill.shutemov@xxxxxxxxxxxxxxx>
> Subject: [PATCHv6 08/11] selftests/x86/lam: Add malloc and tag-bits test cases
> for linear-address masking
>
> From: Weihong Zhang <weihong.zhang@xxxxxxxxx>
>
> 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 <weihong.zhang@xxxxxxxxx>
> Signed-off-by: Kirill A. Shutemov <kirill.shutemov@xxxxxxxxxxxxxxx>
> ---
> 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