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/srccode.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/srccode.c')
| -rw-r--r-- | tools/perf/util/srccode.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/tools/perf/util/srccode.c b/tools/perf/util/srccode.c new file mode 100644 index 000000000..0f4907843 --- /dev/null +++ b/tools/perf/util/srccode.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Manage printing of source lines + * Copyright (c) 2017, Intel Corporation. + * Author: Andi Kleen + */ +#include <linux/list.h> +#include <linux/zalloc.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> +#include "srccode.h" +#include "debug.h" +#include <internal/lib.h> // page_size +#include "hashmap.h" + +#define MAXSRCCACHE (32*1024*1024) +#define MAXSRCFILES 64 +#define SRC_HTAB_SZ 64 + +struct srcfile { + struct hlist_node hash_nd; + struct list_head nd; + char *fn; + char **lines; + char *map; + unsigned numlines; + size_t maplen; +}; + +static struct hlist_head srcfile_htab[SRC_HTAB_SZ]; +static LIST_HEAD(srcfile_list); +static long map_total_sz; +static int num_srcfiles; + +static int countlines(char *map, int maplen) +{ + int numl; + char *end = map + maplen; + char *p = map; + + if (maplen == 0) + return 0; + numl = 0; + while (p < end && (p = memchr(p, '\n', end - p)) != NULL) { + numl++; + p++; + } + if (p < end) + numl++; + return numl; +} + +static void fill_lines(char **lines, int maxline, char *map, int maplen) +{ + int l; + char *end = map + maplen; + char *p = map; + + if (maplen == 0 || maxline == 0) + return; + l = 0; + lines[l++] = map; + while (p < end && (p = memchr(p, '\n', end - p)) != NULL) { + if (l >= maxline) + return; + lines[l++] = ++p; + } + if (p < end) + lines[l] = p; +} + +static void free_srcfile(struct srcfile *sf) +{ + list_del_init(&sf->nd); + hlist_del(&sf->hash_nd); + map_total_sz -= sf->maplen; + munmap(sf->map, sf->maplen); + zfree(&sf->lines); + zfree(&sf->fn); + free(sf); + num_srcfiles--; +} + +static struct srcfile *find_srcfile(char *fn) +{ + struct stat st; + struct srcfile *h; + int fd; + unsigned long sz; + size_t hval = str_hash(fn) % SRC_HTAB_SZ; + + hlist_for_each_entry (h, &srcfile_htab[hval], hash_nd) { + if (!strcmp(fn, h->fn)) { + /* Move to front */ + list_move(&h->nd, &srcfile_list); + return h; + } + } + + /* Only prune if there is more than one entry */ + while ((num_srcfiles > MAXSRCFILES || map_total_sz > MAXSRCCACHE) && + srcfile_list.next != &srcfile_list) { + assert(!list_empty(&srcfile_list)); + h = list_entry(srcfile_list.prev, struct srcfile, nd); + free_srcfile(h); + } + + fd = open(fn, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0) { + pr_debug("cannot open source file %s\n", fn); + return NULL; + } + + h = malloc(sizeof(struct srcfile)); + if (!h) + return NULL; + + h->fn = strdup(fn); + if (!h->fn) + goto out_h; + + h->maplen = st.st_size; + sz = (h->maplen + page_size - 1) & ~(page_size - 1); + h->map = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (h->map == (char *)-1) { + pr_debug("cannot mmap source file %s\n", fn); + goto out_fn; + } + h->numlines = countlines(h->map, h->maplen); + h->lines = calloc(h->numlines, sizeof(char *)); + if (!h->lines) + goto out_map; + fill_lines(h->lines, h->numlines, h->map, h->maplen); + list_add(&h->nd, &srcfile_list); + hlist_add_head(&h->hash_nd, &srcfile_htab[hval]); + map_total_sz += h->maplen; + num_srcfiles++; + return h; + +out_map: + munmap(h->map, sz); +out_fn: + zfree(&h->fn); +out_h: + free(h); + return NULL; +} + +/* Result is not 0 terminated */ +char *find_sourceline(char *fn, unsigned line, int *lenp) +{ + char *l, *p; + struct srcfile *sf = find_srcfile(fn); + if (!sf) + return NULL; + line--; + if (line >= sf->numlines) + return NULL; + l = sf->lines[line]; + if (!l) + return NULL; + p = memchr(l, '\n', sf->map + sf->maplen - l); + *lenp = p - l; + return l; +} |
