diff options
| author | Xie Yuanbin <xieyuanbin1@huawei.com> | 2026-07-28 03:16:42 +0100 |
|---|---|---|
| committer | Russell King <rmk+kernel@armlinux.org.uk> | 2026-08-05 16:15:51 +0100 |
| commit | 1039bffd6ae9c75b42b7d148d6c1106134107b66 (patch) | |
| tree | e8ee4ac80f87e2ad1b6b0c877eccbb62b0915c02 /tools/perf/util/help-unknown-cmd.c | |
| download | linux-stable-1039bffd6ae9c75b42b7d148d6c1106134107b66.tar.gz linux-stable-1039bffd6ae9c75b42b7d148d6c1106134107b66.zip | |
ARM: 9485/1: mm: acquire mmap write lock around show_pte() for user faultsgrafted
When CONFIG_DEBUG_USER=y, and cmdline "user_debug=31" is set,
a user fault may trigger show_pte() without any lock.
If another thread in the same process concurrently calls munmap(),
the page table pages may be freed while show_pte() is still traversing
them, causing a use-after-free in show_pte().
If CONFIG_ARM_LPAE=y, this may cause a kernel panic if the pages table
of PMD are freed when show_pte() is running.
Acquire mmap_write_lock() around show_pte() for user faults to fix the
contention.
For user faults, additionally restrict that show_pte() is called only
when the addr is a user-space address (addr < TASK_SIZE). This is because
the lock of tsk->mm only protects the virtual memory of user address space,
furthermore, dumping the page tables of a kernel-space address for user
faults is unnecessary and may have security implications.
Keep everything unchanged for kernel faults, because the kernel is
already in the "oops" state, acquiring a lock may risk a deadlock.
Co-developed-by: Qi Xi <xiqi2@huawei.com>
Fixes: 6d021b724481 ("ARM: dump pgd, pmd and pte states on unhandled data abort faults")
Link: https://lore.kernel.org/20260716014022.2823-1-xieyuanbin1@huawei.com
Acked-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Qi Xi <xiqi2@huawei.com>
Signed-off-by: Xie Yuanbin <xieyuanbin1@huawei.com>
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Diffstat (limited to 'tools/perf/util/help-unknown-cmd.c')
| -rw-r--r-- | tools/perf/util/help-unknown-cmd.c | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/tools/perf/util/help-unknown-cmd.c b/tools/perf/util/help-unknown-cmd.c new file mode 100644 index 000000000..a0a46e34f --- /dev/null +++ b/tools/perf/util/help-unknown-cmd.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "cache.h" +#include "config.h" +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <subcmd/help.h> +#include "../builtin.h" +#include "levenshtein.h" +#include <linux/zalloc.h> + +static int autocorrect; + +static int perf_unknown_cmd_config(const char *var, const char *value, + void *cb __maybe_unused) +{ + if (!strcmp(var, "help.autocorrect")) + return perf_config_int(&autocorrect, var,value); + + return 0; +} + +static int levenshtein_compare(const void *p1, const void *p2) +{ + const struct cmdname *const *c1 = p1, *const *c2 = p2; + const char *s1 = (*c1)->name, *s2 = (*c2)->name; + int l1 = (*c1)->len; + int l2 = (*c2)->len; + return l1 != l2 ? l1 - l2 : strcmp(s1, s2); +} + +static int add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) +{ + unsigned int i, nr = cmds->cnt + old->cnt; + void *tmp; + + if (nr > cmds->alloc) { + /* Choose bigger one to alloc */ + if (alloc_nr(cmds->alloc) < nr) + cmds->alloc = nr; + else + cmds->alloc = alloc_nr(cmds->alloc); + tmp = realloc(cmds->names, cmds->alloc * sizeof(*cmds->names)); + if (!tmp) + return -1; + cmds->names = tmp; + } + for (i = 0; i < old->cnt; i++) + cmds->names[cmds->cnt++] = old->names[i]; + zfree(&old->names); + old->cnt = 0; + return 0; +} + +const char *help_unknown_cmd(const char *cmd, struct cmdnames *main_cmds) +{ + unsigned int i, n = 0, best_similarity = 0; + struct cmdnames other_cmds; + + memset(&other_cmds, 0, sizeof(other_cmds)); + + perf_config(perf_unknown_cmd_config, NULL); + + load_command_list("perf-", main_cmds, &other_cmds); + + if (add_cmd_list(main_cmds, &other_cmds) < 0) { + fprintf(stderr, "ERROR: Failed to allocate command list for unknown command.\n"); + goto end; + } + qsort(main_cmds->names, main_cmds->cnt, + sizeof(main_cmds->names), cmdname_compare); + uniq(main_cmds); + + if (main_cmds->cnt) { + /* This reuses cmdname->len for similarity index */ + for (i = 0; i < main_cmds->cnt; ++i) { + main_cmds->names[i]->len = + levenshtein(cmd, main_cmds->names[i]->name, + /*swap_penalty=*/0, + /*substition_penality=*/2, + /*insertion_penality=*/1, + /*deletion_penalty=*/1); + } + qsort(main_cmds->names, main_cmds->cnt, + sizeof(*main_cmds->names), levenshtein_compare); + + best_similarity = main_cmds->names[0]->len; + n = 1; + while (n < main_cmds->cnt && best_similarity == main_cmds->names[n]->len) + ++n; + } + + if (autocorrect && n == 1) { + const char *assumed = main_cmds->names[0]->name; + + main_cmds->names[0] = NULL; + clean_cmdnames(&other_cmds); + fprintf(stderr, "WARNING: You called a perf program named '%s', " + "which does not exist.\n" + "Continuing under the assumption that you meant '%s'\n", + cmd, assumed); + if (autocorrect > 0) { + fprintf(stderr, "in %0.1f seconds automatically...\n", + (float)autocorrect/10.0); + poll(NULL, 0, autocorrect * 100); + } + return assumed; + } + + fprintf(stderr, "perf: '%s' is not a perf-command. See 'perf --help'.\n", cmd); + + if (main_cmds->cnt && best_similarity < 6) { + fprintf(stderr, "\nDid you mean %s?\n", + n < 2 ? "this": "one of these"); + + for (i = 0; i < n; i++) + fprintf(stderr, "\t%s\n", main_cmds->names[i]->name); + } +end: + clean_cmdnames(&other_cmds); + return NULL; +} |
