* [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
@ 2026-10-23 0:16 Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
` (5 more replies)
0 siblings, 6 replies; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
/proc/kallsyms is the primary interface for userspace to query the kernel
symbol table. However, resolving even a handful of symbols or addresses
requires reading and parsing the entire text file, which is often several
megabytes and takes thousands of read() syscalls.
This patchseries adds an extensible KALLSYMS_QUERY ioctl() to
/proc/kallsyms with two query modes:
1. KALLSYMS_QUERY_BY_SYMBOL: Resolves a NUL-terminated symbol name to its
address. Searches vmlinux first with binary search, then falls back to
loaded modules. Matches names exactly (without stripping .llvm.<hash>
suffixes) and rejects ambiguous symbols with different addresses.
2. KALLSYMS_QUERY_BY_ADDR: Resolves a kernel address to the symbol's
name, covering vmlinux, loaded modules, BPF JIT programs, and ftrace
trampolines.
Access control matches reading /proc/kallsyms directly: the ioctl checks
the kallsyms_show_value() result cached at open() time and returns -EPERM
whenever symbol addresses would be masked to zero.
There were significant performance improvements with this patchseries:
1. libbpf typeless ksym resolution (8-CPU x86-64 Cuttlefish VM, BPF
object with 11 typeless ksyms):
Metric Text parse KALLSYMS_QUERY Change
-------------------------- ---------- --------------- ------------
Resolve 11 typeless ksyms 157 ms 0.7 ms ~220x faster
2. Perfetto symbolization & callstack sampling (8-CPU x86-64 VM,
175k-line /proc/kallsyms):
Metric Text parse KALLSYMS_QUERY Change
-------------------------- ---------- --------------- ------------
Init 155.7 ms 0.44 ms 352x faster
50k lookups 25.2 ms 2.2 ms 11.7x faster
traced_probes VmRSS 15.1 MB 4.8 MB -68%
traced_probes VmHWM 23.9 MB 4.8 MB -80%
Changes in v2:
- Replaced the BPF syscall approach in libbpf with the more generalizable
KALLSYMS_QUERY ioctl() on /proc/kallsyms, per Andrii
- Added KALLSYMS_QUERY_BY_ADDR for address-to-symbol lookup
- Added proc and bpf selftests and procfs documentation
- v1 link: https://lore.kernel.org/all/20261009130708.1370675-1-wusamuel@google.com/
Samuel Wu (6):
kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
tools: Copy KALLSYMS_QUERY definitions to linux/fs.h
docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms
libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
selftests/bpf: Test typeless module ksym resolution
selftests/proc: Add KALLSYMS_QUERY tests
Documentation/filesystems/proc.rst | 15 ++
include/uapi/linux/fs.h | 41 ++++
kernel/kallsyms.c | 132 +++++++++++
tools/include/uapi/linux/fs.h | 41 ++++
tools/lib/bpf/libbpf.c | 78 ++++++-
.../testing/selftests/bpf/prog_tests/ksyms.c | 7 +
.../testing/selftests/bpf/progs/test_ksyms.c | 3 +
tools/testing/selftests/proc/.gitignore | 1 +
tools/testing/selftests/proc/Makefile | 1 +
.../selftests/proc/proc-kallsyms-query.c | 213 ++++++++++++++++++
10 files changed, 530 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/proc/proc-kallsyms-query.c
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-23 0:16 ` [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h Samuel Wu
` (4 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Add an extensible KALLSYMS_QUERY ioctl() interface to /proc/kallsyms
to support fast point queries without scanning the entire text file.
Exactly one query mode must be set in query_flags.
When KALLSYMS_QUERY_BY_SYMBOL is set in query_flags, KALLSYMS_QUERY
resolves a NUL-terminated symbol name in vmlinux or loaded modules and
then populates sym_addr:
- Searches vmlinux first via kallsyms_lookup_names() (binary search),
which takes precedence over loaded modules, and falls back to
module_kallsyms_on_each_symbol() under module_mutex for module
symbols
- Matches names exactly, like kallsyms_lookup_name(), disregarding the
.llvm.<hash> matching, following precedent set by commit fb6a421fb615
("kallsyms: Match symbols exactly with CONFIG_LTO_CLANG")
- Resolves 0x0 symbol addresses in both vmlinux and modules
- Rejects ambiguous symbols (multiple entries with the same name at
different addresses within vmlinux, or across loaded modules) with
-EADDRNOTAVAIL, while allowing same-address aliases
Userspace symbolizers resolve kernel addresses to symbol names with
/proc/kallsyms. This is typically done by reading the entire
/proc/kallsyms text file, and keeping a compressed sorted copy of it in
order to resolve addresses at runtime.
When KALLSYMS_QUERY_BY_ADDR is set in query_flags, KALLSYMS_QUERY
resolves one address to the symbol that contains it:
- sym_addr holds the address to resolve on input.
- sym_name_addr must point to a user buffer of sym_name_size bytes
that receives the NUL-terminated symbol name. On output,
sym_name_size holds the number of bytes written (including the
terminating NUL). -ENAMETOOLONG is returned if the buffer is too
small; a buffer of KSYM_NAME_LEN (512 at the time of writing) bytes
is always large enough.
- Symbols are resolved via kallsyms_lookup(), the same lookup as %pS,
covering vmlinux, loaded modules, BPF JIT images, and freed module
init functions still tracked by ftrace.
On an 8 CPU Cuttlefish (x86-64 VM), using this patchseries to load a BPF
program with 11 typeless ksyms has ~220x speedup (157ms -> 0.7ms).
On an 8 CPU x86-64 VM with a 175k line kallsyms, there are broad
performance and memory savings for the symbolization and callstack
sampling tool, Perfetto:
- Init time: ~350x speedup (155.7ms -> 0.44ms)
- 50k symbolizations: ~12x speedup (25.2ms -> 2.2ms)
- VmRSS: -68% (15.1MB -> 4.8MB)
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
include/uapi/linux/fs.h | 41 +++++++++++++
kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 173 insertions(+)
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 34c6f219462a..b43f227dad0e 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -674,6 +674,47 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctl */
+#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
+
+/* Exactly one query mode must be set in query_flags. */
+enum kallsyms_query_flags {
+ /*
+ * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
+ * and ftrace symbols are not searched) by exact NUL-terminated name
+ * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
+ * must be 0 on input.
+ */
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+ /*
+ * Look up the symbol covering sym_addr and write its NUL-terminated
+ * name to the sym_name_addr buffer of sym_name_size bytes. The same
+ * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
+ * images, and freed module init functions still tracked by ftrace.
+ * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
+ * always enough. On success, sym_name_size is set to the number of
+ * bytes written (including the terminating NUL).
+ */
+ KALLSYMS_QUERY_BY_ADDR = 0x02,
+};
+
+/*
+ * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
+ * more than one address, -ENAMETOOLONG if the name does not fit in
+ * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
+ * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
+ * (e.g. kptr_restrict).
+ */
+struct kallsyms_query {
+ __u64 size; /* in - struct size for compatibility */
+ __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
+ __u64 sym_name_addr; /* in - address of symbol name buffer */
+ __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
+ __u64 sym_name_size; /* in/out - name buffer size / bytes written */
+};
+
+#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
+
/*
* Shutdown the filesystem.
*/
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index aec2f06858af..7caaf3e421c7 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -31,6 +31,7 @@
#include <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/btf_ids.h>
+#include <linux/uaccess.h>
#include "kallsyms_internal.h"
@@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
}
#endif /* CONFIG_KGDB_KDB */
+struct kallsyms_mod_find_ctx {
+ const char *name;
+ unsigned long addr;
+ bool found;
+};
+
+static int kallsyms_mod_find_cb(void *data, const char *name, unsigned long addr)
+{
+ struct kallsyms_mod_find_ctx *ctx = data;
+
+ if (strcmp(name, ctx->name) != 0)
+ return 0;
+ if (ctx->found && ctx->addr != addr)
+ return -EADDRNOTAVAIL;
+ ctx->found = true;
+ ctx->addr = addr;
+ return 0;
+}
+
+static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
+{
+ char namebuf[KSYM_NAME_LEN];
+ struct kallsyms_query karg;
+ unsigned long addr;
+ unsigned int start;
+ unsigned int end;
+ unsigned int i;
+ __u64 usize;
+ long len;
+ int err;
+
+ BUILD_BUG_ON(sizeof(struct kallsyms_query) != KALLSYMS_QUERY_SIZE_VER0);
+
+ /*
+ * The text interface shows zero addresses to openers without permission.
+ * A zeroed point query result is useless (and an address-to-symbol query
+ * would leak KASLR), so fail instead.
+ */
+ if (!iter->show_value)
+ return -EPERM;
+ if (copy_from_user(&usize, uarg, sizeof(usize)))
+ return -EFAULT;
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+ if (usize < KALLSYMS_QUERY_SIZE_VER0)
+ return -EINVAL;
+
+ err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
+ if (err)
+ return err;
+
+ switch (karg.query_flags) {
+ case KALLSYMS_QUERY_BY_SYMBOL:
+ if (karg.sym_addr || karg.sym_name_size)
+ return -EINVAL;
+
+ len = strncpy_from_user(namebuf, u64_to_user_ptr(karg.sym_name_addr),
+ sizeof(namebuf));
+ if (len < 0)
+ return len;
+ if (len == 0)
+ return -EINVAL;
+ if (len == sizeof(namebuf))
+ return -ENAMETOOLONG;
+
+ if (kallsyms_lookup_names(namebuf, &start, &end)) {
+ struct kallsyms_mod_find_ctx ctx = { .name = namebuf };
+
+ err = module_kallsyms_on_each_symbol(NULL, kallsyms_mod_find_cb, &ctx);
+ /* Check first: maps -EOPNOTSUPP (CONFIG_MODULES=n) to -ENOENT. */
+ if (!ctx.found)
+ return -ENOENT;
+ if (err)
+ return err;
+ addr = ctx.addr;
+ } else {
+ addr = kallsyms_sym_address(get_symbol_seq(start));
+ for (i = start + 1; i <= end; i++) {
+ if (kallsyms_sym_address(get_symbol_seq(i)) != addr)
+ return -EADDRNOTAVAIL;
+ }
+ }
+
+ karg.sym_addr = addr;
+ break;
+ case KALLSYMS_QUERY_BY_ADDR:
+ if (!karg.sym_name_size)
+ return -EINVAL;
+ addr = karg.sym_addr;
+ /* Reject addresses that do not fit in unsigned long (32-bit). */
+ if (addr != karg.sym_addr)
+ return -ENOENT;
+ if (!kallsyms_lookup(addr, NULL, NULL, NULL, namebuf))
+ return -ENOENT;
+
+ len = strlen(namebuf) + 1;
+ if (karg.sym_name_size < len)
+ return -ENAMETOOLONG;
+ if (copy_to_user(u64_to_user_ptr(karg.sym_name_addr), namebuf, len))
+ return -EFAULT;
+
+ karg.sym_name_size = len;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (copy_to_user(uarg, &karg, min_t(size_t, sizeof(karg), usize)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct seq_file *m = file->private_data;
+
+ switch (_IOC_NR(cmd)) {
+ case _IOC_NR(KALLSYMS_QUERY):
+ if (!extensible_ioctl_valid(cmd, KALLSYMS_QUERY, KALLSYMS_QUERY_SIZE_VER0))
+ return -ENOIOCTLCMD;
+ return do_kallsyms_query(m->private, (void __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
static const struct proc_ops kallsyms_proc_ops = {
.proc_open = kallsyms_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
+ .proc_ioctl = kallsyms_ioctl,
+#ifdef CONFIG_COMPAT
+ .proc_compat_ioctl = compat_ptr_ioctl,
+#endif
};
static int __init kallsyms_init(void)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms Samuel Wu
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Copy the KALLSYMS_QUERY ioctl definitions from include/uapi/linux/fs.h
to tools/include/uapi/linux/fs.h for libbpf and the proc and bpf
selftests. Only the new definitions are copied; other differences
between the two headers are left alone.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/include/uapi/linux/fs.h | 41 +++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index f0a26309b6d5..d224516e6d6e 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -567,4 +567,45 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctl */
+#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
+
+/* Exactly one query mode must be set in query_flags. */
+enum kallsyms_query_flags {
+ /*
+ * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
+ * and ftrace symbols are not searched) by exact NUL-terminated name
+ * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
+ * must be 0 on input.
+ */
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+ /*
+ * Look up the symbol covering sym_addr and write its NUL-terminated
+ * name to the sym_name_addr buffer of sym_name_size bytes. The same
+ * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
+ * images, and freed module init functions still tracked by ftrace.
+ * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
+ * always enough. On success, sym_name_size is set to the number of
+ * bytes written (including the terminating NUL).
+ */
+ KALLSYMS_QUERY_BY_ADDR = 0x02,
+};
+
+/*
+ * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
+ * more than one address, -ENAMETOOLONG if the name does not fit in
+ * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
+ * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
+ * (e.g. kptr_restrict).
+ */
+struct kallsyms_query {
+ __u64 size; /* in - struct size for compatibility */
+ __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
+ __u64 sym_name_addr; /* in - address of symbol name buffer */
+ __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
+ __u64 sym_name_size; /* in/out - name buffer size / bytes written */
+};
+
+#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
+
#endif /* _UAPI_LINUX_FS_H */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Document the existence of the KALLSYMS_QUERY ioctl() in the section
describing the /proc/kallsyms file. Refer the user to the UAPI header
for low-level details of this programmatic interface. Add a "kallsyms"
row to the /proc file table.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
Documentation/filesystems/proc.rst | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index c102b62023cd..a0e2fe14a01a 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -761,6 +761,7 @@ files are there, and which are missing.
ioports I/O port usage
irq Masks for irq to cpu affinity (2.4)(smp?)
isapnp ISA PnP (Plug&Play) Info (2.4)
+ kallsyms Kernel symbol table with dynamic queries (see text)
kcore Kernel core image (can be ELF or A.OUT(deprecated in 2.4))
kmsg Kernel messages
ksyms Kernel symbol table
@@ -1368,6 +1369,20 @@ Provides counts of softirq handlers serviced since boot time, for each CPU.
HRTIMER: 0 0 0 0
RCU: 1678 1769 2178 2250
+
+kallsyms
+~~~~~~~~
+
+The /proc/kallsyms file lists all symbols in vmlinux and loaded modules.
+
+In addition to sequential text reading, /proc/kallsyms provides an ioctl()-based
+API (`KALLSYMS_QUERY`) for querying specific symbols and addresses. This
+interface is binary and is meant for efficient extraction of data without
+parsing the entire text file. `struct kallsyms_query`, defined in the
+linux/fs.h UAPI header, serves as the input/output argument to the
+`KALLSYMS_QUERY` ioctl() command. See comments in the linux/fs.h UAPI header
+for details on query semantics, supported flags, and return values.
+
1.3 Networking info in /proc/net
--------------------------------
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (2 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-23 0:16 ` [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
5 siblings, 1 reply; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
When resolving typeless extern ksyms (!ext->ksym.type_id), libbpf
currently reads and parses the entire /proc/kallsyms text file via
libbpf_kallsyms_parse(). The kallsyms text file is commonly and quite
easily several MB, which means with 4kB page sizes, parsing the file
requires a few thousand read() syscalls. This is inefficient, especially
in the common case when only a few symbols are needed.
Add bpf_object__query_ksyms() to resolve typeless extern ksyms via the
KALLSYMS_QUERY ioctl on /proc/kallsyms. Modules are still searched if a
symbol is not found in vmlinux, and ambiguous symbols are still an
error. The lookup follows kallsyms_lookup_name() semantics, so there
are two differences from parsing the text file:
- A name that exists in both vmlinux and a module resolves to the
vmlinux symbol, instead of failing as ambiguous.
- Names are matched exactly, so an extern does not match a
<name>.llvm.<hash> symbol on CONFIG_LTO_CLANG kernels.
Fall back to reading and parsing the full text kallsyms only if the
ioctl is not accessible: -ENOTTY on older kernels without the ioctl, or
-EPERM if kallsyms addresses are hidden from the opener (e.g.
kptr_restrict). A symbol that is not found is left unresolved, so a
missing strong extern still fails with -ESRCH and a missing weak extern
still defaults to zero, without a full text scan.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/lib/bpf/libbpf.c | 78 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 76 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 8fcb8cf39cc0..0e94d2335bd7 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -31,6 +31,7 @@
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/filter.h>
+#include <linux/fs.h>
#include <linux/limits.h>
#include <linux/perf_event.h>
#include <linux/bpf_perf_event.h>
@@ -59,6 +60,23 @@
#define BPF_FS_MAGIC 0xcafe4a11
#endif
+#ifndef KALLSYMS_QUERY
+/* 'f' corresponds to PROCFS_IOCTL_MAGIC (include/uapi/linux/fs.h) */
+#define KALLSYMS_QUERY _IOWR('f', 18, struct kallsyms_query)
+
+enum kallsyms_query_flags {
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+};
+
+struct kallsyms_query {
+ __u64 size;
+ __u64 query_flags;
+ __u64 sym_name_addr;
+ __u64 sym_addr;
+ __u64 sym_name_size;
+};
+#endif
+
#define MAX_EVENT_NAME_LEN 64
#define BPF_FS_DEFAULT_PATH "/sys/fs/bpf"
@@ -9644,8 +9662,64 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type,
return 0;
}
-static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
+static int bpf_object__query_ksyms(struct bpf_object *obj)
+{
+ struct kallsyms_query q;
+ struct extern_desc *ext;
+ int err = 0;
+ int fd;
+ int i;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ for (i = 0; i < obj->nr_extern; i++) {
+ ext = &obj->externs[i];
+ if (ext->type != EXT_KSYM || ext->ksym.type_id)
+ continue;
+
+ q = (struct kallsyms_query){
+ .size = sizeof(q),
+ .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
+ .sym_name_addr = ptr_to_u64(ext->name),
+ };
+
+ if (ioctl(fd, KALLSYMS_QUERY, &q) < 0) {
+ err = -errno;
+ if (err == -ENOENT) {
+ err = 0;
+ continue;
+ }
+ if (err == -EADDRNOTAVAIL)
+ pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
+ ext->name);
+ break;
+ }
+ ext->is_set = true;
+ ext->ksym.addr = q.sym_addr;
+ pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
+ ext->name, ext->ksym.addr);
+ }
+ close(fd);
+ return err;
+}
+
+static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
{
+ int err;
+
+ err = bpf_object__query_ksyms(obj);
+ /*
+ * Fall back to parsing /proc/kallsyms only if the ioctl is not
+ * accessible: not present (-ENOTTY on older kernels) or kallsyms
+ * addresses hidden from this opener (-EPERM, e.g. kptr_restrict).
+ */
+ if (err != -ENOTTY && err != -EPERM)
+ return err;
+
+ pr_debug("KALLSYMS_QUERY unavailable (%s), falling back to /proc/kallsyms\n",
+ errstr(err));
return libbpf_kallsyms_parse(kallsyms_cb, obj);
}
@@ -9922,7 +9996,7 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
return -EINVAL;
}
if (need_kallsyms) {
- err = bpf_object__read_kallsyms_file(obj);
+ err = bpf_object__resolve_typeless_ksyms(obj);
if (err)
return -EINVAL;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (3 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
5 siblings, 0 replies; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Typeless module ksyms are resolved through kallsyms, but the existing
module ksym tests (ksyms_module, ksym_race) only use typed ksyms, which
are resolved through BTF. With KALLSYMS_QUERY, libbpf resolves module
symbols through the module lookup path of the ioctl.
Add a weak typeless extern for bpf_testmod_ksym_percpu to the ksyms test
and check that its address matches /proc/kallsyms when bpf_testmod is
loaded.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/testing/selftests/bpf/prog_tests/ksyms.c | 7 +++++++
tools/testing/selftests/bpf/progs/test_ksyms.c | 3 +++
2 files changed, 10 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms.c b/tools/testing/selftests/bpf/prog_tests/ksyms.c
index dc7aab532fb1..62760284f746 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksyms.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksyms.c
@@ -11,6 +11,7 @@ void test_ksyms(void)
struct test_ksyms *skel;
struct test_ksyms__data *data;
__u64 link_fops_addr, per_cpu_start_addr;
+ __u64 testmod_ksym_addr;
struct stat st;
__u64 btf_size;
int err;
@@ -48,6 +49,12 @@ void test_ksyms(void)
ASSERT_EQ(data->out__btf_size, btf_size, "btf_size");
ASSERT_EQ(data->out__per_cpu_start, per_cpu_start_addr, "__per_cpu_start");
+ if (env.has_testmod &&
+ ASSERT_OK(kallsyms_find("bpf_testmod_ksym_percpu", &testmod_ksym_addr),
+ "bpf_testmod_ksym_percpu: kallsyms_find"))
+ ASSERT_EQ(data->out__bpf_testmod_ksym, testmod_ksym_addr,
+ "bpf_testmod_ksym_percpu");
+
cleanup:
test_ksyms__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_ksyms.c b/tools/testing/selftests/bpf/progs/test_ksyms.c
index 6c9cbb5a3bdf..69309f2a2d0a 100644
--- a/tools/testing/selftests/bpf/progs/test_ksyms.c
+++ b/tools/testing/selftests/bpf/progs/test_ksyms.c
@@ -9,6 +9,7 @@ __u64 out__bpf_link_fops = -1;
__u64 out__bpf_link_fops1 = -1;
__u64 out__btf_size = -1;
__u64 out__per_cpu_start = -1;
+__u64 out__bpf_testmod_ksym = -1;
extern const void bpf_link_fops __ksym;
extern const void __start_BTF __ksym;
@@ -16,6 +17,7 @@ extern const void __stop_BTF __ksym;
extern const void __per_cpu_start __ksym;
/* non-existing symbol, weak, default to zero */
extern const void bpf_link_fops1 __ksym __weak;
+extern const void bpf_testmod_ksym_percpu __ksym __weak;
SEC("raw_tp/sys_enter")
int handler(const void *ctx)
@@ -25,6 +27,7 @@ int handler(const void *ctx)
out__per_cpu_start = (__u64)&__per_cpu_start;
out__bpf_link_fops1 = (__u64)&bpf_link_fops1;
+ out__bpf_testmod_ksym = (__u64)&bpf_testmod_ksym_percpu;
return 0;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (4 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
5 siblings, 0 replies; 9+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Add a selftest for the KALLSYMS_QUERY ioctl() on /proc/kallsyms.
For symbol-to-address lookup (KALLSYMS_QUERY_BY_SYMBOL), cover valid
lookups, missing symbols (-ENOENT), input validation (-EINVAL, -EFAULT,
-ENAMETOOLONG, -E2BIG), and extensible struct size handling.
For address-to-symbol lookup (KALLSYMS_QUERY_BY_ADDR), cover lookups at
exact symbol start and interior offsets, buffer truncation
(-ENAMETOOLONG), unmapped addresses (-ENOENT), and input validation
(-EINVAL, -EFAULT).
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/testing/selftests/proc/.gitignore | 1 +
tools/testing/selftests/proc/Makefile | 1 +
.../selftests/proc/proc-kallsyms-query.c | 213 ++++++++++++++++++
3 files changed, 215 insertions(+)
create mode 100644 tools/testing/selftests/proc/proc-kallsyms-query.c
diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 9c9735570abf..1762c28f524c 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -4,6 +4,7 @@
/fd-003-kthread
/proc-2-is-kthread
/proc-fsconfig-hidepid
+/proc-kallsyms-query
/proc-loadavg-001
/proc-maps-race
/proc-multiple-procfs
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index a7de2bb6d8be..dc3c28e6b83b 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -29,6 +29,7 @@ TEST_GEN_PROGS += setns-sysvipc
TEST_GEN_PROGS += thread-self
TEST_GEN_PROGS += proc-multiple-procfs
TEST_GEN_PROGS += proc-fsconfig-hidepid
+TEST_GEN_PROGS += proc-kallsyms-query
TEST_GEN_PROGS += proc-pidns
include ../lib.mk
diff --git a/tools/testing/selftests/proc/proc-kallsyms-query.c b/tools/testing/selftests/proc/proc-kallsyms-query.c
new file mode 100644
index 000000000000..229cde432d6a
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-kallsyms-query.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for KALLSYMS_QUERY ioctl() on /proc/kallsyms.
+ */
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <linux/fs.h>
+
+#include "kselftest.h"
+
+#define KSYM_NAME_LEN 512
+
+struct kallsyms_query_ext {
+ struct kallsyms_query v0;
+ __u64 extra;
+};
+
+static int do_query(int fd, void *q)
+{
+ return ioctl(fd, KALLSYMS_QUERY, q) < 0 ? -errno : 0;
+}
+
+static struct kallsyms_query sym_query(const char *name)
+{
+ return (struct kallsyms_query){
+ .size = sizeof(struct kallsyms_query),
+ .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
+ .sym_name_addr = (__u64)(uintptr_t)name,
+ };
+}
+
+static int query_symbol(int fd, const char *name, struct kallsyms_query *q)
+{
+ *q = sym_query(name);
+ return do_query(fd, q);
+}
+
+static int query_addr(int fd, unsigned long long addr, char *buf,
+ size_t buf_sz, struct kallsyms_query *q)
+{
+ *q = (struct kallsyms_query){
+ .size = sizeof(*q),
+ .query_flags = KALLSYMS_QUERY_BY_ADDR,
+ .sym_name_addr = (__u64)(uintptr_t)buf,
+ .sym_addr = addr,
+ .sym_name_size = buf_sz,
+ };
+ return do_query(fd, q);
+}
+
+static bool find_vmlinux_symbol(const char *target, unsigned long long *addr_out)
+{
+ char name[KSYM_NAME_LEN];
+ char line[1024];
+ char type;
+ char mod;
+ FILE *f;
+
+ f = fopen("/proc/kallsyms", "r");
+ if (!f)
+ return false;
+
+ while (fgets(line, sizeof(line), f)) {
+ if (sscanf(line, "%llx %c %511s %c", addr_out, &type, name, &mod) == 3 &&
+ strcmp(name, target) == 0) {
+ fclose(f);
+ return true;
+ }
+ }
+ fclose(f);
+ return false;
+}
+
+int main(void)
+{
+ const char *sym_name = "vfs_read";
+ char resolved_name[KSYM_NAME_LEN];
+ char long_name[KSYM_NAME_LEN + 1];
+ size_t page_size = sysconf(_SC_PAGESIZE);
+ unsigned long long exp_addr;
+ struct kallsyms_query_ext q1;
+ struct kallsyms_query q;
+ char *page_buf;
+ int err;
+ int fd;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return KSFT_SKIP;
+ ksft_exit_fail_msg("open(/proc/kallsyms) failed: %s\n",
+ strerror(errno));
+ }
+
+ if (!find_vmlinux_symbol(sym_name, &exp_addr)) {
+ close(fd);
+ return KSFT_SKIP;
+ }
+
+ /* CASE 1: Valid vmlinux symbol query (or -EPERM when addresses are hidden) */
+ err = query_symbol(fd, sym_name, &q);
+ if (err == -ENOTTY) {
+ close(fd);
+ return KSFT_SKIP;
+ }
+ if (!exp_addr) {
+ assert(err == -EPERM);
+ close(fd);
+ ksft_exit_skip("kallsyms addresses are hidden, only -EPERM checked\n");
+ }
+ assert(err == 0);
+ assert(q.sym_addr == exp_addr);
+
+ /* CASE 2: Missing symbol returns -ENOENT */
+ assert(query_symbol(fd, "__nonexistent_kallsyms_symbol__", &q) == -ENOENT);
+
+ /* CASE 3: Empty symbol name returns -EINVAL */
+ assert(query_symbol(fd, "", &q) == -EINVAL);
+
+ /* CASE 4: NULL sym_name_addr returns -EFAULT */
+ assert(query_symbol(fd, NULL, &q) == -EFAULT);
+
+ /* CASE 5: Symbol name >= KSYM_NAME_LEN returns -ENAMETOOLONG */
+ memset(long_name, 'a', KSYM_NAME_LEN);
+ long_name[KSYM_NAME_LEN] = '\0';
+ assert(query_symbol(fd, long_name, &q) == -ENAMETOOLONG);
+
+ /* CASE 6: Symbol name == KSYM_NAME_LEN - 1 returns -ENOENT */
+ long_name[KSYM_NAME_LEN - 1] = '\0';
+ assert(query_symbol(fd, long_name, &q) == -ENOENT);
+
+ /* CASE 7: Zero, multiple, or unknown query_flags return -EINVAL */
+ q = sym_query(sym_name);
+ q.query_flags = 0;
+ assert(do_query(fd, &q) == -EINVAL);
+ q.query_flags = KALLSYMS_QUERY_BY_SYMBOL | KALLSYMS_QUERY_BY_ADDR;
+ assert(do_query(fd, &q) == -EINVAL);
+ q.query_flags = 1ULL << 63;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 8: Non-zero output fields on input in symbol mode return -EINVAL */
+ q = sym_query(sym_name);
+ q.sym_addr = 1;
+ assert(do_query(fd, &q) == -EINVAL);
+ q = sym_query(sym_name);
+ q.sym_name_size = 1;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 9: size < minimum size returns -EINVAL */
+ q = sym_query(sym_name);
+ q.size = KALLSYMS_QUERY_SIZE_VER0 - 8;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 10: size > PAGE_SIZE returns -E2BIG */
+ q = sym_query(sym_name);
+ q.size = page_size + 8;
+ assert(do_query(fd, &q) == -E2BIG);
+
+ /* CASE 11: Larger extensible struct with zeroed trailing bytes succeeds */
+ q1 = (struct kallsyms_query_ext){ .v0 = sym_query(sym_name) };
+ q1.v0.size = sizeof(q1);
+ assert(do_query(fd, &q1) == 0);
+ assert(q1.v0.sym_addr == exp_addr);
+
+ /*
+ * CASE 12: Larger struct with non-zero trailing bytes returns -E2BIG.
+ * Set the last byte of a page-sized struct so that future fields do
+ * not change the result.
+ */
+ page_buf = calloc(1, page_size);
+ assert(page_buf);
+ q = sym_query(sym_name);
+ q.size = page_size;
+ memcpy(page_buf, &q, sizeof(q));
+ page_buf[page_size - 1] = 1;
+ assert(do_query(fd, page_buf) == -E2BIG);
+ free(page_buf);
+
+ /* CASE 13: Valid address-to-symbol query (exact start + interior offset) */
+ memset(resolved_name, 0, sizeof(resolved_name));
+ assert(query_addr(fd, exp_addr, resolved_name, sizeof(resolved_name), &q) == 0);
+ assert(strcmp(resolved_name, sym_name) == 0);
+ assert(q.sym_addr == exp_addr);
+ assert(q.sym_name_size == strlen(sym_name) + 1);
+
+ memset(resolved_name, 0, sizeof(resolved_name));
+ assert(query_addr(fd, exp_addr + 1, resolved_name, sizeof(resolved_name), &q) == 0);
+ assert(strcmp(resolved_name, sym_name) == 0);
+ assert(q.sym_addr == exp_addr + 1);
+ assert(q.sym_name_size == strlen(sym_name) + 1);
+
+ /* CASE 14: KALLSYMS_QUERY_BY_ADDR with buffer too small returns -ENAMETOOLONG */
+ assert(query_addr(fd, exp_addr, resolved_name, strlen(sym_name), &q) == -ENAMETOOLONG);
+
+ /* CASE 15: KALLSYMS_QUERY_BY_ADDR on unmapped address returns -ENOENT */
+ assert(query_addr(fd, ~0ULL, resolved_name, sizeof(resolved_name), &q) == -ENOENT);
+
+ /* CASE 16: KALLSYMS_QUERY_BY_ADDR invalid inputs return -EINVAL or -EFAULT */
+ assert(query_addr(fd, exp_addr, resolved_name, 0, &q) == -EINVAL);
+ assert(query_addr(fd, exp_addr, NULL, sizeof(resolved_name), &q) == -EFAULT);
+
+ close(fd);
+ return 0;
+}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
@ 2026-10-23 0:51 ` bot+bpf-ci
0 siblings, 0 replies; 9+ messages in thread
From: bot+bpf-ci @ 2026-10-23 0:51 UTC (permalink / raw)
To: wusamuel, corbet, skhan, rdunlap, viro, brauner, jack, andrii,
eddyz87, ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt
Cc: wusamuel, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3742 bytes --]
> kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
TL;DR: KALLSYMS_QUERY_BY_ADDR may return BPF JIT symbol names that
bpf_jit_kallsyms=0 or bpf_jit_harden hide in /proc/kallsyms, possibly leaking
JIT image locations to openers with show_value.
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> index aec2f06858af..7caaf3e421c7 100644
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
[ ... ]
> @@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
[ ... ]
> +static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
> +{
[ ... ]
> +
> + /*
> + * The text interface shows zero addresses to openers without permission.
> + * A zeroed point query result is useless (and an address-to-symbol query
> + * would leak KASLR), so fail instead.
> + */
> + if (!iter->show_value)
[ ... ]
> + case KALLSYMS_QUERY_BY_ADDR:
> + if (!karg.sym_name_size)
> + return -EINVAL;
> + addr = karg.sym_addr;
> + /* Reject addresses that do not fit in unsigned long (32-bit). */
> + if (addr != karg.sym_addr)
> + return -ENOENT;
> + if (!kallsyms_lookup(addr, NULL, NULL, NULL, namebuf))
> + return -ENOENT;
Can KALLSYMS_QUERY_BY_ADDR hand out BPF JIT symbol names that /proc/kallsyms
is configured to hide?
kallsyms_lookup() reaches kallsyms_lookup_buildid() and falls through to
bpf_address_lookup() -> bpf_ksym_find(), which searches bpf_tree without any
policy check. bpf_ksym_add() always inserts into bpf_tree
(bpf_prog_kallsyms_add() only requires bpf_prog_kallsyms_candidate() and
CAP_BPF).
The text listing of the same file goes through get_ksymbol_bpf() ->
bpf_get_kallsym(), which starts with:
if (!bpf_jit_kallsyms_enabled())
return ret;
bpf_jit_kallsyms_enabled() returns false when net.core.bpf_jit_harden is
non-zero or net.core.bpf_jit_kallsyms is 0. bpf_get_kallsym() is the only
caller of bpf_jit_kallsyms_enabled() in the tree, so hiding the names in
/proc/kallsyms is now the only thing these sysctls do. Commit af91acbc6299
("bpf: Fix bpf jit kallsym access") says so directly:
It should only skip addr->name resolution for 'cat /proc/kallsyms'.
That's what bpf_jit_kallsyms and bpf_jit_harden protect.
With this patch, a process whose open of /proc/kallsyms has show_value set
can call ioctl(KALLSYMS_QUERY) with query_flags=KALLSYMS_QUERY_BY_ADDR and
get back names like bpf_prog_<tag>_<name> (plus trampolines, dispatchers and
__cfi_ prefixes) for addresses inside JIT images. That gives it an
address->name oracle for finding JIT image locations, which these sysctls
are meant to withhold from /proc/kallsyms.
The comment above the show_value check argues for matching the text
interface ("The text interface shows zero addresses to openers without
permission ... an address-to-symbol query would leak KASLR, so fail
instead"), but the code applies only the show_value gate and skips the
BPF-specific gate. The uapi comment and the commit message both say
BY_ADDR covers "BPF JIT images", but neither mentions bpf_jit_kallsyms or
bpf_jit_harden.
This is reachable whenever CONFIG_KALLSYMS and CONFIG_BPF_JIT are enabled, a
JITed program loaded with CAP_BPF exists, and either sysctl hides BPF
symbols. No later commit in the series changes kernel/ code to address
this.
Should the BY_ADDR path return -ENOENT when kallsyms_lookup() resolves to a
BPF address (for example, is_bpf_text_address(addr) is true and the address
is not in vmlinux or a module) unless bpf_jit_kallsyms_enabled() is true?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39878338337
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
@ 2026-10-23 0:51 ` bot+bpf-ci
0 siblings, 0 replies; 9+ messages in thread
From: bot+bpf-ci @ 2026-10-23 0:51 UTC (permalink / raw)
To: wusamuel, corbet, skhan, rdunlap, viro, brauner, jack, andrii,
eddyz87, ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt
Cc: wusamuel, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6001 bytes --]
> libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
TL;DR:
- Possible regression: ioctl lookup ignores .llvm. suffixed symbols, so
typeless ksyms may fail to load or resolve to 0 on LTO kernels
- Fallback to kallsyms parsing may miss -EACCES (SELinux/seccomp), failing
loads that worked before
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 8fcb8cf39cc0..0e94d2335bd7 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -9644,8 +9662,64 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type,
> return 0;
> }
>
> -static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
> +static int bpf_object__query_ksyms(struct bpf_object *obj)
> +{
> + struct kallsyms_query q;
> + struct extern_desc *ext;
> + int err = 0;
> + int fd;
> + int i;
> +
> + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> + if (fd < 0)
> + return -errno;
> +
> + for (i = 0; i < obj->nr_extern; i++) {
> + ext = &obj->externs[i];
> + if (ext->type != EXT_KSYM || ext->ksym.type_id)
> + continue;
> +
> + q = (struct kallsyms_query){
> + .size = sizeof(q),
> + .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
> + .sym_name_addr = ptr_to_u64(ext->name),
> + };
> +
> + if (ioctl(fd, KALLSYMS_QUERY, &q) < 0) {
> + err = -errno;
> + if (err == -ENOENT) {
> + err = 0;
> + continue;
> + }
> + if (err == -EADDRNOTAVAIL)
> + pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
> + ext->name);
> + break;
> + }
Can this regress typeless ksyms on CONFIG_LTO_CLANG kernels? The old
kallsyms_cb() deliberately matched data symbols that carry a suffix:
res = strstr(sym_name, ".llvm.");
if (sym_type == 'd' && res)
ext = find_extern_by_name_with_len(obj, sym_name, res - sym_name);
That was added by commit c56e59776f46 ("libbpf: Handle <orig_name>.llvm.<hash>
symbol properly") after test_ksyms failed on a CONFIG_LTO_CLANG_THIN kernel,
where bpf_link_fops had been renamed to bpf_link_fops.llvm.<hash>. That
commit also lists tk_core, fake_dst_ops and __fs_reclaim_map as real
renamed symbols.
On the kernel side, KALLSYMS_QUERY only matches names exactly:
kallsyms_lookup_names() does a strcmp() on the expanded name, and
kallsyms_mod_find_cb() also uses strcmp(). So a lookup for "bpf_link_fops"
returns -ENOENT when only "bpf_link_fops.llvm.<hash>" exists.
Here -ENOENT is treated as "not found", err is cleared and the loop
continues. bpf_object__resolve_typeless_ksyms() then falls back only on
-ENOTTY or -EPERM, so libbpf_kallsyms_parse() is never reached. On a
kernel that has this ioctl:
- a strong "extern const void foo __ksym;" now makes bpf_object__load()
fail with "extern 'foo' (strong): not resolved" (-ESRCH), where it
loaded before
- a weak one silently gets address 0 instead of the real address
The selftests helper kallsyms_find() in trace_helpers.c still strips
".llvm." from 'd' symbols, so it and libbpf now disagree about what
test_ksyms (bpf_link_fops) should resolve to.
The commit message notes this difference, but gives no reason for dropping
behavior that libbpf added on purpose. libbpf kept the .llvm. handling
elsewhere to avoid regressions, and commit f8a05692de06 says "we'd like to
not regress".
Would it work to fall back to libbpf_kallsyms_parse() for typeless externs
that are still unresolved after the ioctl pass? That only costs extra time
when a symbol is actually missing. Alternatively, the kernel query could
match the .llvm. suffix for data symbols. I did not find a fix for this in
the rest of the series.
> + ext->is_set = true;
> + ext->ksym.addr = q.sym_addr;
> + pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
> + ext->name, ext->ksym.addr);
> + }
> + close(fd);
> + return err;
> +}
> +
> +static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
> {
> + int err;
> +
> + err = bpf_object__query_ksyms(obj);
> + /*
> + * Fall back to parsing /proc/kallsyms only if the ioctl is not
> + * accessible: not present (-ENOTTY on older kernels) or kallsyms
> + * addresses hidden from this opener (-EPERM, e.g. kptr_restrict).
> + */
> + if (err != -ENOTTY && err != -EPERM)
> + return err;
Does this fallback miss other ways the ioctl can be blocked while reading
/proc/kallsyms is still allowed?
The old code never issued an ioctl on this file. Now every KALLSYMS_QUERY
goes through security_file_ioctl(). For this command selinux_file_ioctl()
takes the default case and calls ioctl_has_perm(cred, file, FILE__IOCTL,
(u16)cmd), which returns -EACCES when the policy grants read on the
kallsyms label but not ioctl, or limits ioctl commands with allowxperm. A
seccomp filter that makes ioctl() return an errno other than EPERM has the
same effect.
In those setups bpf_object__query_ksyms() returns -EACCES (or similar),
bpf_object__resolve_typeless_ksyms() returns it as-is, and
bpf_object__resolve_externs() turns it into -EINVAL:
err = bpf_object__resolve_typeless_ksyms(obj);
if (err)
return -EINVAL;
So an object with any typeless ksym now fails to load, even though parsing
the text file would have worked as it did before this patch.
The failure is also hard to diagnose. Apart from -EADDRNOTAVAIL, nothing
from the ioctl or from open() is reported. The open() failure path is a
bare:
fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
without the "failed to open /proc/kallsyms" pr_warn() that
libbpf_kallsyms_parse() prints, so the user only sees "failed to load
object".
Should the fallback cover any error other than -EADDRNOTAVAIL (and other
real lookup errors), or at least -EACCES as well, with a pr_warn() for the
errors that are not handled?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39878338337
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-23 0:51 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-23 0:16 ` [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-23 0:16 ` [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®