* [PATCH v2 0/2] x86/fpu: Fix dynamic fpstate leak on exec() @ 2026-10-08 5:59 Guixiong Wei 2026-10-08 5:59 ` [PATCH v2 2/2] selftests/x86/amx: Test dynamic fpstate cleanup across exec() Guixiong Wei 0 siblings, 1 reply; 3+ messages in thread From: Guixiong Wei @ 2026-10-08 5:59 UTC (permalink / raw) To: x86 Cc: Guixiong Wei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, Chang S . Bae, Shuah Khan, linux-kernel, linux-kselftest An exec() after using an XFD-controlled xfeature resets fpu->fpstate to its embedded storage without freeing the dynamically allocated state. Fix the leak in fpstate_reset() and add an AMX regression selftest. The fix preserves the old pointer, installs and initializes the embedded fpstate, and then frees the detached allocation. Since fpstate_reset() now owns cleanup, fpu_clone() initializes dst_fpu->fpstate to NULL before invoking it. The selftest performs ten XTILEDATA request, XRSTOR and self-exec cycles in the same task and checks the associated /proc/vmallocinfo entries. It fails with 10 leaked allocations on the unfixed kernel and passes with zero on the fixed kernel. Changes since v1: - Rename the fix and describe the memory leak explicitly. - Move cleanup into fpstate_reset(). - Detach the old fpstate before freeing it. - Initialize dst_fpu->fpstate to NULL before resetting it. - Add the requested AMX regression selftest as a separate patch. v1: https://lore.kernel.org/r/20260929151013.81562-2-weiguixiong@bytedance.com Guixiong Wei (2): x86/fpu: Fix memory leak with dynamic fpstate and exec() selftests/x86/amx: Test dynamic fpstate cleanup across exec() arch/x86/include/asm/fpu/api.h | 6 +- arch/x86/kernel/fpu/core.c | 5 ++ arch/x86/kernel/fpu/xstate.c | 10 +-- arch/x86/kernel/process.c | 2 +- tools/testing/selftests/x86/amx.c | 144 +++++++++++++++++++++++++++++- 5 files changed, 156 insertions(+), 11 deletions(-) base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e -- 2.50.1 (Apple Git-155) ^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2 2/2] selftests/x86/amx: Test dynamic fpstate cleanup across exec() 2026-10-08 5:59 [PATCH v2 0/2] x86/fpu: Fix dynamic fpstate leak on exec() Guixiong Wei @ 2026-10-08 5:59 ` Guixiong Wei 2026-10-15 0:19 ` Chang S. Bae 0 siblings, 1 reply; 3+ messages in thread From: Guixiong Wei @ 2026-10-08 5:59 UTC (permalink / raw) To: x86 Cc: Guixiong Wei, Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, Chang S . Bae, Shuah Khan, linux-kernel, linux-kselftest An AMX task gets a dynamically allocated fpstate after requesting XTILEDATA permission and first loading tile data. Test that exec() releases this allocation instead of making it unreachable. Run ten request, XRSTOR and self-exec cycles in the same task. Count the matching allocations in /proc/vmallocinfo before the test, while the first allocation is live and after the task exits. Skip the test when /proc/vmallocinfo is unavailable or when concurrent global vmalloc changes prevent an isolated measurement. The test observed 10 leaked allocations on an unfixed kernel and zero with the fix applied. Signed-off-by: Guixiong Wei <weiguixiong@bytedance.com> --- tools/testing/selftests/x86/amx.c | 144 +++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/x86/amx.c b/tools/testing/selftests/x86/amx.c index 40769c16de1bb..cf098dfbe50c0 100644 --- a/tools/testing/selftests/x86/amx.c +++ b/tools/testing/selftests/x86/amx.c @@ -3,8 +3,10 @@ #define _GNU_SOURCE #include <err.h> #include <errno.h> +#include <limits.h> #include <setjmp.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <stdbool.h> #include <unistd.h> @@ -30,6 +32,9 @@ #define XFEATURE_MASK_XTILEDATA (1 << XFEATURE_XTILEDATA) #define XFEATURE_MASK_XTILE (XFEATURE_MASK_XTILECFG | XFEATURE_MASK_XTILEDATA) +#define EXEC_TEST_ARG "--exec-test" +#define EXEC_TEST_ITERS 10 + struct xstate_info xtiledata; /* The helpers for managing XSAVE buffer and tile states: */ @@ -478,11 +483,144 @@ static void test_fork(void) _exit(0); } -int main(void) +static int count_dynamic_fpstates(void) +{ + char *line = NULL; + size_t line_size = 0; + FILE *fp; + int count = 0; + + fp = fopen("/proc/vmallocinfo", "r"); + if (!fp) + return -errno; + + while (getline(&line, &line_size, fp) >= 0) { + if (strstr(line, "__xfd_enable_feature") || + strstr(line, "fpstate_realloc")) + count++; + } + + if (ferror(fp)) + count = -EIO; + + free(line); + fclose(fp); + return count; +} + +static int parse_exec_arg(const char *arg) +{ + char *end; + long value; + + errno = 0; + value = strtol(arg, &end, 10); + if (errno || *end || value < 0 || value > INT_MAX) + fatal_error("invalid exec test argument: %s", arg); + + return value; +} + +static int run_exec_test(int iterations, int baseline) +{ + char iterations_arg[16]; + char baseline_arg[16]; + int allocated; + + if (!iterations) + return 0; + + req_xtiledata_perm(); + if (!load_rand_tiledata(stashed_xsave)) + fatal_error("failed to load tiledata before exec()"); + + if (iterations == EXEC_TEST_ITERS) { + allocated = count_dynamic_fpstates(); + if (allocated != baseline + 1) + return KSFT_SKIP; + } + + snprintf(iterations_arg, sizeof(iterations_arg), "%d", iterations - 1); + snprintf(baseline_arg, sizeof(baseline_arg), "%d", baseline); + execl("/proc/self/exe", "amx", EXEC_TEST_ARG, iterations_arg, + baseline_arg, NULL); + fatal_error("exec"); +} + +static void test_exec(void) +{ + char iterations_arg[16]; + char baseline_arg[16]; + int before, after, delta; + pid_t child; + int status; + + printf("[RUN]\tCheck dynamic fpstate cleanup across exec().\n"); + + before = count_dynamic_fpstates(); + if (before < 0) { + printf("[SKIP]\tCannot read /proc/vmallocinfo: %s\n", + strerror(-before)); + return; + } + + child = fork(); + if (child < 0) + fatal_error("fork"); + if (!child) { + snprintf(iterations_arg, sizeof(iterations_arg), "%d", + EXEC_TEST_ITERS); + snprintf(baseline_arg, sizeof(baseline_arg), "%d", before); + execl("/proc/self/exe", "amx", EXEC_TEST_ARG, iterations_arg, + baseline_arg, NULL); + fatal_error("exec"); + } + + if (waitpid(child, &status, 0) != child) + fatal_error("waitpid"); + if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP) { + printf("[SKIP]\tDynamic fpstate allocation was not isolated.\n"); + return; + } + if (!WIFEXITED(status) || WEXITSTATUS(status)) + fatal_error("exec test child"); + + after = count_dynamic_fpstates(); + if (after < 0) { + printf("[SKIP]\tCannot read /proc/vmallocinfo after exec(): %s\n", + strerror(-after)); + return; + } + + delta = after - before; + if (!delta) { + printf("[OK]\tDynamic fpstate allocations were freed across exec().\n"); + return; + } + + if (delta == EXEC_TEST_ITERS) + errx(1, "[FAIL]\texec() leaked %d dynamic fpstate allocations", + delta); + + printf("[SKIP]\tDynamic fpstate vmalloc usage changed concurrently.\n"); +} + +int main(int argc, char **argv) { unsigned long features; + int iterations = 0; + int baseline = 0; + bool exec_test; long rc; + exec_test = argc == 4 && !strcmp(argv[1], EXEC_TEST_ARG); + if (exec_test) { + iterations = parse_exec_arg(argv[2]); + baseline = parse_exec_arg(argv[3]); + if (!iterations) + return 0; + } + rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features); if (rc || (features & XFEATURE_MASK_XTILE) != XFEATURE_MASK_XTILE) { ksft_print_msg("no AMX support\n"); @@ -498,6 +636,10 @@ int main(void) init_stashed_xsave(); sethandler(SIGILL, handle_noperm, 0); + if (exec_test) + return run_exec_test(iterations, baseline); + + test_exec(); test_dynamic_state(); /* Request permission for the following tests */ -- 2.50.1 (Apple Git-155) ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v2 2/2] selftests/x86/amx: Test dynamic fpstate cleanup across exec() 2026-10-08 5:59 ` [PATCH v2 2/2] selftests/x86/amx: Test dynamic fpstate cleanup across exec() Guixiong Wei @ 2026-10-15 0:19 ` Chang S. Bae 0 siblings, 0 replies; 3+ messages in thread From: Chang S. Bae @ 2026-10-15 0:19 UTC (permalink / raw) To: Guixiong Wei, x86 Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, H . Peter Anvin, Shuah Khan, linux-kernel, linux-kselftest On 10/7/2026 10:59 PM, Guixiong Wei wrote: > > -int main(void) > +static int count_dynamic_fpstates(void) > +{ > + char *line = NULL; > + size_t line_size = 0; > + FILE *fp; > + int count = 0; > + > + fp = fopen("/proc/vmallocinfo", "r"); > + if (!fp) > + return -errno; The call site doesn't interprete the error code. Instead, you can simply call fatal_error() here and below on an IO error. > + > + while (getline(&line, &line_size, fp) >= 0) { > + if (strstr(line, "__xfd_enable_feature") || > + strstr(line, "fpstate_realloc")) > + count++; > + } > + > + if (ferror(fp)) > + count = -EIO; > + > + free(line); > + fclose(fp); > + return count; > +} > +static void test_exec(void) > +{ > + char iterations_arg[16]; > + char baseline_arg[16]; > + int before, after, delta; > + pid_t child; > + int status; > + > + printf("[RUN]\tCheck dynamic fpstate cleanup across exec().\n"); > + > + before = count_dynamic_fpstates(); > + if (before < 0) { > + printf("[SKIP]\tCannot read /proc/vmallocinfo: %s\n", > + strerror(-before)); > + return; > + } > + > + child = fork(); > + if (child < 0) > + fatal_error("fork"); > + if (!child) { > + snprintf(iterations_arg, sizeof(iterations_arg), "%d", > + EXEC_TEST_ITERS); > + snprintf(baseline_arg, sizeof(baseline_arg), "%d", before); > + execl("/proc/self/exe", "amx", EXEC_TEST_ARG, iterations_arg, > + baseline_arg, NULL); > + fatal_error("exec"); > + } This nested exec() with passing numbers via arguments looks more complicate than necessary. Instead, you could put the fork()/exec() sequence in a loop. Before each exec(), call load_rand_tiledata(), and adjust main() to return immediately from the exec test. I found this also reproduces the increase in /proc/vmallocinfo. > + > + if (waitpid(child, &status, 0) != child) > + fatal_error("waitpid"); > + if (WIFEXITED(status) && WEXITSTATUS(status) == KSFT_SKIP) { > + printf("[SKIP]\tDynamic fpstate allocation was not isolated.\n"); > + return; > + } > + if (!WIFEXITED(status) || WEXITSTATUS(status)) > + fatal_error("exec test child"); > + > + after = count_dynamic_fpstates(); > + if (after < 0) { > + printf("[SKIP]\tCannot read /proc/vmallocinfo after exec(): %s\n", > + strerror(-after)); > + return; > + } > + > + delta = after - before; > + if (!delta) { > + printf("[OK]\tDynamic fpstate allocations were freed across exec().\n"); > + return; > + } > + > + if (delta == EXEC_TEST_ITERS) > + errx(1, "[FAIL]\texec() leaked %d dynamic fpstate allocations", > + delta); errx() terminates the entire test, preventing subsequent tests from running. Unless this failure makes it unsafe or meaningless to continue, why not report the failure with printf() and let the remaining tests run? > + > + printf("[SKIP]\tDynamic fpstate vmalloc usage changed concurrently.\n"); > +} Thanks, Chang ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-15 0:19 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-08 5:59 [PATCH v2 0/2] x86/fpu: Fix dynamic fpstate leak on exec() Guixiong Wei 2026-10-08 5:59 ` [PATCH v2 2/2] selftests/x86/amx: Test dynamic fpstate cleanup across exec() Guixiong Wei 2026-10-15 0:19 ` Chang S. Bae
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.