* [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps
@ 2024-06-18 22:45 Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 1/6] fs/procfs: extract logic for getting VMA name constituents Andrii Nakryiko
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
Implement binary ioctl()-based interface to /proc/<pid>/maps file to allow
applications to query VMA information more efficiently than reading *all* VMAs
nonselectively through text-based interface of /proc/<pid>/maps file.
Patch #2 goes into a lot of details and background on some common patterns of
using /proc/<pid>/maps in the area of performance profiling and subsequent
symbolization of captured stack traces. As mentioned in that patch, patterns
of VMA querying can differ depending on specific use case, but can generally
be grouped into two main categories: the need to query a small subset of VMAs
covering a given batch of addresses, or reading/storing/caching all
(typically, executable) VMAs upfront for later processing.
The new PROCMAP_QUERY ioctl() API added in this patch set was motivated by the
former pattern of usage. Earlier revisions had a patch adding a tool that
faithfully reproduces an efficient VMA matching pass of a symbolizer,
collecting a subset of covering VMAs for a given set of addresses as
efficiently as possible. This tool served both as a testing ground, as well as
a benchmarking tool. It implements everything both for currently existing
text-based /proc/<pid>/maps interface, as well as for newly-added
PROCMAP_QUERY ioctl(). This revision dropped the tool from the patch set and,
once the API lands upstream, this tool might be added separately on Github as
an example.
Based on discussion on earlier revisions of this patch set, it turned out
that this ioctl() API is competitive with highly-optimized text-based
pre-processing pattern that perf tool is using. Based on perf discussion, this
revision adds more flexibility in specifying a subset of VMAs that are of
interest. Now it's possible to specify desired permissions of VMAs (e.g.,
request only executable ones) and/or restrict to only a subset of VMAs that
have file backing. This further improves the efficiency when using this new
API thanks to more selective (executable VMAs only) querying.
In addition to a custom benchmarking tool, and experimental perf integration
(available at [0]), Daniel Mueller has since also implemented an experimental
integration into blazesym (see [1]), a library used for stack trace
symbolization by our server fleet-wide profiler and another on-device profiler
agent that runs on weaker ARM devices. The latter ARM-based device profiler is
especially sensitive to performance, and so we benchmarked and compared
text-based /proc/<pid>/maps solution to the equivalent one using PROCMAP_QUERY
ioctl().
Results are very encouraging, giving us 5x improvement for end-to-end
so-called "address normalization" pass, which is the part of the symbolization
process that happens locally on ARM device, before being sent out for further
heavier-weight processing on more powerful remote server. Note that this is
not an artificial microbenchmark. It's a full end-to-end API call being
measured with real-world data on real-world device.
TEXT-BASED
==========
Benchmarking main/normalize_process_no_build_ids_uncached_maps
main/normalize_process_no_build_ids_uncached_maps
time: [49.777 µs 49.982 µs 50.250 µs]
IOCTL-BASED
===========
Benchmarking main/normalize_process_no_build_ids_uncached_maps
main/normalize_process_no_build_ids_uncached_maps
time: [10.328 µs 10.391 µs 10.457 µs]
change: [−79.453% −79.304% −79.166%] (p = 0.00 < 0.02)
Performance has improved.
You can see above that we see the drop from 50µs down to 10µs for exactly
the same amount of work, with the same data and target process.
With the aforementioned custom tool, we see about ~40x improvement (it might
vary a bit, depending on a specific captured set of addresses). And even for
perf-based benchmark it's on par or slightly ahead when using permission-based
filtering (fetching only executable VMAs).
Earlier revisions attempted to use per-VMA locking, if kernel was compiled
with CONFIG_PER_VMA_LOCK=y, but it turned out that anon_vma_name() is not yet
compatible with per-VMA locking and assumes mmap_lock to be taken, which makes
the use of per-VMA locking for this API premature. It was agreed ([2]) to
continue for now with just mmap_lock, but the code structure is such that it
should be easy to add per-VMA locking support once all the pieces are ready.
One thing that did not change was basing this new API as an ioctl() command
on /proc/<pid>/maps file. An ioctl-based API on top of pidfd was considered,
but has its own downsides. Implementing ioctl() directly on pidfd will cause
access permission checks on every single ioctl(), which leads to performance
concerns and potential spam of capable() audit messages. It also prevents
a nice pattern, possible with /proc/<pid>/maps, in which application opens
/proc/self/maps FD (requiring no additional capabilities) and passed this FD
to profiling agent for querying. To achieve similar pattern, a new file would
have to be created from pidf just for VMA querying, which is considered to be
inferior to just querying /proc/<pid>/maps FD as proposed in current approach.
These aspects were discussed in the hallway track at recent LSF/MM/BPF 2024
and sticking to procfs ioctl() was the final agreement we arrived at.
This patch set is based on top of mm-unstable branch in mm tree.
[0] https://github.com/anakryiko/linux/commits/procfs-proc-maps-ioctl-v2/
[1] https://github.com/libbpf/blazesym/pull/675
[2] https://lore.kernel.org/bpf/7rm3izyq2vjp5evdjc7c6z4crdd3oerpiknumdnmmemwyiwx7t@hleldw7iozi3/
v4->v5:
- added tests in selftests/proc (Andrew);
- added vma_page_size field (Liam);
- dropped the benchmark tool and BPF selftests parts, I'll send them
directly to bpf-next once this API makes it into that tree;
v3->v4:
- drop per-VMA locking changes for now, we'll need anon_vma_name() to be
compatible with vma->vm_lock approach (Suren, Liam);
v2->v3:
- drop mmap_lock aggressively under CONFIG_PER_VMA_LOCK (Liam);
- code massaging to abstract per-VMA vs mmap_lock differences (Liam);
v1->v2:
- per-VMA lock is used, if possible (Liam, Suren);
- added file-backed VMA querying (perf folks);
- added permission-based VMA querying (perf folks);
- split out build ID into separate patch (Suren);
- better documented API, added mention of ioctl() into procfs docs (Greg).
Andrii Nakryiko (6):
fs/procfs: extract logic for getting VMA name constituents
fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps
fs/procfs: add build ID fetching to PROCMAP_QUERY API
docs/procfs: call out ioctl()-based PROCMAP_QUERY command existence
tools: sync uapi/linux/fs.h header into tools subdir
selftests/proc: add PROCMAP_QUERY ioctl tests
Documentation/filesystems/proc.rst | 9 +
fs/proc/task_mmu.c | 383 ++++++++++++++++++---
include/uapi/linux/fs.h | 158 ++++++++-
tools/include/uapi/linux/fs.h | 184 +++++++++-
tools/testing/selftests/proc/Makefile | 1 +
tools/testing/selftests/proc/proc-pid-vm.c | 86 +++++
6 files changed, 754 insertions(+), 67 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 1/6] fs/procfs: extract logic for getting VMA name constituents
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps Andrii Nakryiko
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
Extract generic logic to fetch relevant pieces of data to describe VMA
name. This could be just some string (either special constant or
user-provided), or a string with some formatted wrapping text (e.g.,
"[anon_shmem:<something>]"), or, commonly, file path. seq_file-based
logic has different methods to handle all three cases, but they are
currently mixed in with extracting underlying sources of data.
This patch splits this into data fetching and data formatting, so that
data fetching can be reused later on.
There should be no functional changes.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
fs/proc/task_mmu.c | 125 +++++++++++++++++++++++++--------------------
1 file changed, 71 insertions(+), 54 deletions(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 93fb2c61b154..507b7dc7c4c8 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -239,6 +239,67 @@ static int do_maps_open(struct inode *inode, struct file *file,
sizeof(struct proc_maps_private));
}
+static void get_vma_name(struct vm_area_struct *vma,
+ const struct path **path,
+ const char **name,
+ const char **name_fmt)
+{
+ struct anon_vma_name *anon_name = vma->vm_mm ? anon_vma_name(vma) : NULL;
+
+ *name = NULL;
+ *path = NULL;
+ *name_fmt = NULL;
+
+ /*
+ * Print the dentry name for named mappings, and a
+ * special [heap] marker for the heap:
+ */
+ if (vma->vm_file) {
+ /*
+ * If user named this anon shared memory via
+ * prctl(PR_SET_VMA ..., use the provided name.
+ */
+ if (anon_name) {
+ *name_fmt = "[anon_shmem:%s]";
+ *name = anon_name->name;
+ } else {
+ *path = file_user_path(vma->vm_file);
+ }
+ return;
+ }
+
+ if (vma->vm_ops && vma->vm_ops->name) {
+ *name = vma->vm_ops->name(vma);
+ if (*name)
+ return;
+ }
+
+ *name = arch_vma_name(vma);
+ if (*name)
+ return;
+
+ if (!vma->vm_mm) {
+ *name = "[vdso]";
+ return;
+ }
+
+ if (vma_is_initial_heap(vma)) {
+ *name = "[heap]";
+ return;
+ }
+
+ if (vma_is_initial_stack(vma)) {
+ *name = "[stack]";
+ return;
+ }
+
+ if (anon_name) {
+ *name_fmt = "[anon:%s]";
+ *name = anon_name->name;
+ return;
+ }
+}
+
static void show_vma_header_prefix(struct seq_file *m,
unsigned long start, unsigned long end,
vm_flags_t flags, unsigned long long pgoff,
@@ -262,17 +323,15 @@ static void show_vma_header_prefix(struct seq_file *m,
static void
show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
{
- struct anon_vma_name *anon_name = NULL;
- struct mm_struct *mm = vma->vm_mm;
- struct file *file = vma->vm_file;
+ const struct path *path;
+ const char *name_fmt, *name;
vm_flags_t flags = vma->vm_flags;
unsigned long ino = 0;
unsigned long long pgoff = 0;
unsigned long start, end;
dev_t dev = 0;
- const char *name = NULL;
- if (file) {
+ if (vma->vm_file) {
const struct inode *inode = file_user_inode(vma->vm_file);
dev = inode->i_sb->s_dev;
@@ -283,57 +342,15 @@ show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
start = vma->vm_start;
end = vma->vm_end;
show_vma_header_prefix(m, start, end, flags, pgoff, dev, ino);
- if (mm)
- anon_name = anon_vma_name(vma);
- /*
- * Print the dentry name for named mappings, and a
- * special [heap] marker for the heap:
- */
- if (file) {
+ get_vma_name(vma, &path, &name, &name_fmt);
+ if (path) {
seq_pad(m, ' ');
- /*
- * If user named this anon shared memory via
- * prctl(PR_SET_VMA ..., use the provided name.
- */
- if (anon_name)
- seq_printf(m, "[anon_shmem:%s]", anon_name->name);
- else
- seq_path(m, file_user_path(file), "\n");
- goto done;
- }
-
- if (vma->vm_ops && vma->vm_ops->name) {
- name = vma->vm_ops->name(vma);
- if (name)
- goto done;
- }
-
- name = arch_vma_name(vma);
- if (!name) {
- if (!mm) {
- name = "[vdso]";
- goto done;
- }
-
- if (vma_is_initial_heap(vma)) {
- name = "[heap]";
- goto done;
- }
-
- if (vma_is_initial_stack(vma)) {
- name = "[stack]";
- goto done;
- }
-
- if (anon_name) {
- seq_pad(m, ' ');
- seq_printf(m, "[anon:%s]", anon_name->name);
- }
- }
-
-done:
- if (name) {
+ seq_path(m, path, "\n");
+ } else if (name_fmt) {
+ seq_pad(m, ' ');
+ seq_printf(m, name_fmt, name);
+ } else if (name) {
seq_pad(m, ' ');
seq_puts(m, name);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 1/6] fs/procfs: extract logic for getting VMA name constituents Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
2024-06-26 2:42 ` Liam R. Howlett
2024-06-18 22:45 ` [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API Andrii Nakryiko
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
/proc/<pid>/maps file is extremely useful in practice for various tasks
involving figuring out process memory layout, what files are backing any
given memory range, etc. One important class of applications that
absolutely rely on this are profilers/stack symbolizers (perf tool being one
of them). Patterns of use differ, but they generally would fall into two
categories.
In on-demand pattern, a profiler/symbolizer would normally capture stack
trace containing absolute memory addresses of some functions, and would
then use /proc/<pid>/maps file to find corresponding backing ELF files
(normally, only executable VMAs are of interest), file offsets within
them, and then continue from there to get yet more information (ELF
symbols, DWARF information) to get human-readable symbolic information.
This pattern is used by Meta's fleet-wide profiler, as one example.
In preprocessing pattern, application doesn't know the set of addresses
of interest, so it has to fetch all relevant VMAs (again, probably only
executable ones), store or cache them, then proceed with profiling and
stack trace capture. Once done, it would do symbolization based on
stored VMA information. This can happen at much later point in time.
This patterns is used by perf tool, as an example.
In either case, there are both performance and correctness requirement
involved. This address to VMA information translation has to be done as
efficiently as possible, but also not miss any VMA (especially in the
case of loading/unloading shared libraries). In practice, correctness
can't be guaranteed (due to process dying before VMA data can be
captured, or shared library being unloaded, etc), but any effort to
maximize the chance of finding the VMA is appreciated.
Unfortunately, for all the /proc/<pid>/maps file universality and
usefulness, it doesn't fit the above use cases 100%.
First, it's main purpose is to emit all VMAs sequentially, but in
practice captured addresses would fall only into a smaller subset of all
process' VMAs, mainly containing executable text. Yet, library would
need to parse most or all of the contents to find needed VMAs, as there
is no way to skip VMAs that are of no use. Efficient library can do the
linear pass and it is still relatively efficient, but it's definitely an
overhead that can be avoided, if there was a way to do more targeted
querying of the relevant VMA information.
Second, it's a text based interface, which makes its programmatic use from
applications and libraries more cumbersome and inefficient due to the
need to handle text parsing to get necessary pieces of information. The
overhead is actually payed both by kernel, formatting originally binary
VMA data into text, and then by user space application, parsing it back
into binary data for further use.
For the on-demand pattern of usage, described above, another problem
when writing generic stack trace symbolization library is an unfortunate
performance-vs-correctness tradeoff that needs to be made. Library has
to make a decision to either cache parsed contents of /proc/<pid>/maps
(after initial processing) to service future requests (if application
requests to symbolize another set of addresses (for the same process),
captured at some later time, which is typical for periodic/continuous
profiling cases) to avoid higher costs of re-parsing this file. Or it
has to choose to cache the contents in memory to speed up future
requests. In the former case, more memory is used for the cache and
there is a risk of getting stale data if application loads or unloads
shared libraries, or otherwise changed its set of VMAs somehow, e.g.,
through additional mmap() calls. In the latter case, it's the
performance hit that comes from re-opening the file and re-parsing its
contents all over again.
This patch aims to solve this problem by providing a new API built on
top of /proc/<pid>/maps. It's meant to address both non-selectiveness
and text nature of /proc/<pid>/maps, by giving user more control of what
sort of VMA(s) needs to be queried, and being binary-based interface
eliminates the overhead of text formatting (on kernel side) and parsing
(on user space side).
It's also designed to be extensible and forward/backward compatible by
including required struct size field, which user has to provide. We use
established copy_struct_from_user() approach to handle extensibility.
User has a choice to pick either getting VMA that covers provided
address or -ENOENT if none is found (exact, least surprising, case). Or,
with an extra query flag (PROCMAP_QUERY_COVERING_OR_NEXT_VMA), they can
get either VMA that covers the address (if there is one), or the closest
next VMA (i.e., VMA with the smallest vm_start > addr). The latter allows
more efficient use, but, given it could be a surprising behavior,
requires an explicit opt-in.
There is another query flag that is useful for some use cases.
PROCMAP_QUERY_FILE_BACKED_VMA instructs this API to only return
file-backed VMAs. Combining this with PROCMAP_QUERY_COVERING_OR_NEXT_VMA
makes it possible to efficiently iterate only file-backed VMAs of the
process, which is what profilers/symbolizers are normally interested in.
All the above querying flags can be combined with (also optional) set of
desired VMA permissions flags. This allows to, for example, iterate only
an executable subset of VMAs, which is what preprocessing pattern, used
by perf tool, would benefit from, as the assumption is that captured
stack traces would have addresses of executable code. This saves time by
skipping non-executable VMAs altogether efficienty.
All these querying flags (modifiers) are orthogonal and can be combined
in a semantically meaningful and natural way.
Basing this ioctl()-based API on top of /proc/<pid>/maps's FD makes
sense given it's querying the same set of VMA data. It's also benefitial
because permission checks for /proc/<pid>/maps is performed at open time
once, and the actual data read of text contents of /proc/<pid>/maps is
done without further permission checks. We piggyback on this pattern
with ioctl()-based API as well, as that's a desired property. Both for
performance reasons, but also for security and flexibility reasons.
Allowing application to open an FD for /proc/self/maps without any extra
capabilities, and then passing it to some sort of profiling agent
through Unix-domain socket, would allow such profiling agent to not
require some of the capabilities that are otherwise expected when
opening /proc/<pid>/maps file for *another* process. This is a desirable
property for some more restricted setups.
This new ioctl-based implementation doesn't interfere with
seq_file-based implementation of /proc/<pid>/maps textual interface, and
so could be used together or independently without paying any price for
that.
Note also, that fetching VMA name (e.g., backing file path, or special
hard-coded or user-provided names) is optional just like build ID. If
user sets vma_name_size to zero, kernel code won't attempt to retrieve
it, saving resources.
Earlier versions of this patch set were adding per-VMA locking, which is
why we have a code structure that is ready for abstracting mmap_lock vs
vm_lock differences (query_vma_setup(), query_vma_teardown(), and
query_vma_find_by_addr()), but given anon_vma_name() is not yet compatible
with per-VMA locking, initial implementation sticks to using only
mmap_lock for now. It will be easy to add back per-VMA locking once all
the pieces are ready later on. Which is why we keep existing code
structure with setup/teardown/query helper functions.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
fs/proc/task_mmu.c | 235 ++++++++++++++++++++++++++++++++++++++++
include/uapi/linux/fs.h | 130 +++++++++++++++++++++-
2 files changed, 364 insertions(+), 1 deletion(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 507b7dc7c4c8..674405b99d0d 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -375,11 +375,246 @@ static int pid_maps_open(struct inode *inode, struct file *file)
return do_maps_open(inode, file, &proc_pid_maps_op);
}
+#define PROCMAP_QUERY_VMA_FLAGS ( \
+ PROCMAP_QUERY_VMA_READABLE | \
+ PROCMAP_QUERY_VMA_WRITABLE | \
+ PROCMAP_QUERY_VMA_EXECUTABLE | \
+ PROCMAP_QUERY_VMA_SHARED \
+)
+
+#define PROCMAP_QUERY_VALID_FLAGS_MASK ( \
+ PROCMAP_QUERY_COVERING_OR_NEXT_VMA | \
+ PROCMAP_QUERY_FILE_BACKED_VMA | \
+ PROCMAP_QUERY_VMA_FLAGS \
+)
+
+static int query_vma_setup(struct mm_struct *mm)
+{
+ return mmap_read_lock_killable(mm);
+}
+
+static void query_vma_teardown(struct mm_struct *mm, struct vm_area_struct *vma)
+{
+ mmap_read_unlock(mm);
+}
+
+static struct vm_area_struct *query_vma_find_by_addr(struct mm_struct *mm, unsigned long addr)
+{
+ return find_vma(mm, addr);
+}
+
+static struct vm_area_struct *query_matching_vma(struct mm_struct *mm,
+ unsigned long addr, u32 flags)
+{
+ struct vm_area_struct *vma;
+
+next_vma:
+ vma = query_vma_find_by_addr(mm, addr);
+ if (!vma)
+ goto no_vma;
+
+ /* user requested only file-backed VMA, keep iterating */
+ if ((flags & PROCMAP_QUERY_FILE_BACKED_VMA) && !vma->vm_file)
+ goto skip_vma;
+
+ /* VMA permissions should satisfy query flags */
+ if (flags & PROCMAP_QUERY_VMA_FLAGS) {
+ u32 perm = 0;
+
+ if (flags & PROCMAP_QUERY_VMA_READABLE)
+ perm |= VM_READ;
+ if (flags & PROCMAP_QUERY_VMA_WRITABLE)
+ perm |= VM_WRITE;
+ if (flags & PROCMAP_QUERY_VMA_EXECUTABLE)
+ perm |= VM_EXEC;
+ if (flags & PROCMAP_QUERY_VMA_SHARED)
+ perm |= VM_MAYSHARE;
+
+ if ((vma->vm_flags & perm) != perm)
+ goto skip_vma;
+ }
+
+ /* found covering VMA or user is OK with the matching next VMA */
+ if ((flags & PROCMAP_QUERY_COVERING_OR_NEXT_VMA) || vma->vm_start <= addr)
+ return vma;
+
+skip_vma:
+ /*
+ * If the user needs closest matching VMA, keep iterating.
+ */
+ addr = vma->vm_end;
+ if (flags & PROCMAP_QUERY_COVERING_OR_NEXT_VMA)
+ goto next_vma;
+no_vma:
+ return ERR_PTR(-ENOENT);
+}
+
+static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
+{
+ struct procmap_query karg;
+ struct vm_area_struct *vma;
+ struct mm_struct *mm;
+ const char *name = NULL;
+ char *name_buf = NULL;
+ __u64 usize;
+ int err;
+
+ if (copy_from_user(&usize, (void __user *)uarg, sizeof(usize)))
+ return -EFAULT;
+ /* argument struct can never be that large, reject abuse */
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+ /* argument struct should have at least query_flags and query_addr fields */
+ if (usize < offsetofend(struct procmap_query, query_addr))
+ return -EINVAL;
+ err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
+ if (err)
+ return err;
+
+ /* reject unknown flags */
+ if (karg.query_flags & ~PROCMAP_QUERY_VALID_FLAGS_MASK)
+ return -EINVAL;
+ /* either both buffer address and size are set, or both should be zero */
+ if (!!karg.vma_name_size != !!karg.vma_name_addr)
+ return -EINVAL;
+
+ mm = priv->mm;
+ if (!mm || !mmget_not_zero(mm))
+ return -ESRCH;
+
+ err = query_vma_setup(mm);
+ if (err) {
+ mmput(mm);
+ return err;
+ }
+
+ vma = query_matching_vma(mm, karg.query_addr, karg.query_flags);
+ if (IS_ERR(vma)) {
+ err = PTR_ERR(vma);
+ vma = NULL;
+ goto out;
+ }
+
+ karg.vma_start = vma->vm_start;
+ karg.vma_end = vma->vm_end;
+
+ karg.vma_flags = 0;
+ if (vma->vm_flags & VM_READ)
+ karg.vma_flags |= PROCMAP_QUERY_VMA_READABLE;
+ if (vma->vm_flags & VM_WRITE)
+ karg.vma_flags |= PROCMAP_QUERY_VMA_WRITABLE;
+ if (vma->vm_flags & VM_EXEC)
+ karg.vma_flags |= PROCMAP_QUERY_VMA_EXECUTABLE;
+ if (vma->vm_flags & VM_MAYSHARE)
+ karg.vma_flags |= PROCMAP_QUERY_VMA_SHARED;
+
+ karg.vma_page_size = vma_kernel_pagesize(vma);
+
+ if (vma->vm_file) {
+ const struct inode *inode = file_user_inode(vma->vm_file);
+
+ karg.vma_offset = ((__u64)vma->vm_pgoff) << PAGE_SHIFT;
+ karg.dev_major = MAJOR(inode->i_sb->s_dev);
+ karg.dev_minor = MINOR(inode->i_sb->s_dev);
+ karg.inode = inode->i_ino;
+ } else {
+ karg.vma_offset = 0;
+ karg.dev_major = 0;
+ karg.dev_minor = 0;
+ karg.inode = 0;
+ }
+
+ if (karg.build_id_size) {
+ __u32 build_id_sz;
+
+ err = build_id_parse(vma, build_id_buf, &build_id_sz);
+ if (err) {
+ karg.build_id_size = 0;
+ } else {
+ if (karg.build_id_size < build_id_sz) {
+ err = -ENAMETOOLONG;
+ goto out;
+ }
+ karg.build_id_size = build_id_sz;
+ }
+ }
+
+ if (karg.vma_name_size) {
+ size_t name_buf_sz = min_t(size_t, PATH_MAX, karg.vma_name_size);
+ const struct path *path;
+ const char *name_fmt;
+ size_t name_sz = 0;
+
+ get_vma_name(vma, &path, &name, &name_fmt);
+
+ if (path || name_fmt || name) {
+ name_buf = kmalloc(name_buf_sz, GFP_KERNEL);
+ if (!name_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
+ if (path) {
+ name = d_path(path, name_buf, name_buf_sz);
+ if (IS_ERR(name)) {
+ err = PTR_ERR(name);
+ goto out;
+ }
+ name_sz = name_buf + name_buf_sz - name;
+ } else if (name || name_fmt) {
+ name_sz = 1 + snprintf(name_buf, name_buf_sz, name_fmt ?: "%s", name);
+ name = name_buf;
+ }
+ if (name_sz > name_buf_sz) {
+ err = -ENAMETOOLONG;
+ goto out;
+ }
+ karg.vma_name_size = name_sz;
+ }
+
+ /* unlock vma or mmap_lock, and put mm_struct before copying data to user */
+ query_vma_teardown(mm, vma);
+ mmput(mm);
+
+ if (karg.vma_name_size && copy_to_user((void __user *)karg.vma_name_addr,
+ name, karg.vma_name_size)) {
+ kfree(name_buf);
+ return -EFAULT;
+ }
+ kfree(name_buf);
+
+ if (copy_to_user(uarg, &karg, min_t(size_t, sizeof(karg), usize)))
+ return -EFAULT;
+
+ return 0;
+
+out:
+ query_vma_teardown(mm, vma);
+ mmput(mm);
+ kfree(name_buf);
+ return err;
+}
+
+static long procfs_procmap_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct seq_file *seq = file->private_data;
+ struct proc_maps_private *priv = seq->private;
+
+ switch (cmd) {
+ case PROCMAP_QUERY:
+ return do_procmap_query(priv, (void __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
const struct file_operations proc_pid_maps_operations = {
.open = pid_maps_open,
.read = seq_read,
.llseek = seq_lseek,
.release = proc_map_release,
+ .unlocked_ioctl = procfs_procmap_ioctl,
+ .compat_ioctl = procfs_procmap_ioctl,
};
/*
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 45e4e64fd664..74480ed4fa78 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -333,8 +333,10 @@ typedef int __bitwise __kernel_rwf_t;
#define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\
RWF_APPEND | RWF_NOAPPEND)
+#define PROCFS_IOCTL_MAGIC 'f'
+
/* Pagemap ioctl */
-#define PAGEMAP_SCAN _IOWR('f', 16, struct pm_scan_arg)
+#define PAGEMAP_SCAN _IOWR(PROCFS_IOCTL_MAGIC, 16, struct pm_scan_arg)
/* Bitmasks provided in pm_scan_args masks and reported in page_region.categories. */
#define PAGE_IS_WPALLOWED (1 << 0)
@@ -393,4 +395,130 @@ struct pm_scan_arg {
__u64 return_mask;
};
+/* /proc/<pid>/maps ioctl */
+#define PROCMAP_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 17, struct procmap_query)
+
+enum procmap_query_flags {
+ /*
+ * VMA permission flags.
+ *
+ * Can be used as part of procmap_query.query_flags field to look up
+ * only VMAs satisfying specified subset of permissions. E.g., specifying
+ * PROCMAP_QUERY_VMA_READABLE only will return both readable and read/write VMAs,
+ * while having PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_WRITABLE will only
+ * return read/write VMAs, though both executable/non-executable and
+ * private/shared will be ignored.
+ *
+ * PROCMAP_QUERY_VMA_* flags are also returned in procmap_query.vma_flags
+ * field to specify actual VMA permissions.
+ */
+ PROCMAP_QUERY_VMA_READABLE = 0x01,
+ PROCMAP_QUERY_VMA_WRITABLE = 0x02,
+ PROCMAP_QUERY_VMA_EXECUTABLE = 0x04,
+ PROCMAP_QUERY_VMA_SHARED = 0x08,
+ /*
+ * Query modifier flags.
+ *
+ * By default VMA that covers provided address is returned, or -ENOENT
+ * is returned. With PROCMAP_QUERY_COVERING_OR_NEXT_VMA flag set, closest
+ * VMA with vma_start > addr will be returned if no covering VMA is
+ * found.
+ *
+ * PROCMAP_QUERY_FILE_BACKED_VMA instructs query to consider only VMAs that
+ * have file backing. Can be combined with PROCMAP_QUERY_COVERING_OR_NEXT_VMA
+ * to iterate all VMAs with file backing.
+ */
+ PROCMAP_QUERY_COVERING_OR_NEXT_VMA = 0x10,
+ PROCMAP_QUERY_FILE_BACKED_VMA = 0x20,
+};
+
+/*
+ * Input/output argument structured passed into ioctl() call. It can be used
+ * to query a set of VMAs (Virtual Memory Areas) of a process.
+ *
+ * Each field can be one of three kinds, marked in a short comment to the
+ * right of the field:
+ * - "in", input argument, user has to provide this value, kernel doesn't modify it;
+ * - "out", output argument, kernel sets this field with VMA data;
+ * - "in/out", input and output argument; user provides initial value (used
+ * to specify maximum allowable buffer size), and kernel sets it to actual
+ * amount of data written (or zero, if there is no data).
+ *
+ * If matching VMA is found (according to criterias specified by
+ * query_addr/query_flags, all the out fields are filled out, and ioctl()
+ * returns 0. If there is no matching VMA, -ENOENT will be returned.
+ * In case of any other error, negative error code other than -ENOENT is
+ * returned.
+ *
+ * Most of the data is similar to the one returned as text in /proc/<pid>/maps
+ * file, but procmap_query provides more querying flexibility. There are no
+ * consistency guarantees between subsequent ioctl() calls, but data returned
+ * for matched VMA is self-consistent.
+ */
+struct procmap_query {
+ /* Query struct size, for backwards/forward compatibility */
+ __u64 size;
+ /*
+ * Query flags, a combination of enum procmap_query_flags values.
+ * Defines query filtering and behavior, see enum procmap_query_flags.
+ *
+ * Input argument, provided by user. Kernel doesn't modify it.
+ */
+ __u64 query_flags; /* in */
+ /*
+ * Query address. By default, VMA that covers this address will
+ * be looked up. PROCMAP_QUERY_* flags above modify this default
+ * behavior further.
+ *
+ * Input argument, provided by user. Kernel doesn't modify it.
+ */
+ __u64 query_addr; /* in */
+ /* VMA starting (inclusive) and ending (exclusive) address, if VMA is found. */
+ __u64 vma_start; /* out */
+ __u64 vma_end; /* out */
+ /* VMA permissions flags. A combination of PROCMAP_QUERY_VMA_* flags. */
+ __u64 vma_flags; /* out */
+ /* VMA backing page size granularity. */
+ __u32 vma_page_size; /* out */
+ /*
+ * VMA file offset. If VMA has file backing, this specifies offset
+ * within the file that VMA's start address corresponds to.
+ * Is set to zero if VMA has no backing file.
+ */
+ __u64 vma_offset; /* out */
+ /* Backing file's inode number, or zero, if VMA has no backing file. */
+ __u64 inode; /* out */
+ /* Backing file's device major/minor number, or zero, if VMA has no backing file. */
+ __u32 dev_major; /* out */
+ __u32 dev_minor; /* out */
+ /*
+ * If set to non-zero value, signals the request to return VMA name
+ * (i.e., VMA's backing file's absolute path, with " (deleted)" suffix
+ * appended, if file was unlinked from FS) for matched VMA. VMA name
+ * can also be some special name (e.g., "[heap]", "[stack]") or could
+ * be even user-supplied with prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME).
+ *
+ * Kernel will set this field to zero, if VMA has no associated name.
+ * Otherwise kernel will return actual amount of bytes filled in
+ * user-supplied buffer (see vma_name_addr field below), including the
+ * terminating zero.
+ *
+ * If VMA name is longer that user-supplied maximum buffer size,
+ * -E2BIG error is returned.
+ *
+ * If this field is set to non-zero value, vma_name_addr should point
+ * to valid user space memory buffer of at least vma_name_size bytes.
+ * If set to zero, vma_name_addr should be set to zero as well
+ */
+ __u32 vma_name_size; /* in/out */
+ /*
+ * User-supplied address of a buffer of at least vma_name_size bytes
+ * for kernel to fill with matched VMA's name (see vma_name_size field
+ * description above for details).
+ *
+ * Should be set to zero if VMA name should not be returned.
+ */
+ __u64 vma_name_addr; /* in */
+};
+
#endif /* _UAPI_LINUX_FS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 1/6] fs/procfs: extract logic for getting VMA name constituents Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
2024-06-19 10:14 ` Alexey Dobriyan
2024-06-18 22:45 ` [PATCH v5 4/6] docs/procfs: call out ioctl()-based PROCMAP_QUERY command existence Andrii Nakryiko
` (2 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
The need to get ELF build ID reliably is an important aspect when
dealing with profiling and stack trace symbolization, and
/proc/<pid>/maps textual representation doesn't help with this.
To get backing file's ELF build ID, application has to first resolve
VMA, then use it's start/end address range to follow a special
/proc/<pid>/map_files/<start>-<end> symlink to open the ELF file (this
is necessary because backing file might have been removed from the disk
or was already replaced with another binary in the same file path.
Such approach, beyond just adding complexity of having to do a bunch of
extra work, has extra security implications. Because application opens
underlying ELF file and needs read access to its entire contents (as far
as kernel is concerned), kernel puts additional capable() checks on
following /proc/<pid>/map_files/<start>-<end> symlink. And that makes
sense in general.
But in the case of build ID, profiler/symbolizer doesn't need the
contents of ELF file, per se. It's only build ID that is of interest,
and ELF build ID itself doesn't provide any sensitive information.
So this patch adds a way to request backing file's ELF build ID along
the rest of VMA information in the same API. User has control over
whether this piece of information is requested or not by either setting
build_id_size field to zero or non-zero maximum buffer size they
provided through build_id_addr field (which encodes user pointer as
__u64 field). This is a completely optional piece of information, and so
has no performance implications for user cases that don't care about
build ID, while improving performance and simplifying the setup for
those application that do need it.
Kernel already implements build ID fetching, which is used from BPF
subsystem. We are reusing this code here, but plan a follow up changes
to make it work better under more relaxed assumption (compared to what
existing code assumes) of being called from user process context, in
which page faults are allowed. BPF-specific implementation currently
bails out if necessary part of ELF file is not paged in, all due to
extra BPF-specific restrictions (like the need to fetch build ID in
restrictive contexts such as NMI handler).
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
fs/proc/task_mmu.c | 25 ++++++++++++++++++++++++-
include/uapi/linux/fs.h | 28 ++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c
index 674405b99d0d..32bef3eeab7f 100644
--- a/fs/proc/task_mmu.c
+++ b/fs/proc/task_mmu.c
@@ -22,6 +22,7 @@
#include <linux/pkeys.h>
#include <linux/minmax.h>
#include <linux/overflow.h>
+#include <linux/buildid.h>
#include <asm/elf.h>
#include <asm/tlb.h>
@@ -445,6 +446,7 @@ static struct vm_area_struct *query_matching_vma(struct mm_struct *mm,
addr = vma->vm_end;
if (flags & PROCMAP_QUERY_COVERING_OR_NEXT_VMA)
goto next_vma;
+
no_vma:
return ERR_PTR(-ENOENT);
}
@@ -455,7 +457,7 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
struct vm_area_struct *vma;
struct mm_struct *mm;
const char *name = NULL;
- char *name_buf = NULL;
+ char build_id_buf[BUILD_ID_SIZE_MAX], *name_buf = NULL;
__u64 usize;
int err;
@@ -477,6 +479,8 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
/* either both buffer address and size are set, or both should be zero */
if (!!karg.vma_name_size != !!karg.vma_name_addr)
return -EINVAL;
+ if (!!karg.build_id_size != !!karg.build_id_addr)
+ return -EINVAL;
mm = priv->mm;
if (!mm || !mmget_not_zero(mm))
@@ -539,6 +543,21 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
}
}
+ if (karg.build_id_size) {
+ __u32 build_id_sz;
+
+ err = build_id_parse(vma, build_id_buf, &build_id_sz);
+ if (err) {
+ karg.build_id_size = 0;
+ } else {
+ if (karg.build_id_size < build_id_sz) {
+ err = -ENAMETOOLONG;
+ goto out;
+ }
+ karg.build_id_size = build_id_sz;
+ }
+ }
+
if (karg.vma_name_size) {
size_t name_buf_sz = min_t(size_t, PATH_MAX, karg.vma_name_size);
const struct path *path;
@@ -583,6 +602,10 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
}
kfree(name_buf);
+ if (karg.build_id_size && copy_to_user((void __user *)karg.build_id_addr,
+ build_id_buf, karg.build_id_size))
+ return -EFAULT;
+
if (copy_to_user(uarg, &karg, min_t(size_t, sizeof(karg), usize)))
return -EFAULT;
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 74480ed4fa78..cad6375044bc 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -511,6 +511,26 @@ struct procmap_query {
* If set to zero, vma_name_addr should be set to zero as well
*/
__u32 vma_name_size; /* in/out */
+ /*
+ * If set to non-zero value, signals the request to extract and return
+ * VMA's backing file's build ID, if the backing file is an ELF file
+ * and it contains embedded build ID.
+ *
+ * Kernel will set this field to zero, if VMA has no backing file,
+ * backing file is not an ELF file, or ELF file has no build ID
+ * embedded.
+ *
+ * Build ID is a binary value (not a string). Kernel will set
+ * build_id_size field to exact number of bytes used for build ID.
+ * If build ID is requested and present, but needs more bytes than
+ * user-supplied maximum buffer size (see build_id_addr field below),
+ * -E2BIG error will be returned.
+ *
+ * If this field is set to non-zero value, build_id_addr should point
+ * to valid user space memory buffer of at least build_id_size bytes.
+ * If set to zero, build_id_addr should be set to zero as well
+ */
+ __u32 build_id_size; /* in/out */
/*
* User-supplied address of a buffer of at least vma_name_size bytes
* for kernel to fill with matched VMA's name (see vma_name_size field
@@ -519,6 +539,14 @@ struct procmap_query {
* Should be set to zero if VMA name should not be returned.
*/
__u64 vma_name_addr; /* in */
+ /*
+ * User-supplied address of a buffer of at least build_id_size bytes
+ * for kernel to fill with matched VMA's ELF build ID, if available
+ * (see build_id_size field description above for details).
+ *
+ * Should be set to zero if build ID should not be returned.
+ */
+ __u64 build_id_addr; /* in */
};
#endif /* _UAPI_LINUX_FS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 4/6] docs/procfs: call out ioctl()-based PROCMAP_QUERY command existence
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
` (2 preceding siblings ...)
2024-06-18 22:45 ` [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 5/6] tools: sync uapi/linux/fs.h header into tools subdir Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 6/6] selftests/proc: add PROCMAP_QUERY ioctl tests Andrii Nakryiko
5 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
Call out PROCMAP_QUERY ioctl() existence in the section describing
/proc/PID/maps file in documentation. We refer user to UAPI header for
low-level details of this programmatic interface.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
Documentation/filesystems/proc.rst | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index 82d142de3461..e834779d9611 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -443,6 +443,15 @@ is not associated with a file:
or if empty, the mapping is anonymous.
+Starting with 6.11 kernel, /proc/PID/maps provides an alternative
+ioctl()-based API that gives ability to flexibly and efficiently query and
+filter individual VMAs. This interface is binary and is meant for more
+efficient and easy programmatic use. `struct procmap_query`, defined in
+linux/fs.h UAPI header, serves as an input/output argument to the
+`PROCMAP_QUERY` ioctl() command. See comments in linus/fs.h UAPI header for
+details on query semantics, supported flags, data returned, and general API
+usage information.
+
The /proc/PID/smaps is an extension based on maps, showing the memory
consumption for each of the process's mappings. For each mapping (aka Virtual
Memory Area, or VMA) there is a series of lines such as the following::
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 5/6] tools: sync uapi/linux/fs.h header into tools subdir
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
` (3 preceding siblings ...)
2024-06-18 22:45 ` [PATCH v5 4/6] docs/procfs: call out ioctl()-based PROCMAP_QUERY command existence Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 6/6] selftests/proc: add PROCMAP_QUERY ioctl tests Andrii Nakryiko
5 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
We need this UAPI header in tools/include subdirectory for using it from
BPF selftests.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
tools/include/uapi/linux/fs.h | 184 +++++++++++++++++++++++++++++++---
1 file changed, 172 insertions(+), 12 deletions(-)
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index cc3fea99fd43..cad6375044bc 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
-#ifndef _LINUX_FS_H
-#define _LINUX_FS_H
+#ifndef _UAPI_LINUX_FS_H
+#define _UAPI_LINUX_FS_H
/*
* This file has definitions for some important file table structures
@@ -13,10 +13,14 @@
#include <linux/limits.h>
#include <linux/ioctl.h>
#include <linux/types.h>
+#ifndef __KERNEL__
#include <linux/fscrypt.h>
+#endif
/* Use of MS_* flags within the kernel is restricted to core mount(2) code. */
+#if !defined(__KERNEL__)
#include <linux/mount.h>
+#endif
/*
* It's silly to have NR_OPEN bigger than NR_FILE, but you can change
@@ -24,8 +28,8 @@
* nr_file rlimit, so it's safe to set up a ridiculously high absolute
* upper limit on files-per-process.
*
- * Some programs (notably those using select()) may have to be
- * recompiled to take full advantage of the new limits..
+ * Some programs (notably those using select()) may have to be
+ * recompiled to take full advantage of the new limits..
*/
/* Fixed constants first: */
@@ -308,29 +312,31 @@ struct fsxattr {
typedef int __bitwise __kernel_rwf_t;
/* high priority request, poll if possible */
-#define RWF_HIPRI ((__kernel_rwf_t)0x00000001)
+#define RWF_HIPRI ((__force __kernel_rwf_t)0x00000001)
/* per-IO O_DSYNC */
-#define RWF_DSYNC ((__kernel_rwf_t)0x00000002)
+#define RWF_DSYNC ((__force __kernel_rwf_t)0x00000002)
/* per-IO O_SYNC */
-#define RWF_SYNC ((__kernel_rwf_t)0x00000004)
+#define RWF_SYNC ((__force __kernel_rwf_t)0x00000004)
/* per-IO, return -EAGAIN if operation would block */
-#define RWF_NOWAIT ((__kernel_rwf_t)0x00000008)
+#define RWF_NOWAIT ((__force __kernel_rwf_t)0x00000008)
/* per-IO O_APPEND */
-#define RWF_APPEND ((__kernel_rwf_t)0x00000010)
+#define RWF_APPEND ((__force __kernel_rwf_t)0x00000010)
/* per-IO negation of O_APPEND */
-#define RWF_NOAPPEND ((__kernel_rwf_t)0x00000020)
+#define RWF_NOAPPEND ((__force __kernel_rwf_t)0x00000020)
/* mask of flags supported by the kernel */
#define RWF_SUPPORTED (RWF_HIPRI | RWF_DSYNC | RWF_SYNC | RWF_NOWAIT |\
RWF_APPEND | RWF_NOAPPEND)
+#define PROCFS_IOCTL_MAGIC 'f'
+
/* Pagemap ioctl */
-#define PAGEMAP_SCAN _IOWR('f', 16, struct pm_scan_arg)
+#define PAGEMAP_SCAN _IOWR(PROCFS_IOCTL_MAGIC, 16, struct pm_scan_arg)
/* Bitmasks provided in pm_scan_args masks and reported in page_region.categories. */
#define PAGE_IS_WPALLOWED (1 << 0)
@@ -389,4 +395,158 @@ struct pm_scan_arg {
__u64 return_mask;
};
-#endif /* _LINUX_FS_H */
+/* /proc/<pid>/maps ioctl */
+#define PROCMAP_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 17, struct procmap_query)
+
+enum procmap_query_flags {
+ /*
+ * VMA permission flags.
+ *
+ * Can be used as part of procmap_query.query_flags field to look up
+ * only VMAs satisfying specified subset of permissions. E.g., specifying
+ * PROCMAP_QUERY_VMA_READABLE only will return both readable and read/write VMAs,
+ * while having PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_WRITABLE will only
+ * return read/write VMAs, though both executable/non-executable and
+ * private/shared will be ignored.
+ *
+ * PROCMAP_QUERY_VMA_* flags are also returned in procmap_query.vma_flags
+ * field to specify actual VMA permissions.
+ */
+ PROCMAP_QUERY_VMA_READABLE = 0x01,
+ PROCMAP_QUERY_VMA_WRITABLE = 0x02,
+ PROCMAP_QUERY_VMA_EXECUTABLE = 0x04,
+ PROCMAP_QUERY_VMA_SHARED = 0x08,
+ /*
+ * Query modifier flags.
+ *
+ * By default VMA that covers provided address is returned, or -ENOENT
+ * is returned. With PROCMAP_QUERY_COVERING_OR_NEXT_VMA flag set, closest
+ * VMA with vma_start > addr will be returned if no covering VMA is
+ * found.
+ *
+ * PROCMAP_QUERY_FILE_BACKED_VMA instructs query to consider only VMAs that
+ * have file backing. Can be combined with PROCMAP_QUERY_COVERING_OR_NEXT_VMA
+ * to iterate all VMAs with file backing.
+ */
+ PROCMAP_QUERY_COVERING_OR_NEXT_VMA = 0x10,
+ PROCMAP_QUERY_FILE_BACKED_VMA = 0x20,
+};
+
+/*
+ * Input/output argument structured passed into ioctl() call. It can be used
+ * to query a set of VMAs (Virtual Memory Areas) of a process.
+ *
+ * Each field can be one of three kinds, marked in a short comment to the
+ * right of the field:
+ * - "in", input argument, user has to provide this value, kernel doesn't modify it;
+ * - "out", output argument, kernel sets this field with VMA data;
+ * - "in/out", input and output argument; user provides initial value (used
+ * to specify maximum allowable buffer size), and kernel sets it to actual
+ * amount of data written (or zero, if there is no data).
+ *
+ * If matching VMA is found (according to criterias specified by
+ * query_addr/query_flags, all the out fields are filled out, and ioctl()
+ * returns 0. If there is no matching VMA, -ENOENT will be returned.
+ * In case of any other error, negative error code other than -ENOENT is
+ * returned.
+ *
+ * Most of the data is similar to the one returned as text in /proc/<pid>/maps
+ * file, but procmap_query provides more querying flexibility. There are no
+ * consistency guarantees between subsequent ioctl() calls, but data returned
+ * for matched VMA is self-consistent.
+ */
+struct procmap_query {
+ /* Query struct size, for backwards/forward compatibility */
+ __u64 size;
+ /*
+ * Query flags, a combination of enum procmap_query_flags values.
+ * Defines query filtering and behavior, see enum procmap_query_flags.
+ *
+ * Input argument, provided by user. Kernel doesn't modify it.
+ */
+ __u64 query_flags; /* in */
+ /*
+ * Query address. By default, VMA that covers this address will
+ * be looked up. PROCMAP_QUERY_* flags above modify this default
+ * behavior further.
+ *
+ * Input argument, provided by user. Kernel doesn't modify it.
+ */
+ __u64 query_addr; /* in */
+ /* VMA starting (inclusive) and ending (exclusive) address, if VMA is found. */
+ __u64 vma_start; /* out */
+ __u64 vma_end; /* out */
+ /* VMA permissions flags. A combination of PROCMAP_QUERY_VMA_* flags. */
+ __u64 vma_flags; /* out */
+ /* VMA backing page size granularity. */
+ __u32 vma_page_size; /* out */
+ /*
+ * VMA file offset. If VMA has file backing, this specifies offset
+ * within the file that VMA's start address corresponds to.
+ * Is set to zero if VMA has no backing file.
+ */
+ __u64 vma_offset; /* out */
+ /* Backing file's inode number, or zero, if VMA has no backing file. */
+ __u64 inode; /* out */
+ /* Backing file's device major/minor number, or zero, if VMA has no backing file. */
+ __u32 dev_major; /* out */
+ __u32 dev_minor; /* out */
+ /*
+ * If set to non-zero value, signals the request to return VMA name
+ * (i.e., VMA's backing file's absolute path, with " (deleted)" suffix
+ * appended, if file was unlinked from FS) for matched VMA. VMA name
+ * can also be some special name (e.g., "[heap]", "[stack]") or could
+ * be even user-supplied with prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME).
+ *
+ * Kernel will set this field to zero, if VMA has no associated name.
+ * Otherwise kernel will return actual amount of bytes filled in
+ * user-supplied buffer (see vma_name_addr field below), including the
+ * terminating zero.
+ *
+ * If VMA name is longer that user-supplied maximum buffer size,
+ * -E2BIG error is returned.
+ *
+ * If this field is set to non-zero value, vma_name_addr should point
+ * to valid user space memory buffer of at least vma_name_size bytes.
+ * If set to zero, vma_name_addr should be set to zero as well
+ */
+ __u32 vma_name_size; /* in/out */
+ /*
+ * If set to non-zero value, signals the request to extract and return
+ * VMA's backing file's build ID, if the backing file is an ELF file
+ * and it contains embedded build ID.
+ *
+ * Kernel will set this field to zero, if VMA has no backing file,
+ * backing file is not an ELF file, or ELF file has no build ID
+ * embedded.
+ *
+ * Build ID is a binary value (not a string). Kernel will set
+ * build_id_size field to exact number of bytes used for build ID.
+ * If build ID is requested and present, but needs more bytes than
+ * user-supplied maximum buffer size (see build_id_addr field below),
+ * -E2BIG error will be returned.
+ *
+ * If this field is set to non-zero value, build_id_addr should point
+ * to valid user space memory buffer of at least build_id_size bytes.
+ * If set to zero, build_id_addr should be set to zero as well
+ */
+ __u32 build_id_size; /* in/out */
+ /*
+ * User-supplied address of a buffer of at least vma_name_size bytes
+ * for kernel to fill with matched VMA's name (see vma_name_size field
+ * description above for details).
+ *
+ * Should be set to zero if VMA name should not be returned.
+ */
+ __u64 vma_name_addr; /* in */
+ /*
+ * User-supplied address of a buffer of at least build_id_size bytes
+ * for kernel to fill with matched VMA's ELF build ID, if available
+ * (see build_id_size field description above for details).
+ *
+ * Should be set to zero if build ID should not be returned.
+ */
+ __u64 build_id_addr; /* in */
+};
+
+#endif /* _UAPI_LINUX_FS_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 6/6] selftests/proc: add PROCMAP_QUERY ioctl tests
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
` (4 preceding siblings ...)
2024-06-18 22:45 ` [PATCH v5 5/6] tools: sync uapi/linux/fs.h header into tools subdir Andrii Nakryiko
@ 2024-06-18 22:45 ` Andrii Nakryiko
5 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-18 22:45 UTC (permalink / raw)
To: linux-fsdevel, brauner, viro, akpm
Cc: linux-kernel, bpf, gregkh, linux-mm, liam.howlett, surenb, rppt,
adobriyan, Andrii Nakryiko
Extend existing proc-pid-vm.c tests with PROCMAP_QUERY ioctl() API.
Test a few successful and negative cases, validating querying filtering
and exact vs next VMA logic works as expected.
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
tools/testing/selftests/proc/Makefile | 1 +
tools/testing/selftests/proc/proc-pid-vm.c | 86 ++++++++++++++++++++++
2 files changed, 87 insertions(+)
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index cd95369254c0..291e7087f1b3 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -Wall -O2 -Wno-unused-function
CFLAGS += -D_GNU_SOURCE
+CFLAGS += $(TOOLS_INCLUDES)
LDFLAGS += -pthread
TEST_GEN_PROGS :=
diff --git a/tools/testing/selftests/proc/proc-pid-vm.c b/tools/testing/selftests/proc/proc-pid-vm.c
index cacbd2a4aec9..d04685771952 100644
--- a/tools/testing/selftests/proc/proc-pid-vm.c
+++ b/tools/testing/selftests/proc/proc-pid-vm.c
@@ -45,6 +45,7 @@
#include <linux/kdev_t.h>
#include <sys/time.h>
#include <sys/resource.h>
+#include <linux/fs.h>
#include "../kselftest.h"
@@ -492,6 +493,91 @@ int main(void)
assert(buf[13] == '\n');
}
+ /* Test PROCMAP_QUERY ioctl() for /proc/$PID/maps */
+ {
+ char path_buf[256], exp_path_buf[256];
+ struct procmap_query q;
+ int fd, err;
+
+ snprintf(path_buf, sizeof(path_buf), "/proc/%u/maps", pid);
+ fd = open(path_buf, O_RDONLY);
+ if (fd == -1)
+ return 1;
+
+ /* CASE 1: exact MATCH at VADDR */
+ memset(&q, 0, sizeof(q));
+ q.size = sizeof(q);
+ q.query_addr = VADDR;
+ q.query_flags = 0;
+ q.vma_name_addr = (__u64)(unsigned long)path_buf;
+ q.vma_name_size = sizeof(path_buf);
+
+ err = ioctl(fd, PROCMAP_QUERY, &q);
+ assert(err == 0);
+
+ assert(q.query_addr == VADDR);
+ assert(q.query_flags == 0);
+
+ assert(q.vma_flags == (PROCMAP_QUERY_VMA_READABLE | PROCMAP_QUERY_VMA_EXECUTABLE));
+ assert(q.vma_start == VADDR);
+ assert(q.vma_end == VADDR + PAGE_SIZE);
+ assert(q.vma_page_size == PAGE_SIZE);
+
+ assert(q.vma_offset == 0);
+ assert(q.inode == st.st_ino);
+ assert(q.dev_major == MAJOR(st.st_dev));
+ assert(q.dev_minor == MINOR(st.st_dev));
+
+ snprintf(exp_path_buf, sizeof(exp_path_buf),
+ "/tmp/#%llu (deleted)", (unsigned long long)st.st_ino);
+ assert(q.vma_name_size == strlen(exp_path_buf) + 1);
+ assert(strcmp(path_buf, exp_path_buf) == 0);
+
+ /* CASE 2: NO MATCH at VADDR-1 */
+ memset(&q, 0, sizeof(q));
+ q.size = sizeof(q);
+ q.query_addr = VADDR - 1;
+ q.query_flags = 0; /* exact match */
+
+ err = ioctl(fd, PROCMAP_QUERY, &q);
+ err = err < 0 ? -errno : 0;
+ assert(err == -ENOENT);
+
+ /* CASE 3: MATCH COVERING_OR_NEXT_VMA at VADDR - 1 */
+ memset(&q, 0, sizeof(q));
+ q.size = sizeof(q);
+ q.query_addr = VADDR - 1;
+ q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
+
+ err = ioctl(fd, PROCMAP_QUERY, &q);
+ assert(err == 0);
+
+ assert(q.query_addr == VADDR - 1);
+ assert(q.query_flags == PROCMAP_QUERY_COVERING_OR_NEXT_VMA);
+ assert(q.vma_start == VADDR);
+ assert(q.vma_end == VADDR + PAGE_SIZE);
+
+ /* CASE 4: NO MATCH at VADDR + PAGE_SIZE */
+ memset(&q, 0, sizeof(q));
+ q.size = sizeof(q);
+ q.query_addr = VADDR + PAGE_SIZE; /* point right after the VMA */
+ q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
+
+ err = ioctl(fd, PROCMAP_QUERY, &q);
+ err = err < 0 ? -errno : 0;
+ assert(err == -ENOENT);
+
+ /* CASE 5: NO MATCH WRITABLE at VADDR */
+ memset(&q, 0, sizeof(q));
+ q.size = sizeof(q);
+ q.query_addr = VADDR;
+ q.query_flags = PROCMAP_QUERY_VMA_WRITABLE;
+
+ err = ioctl(fd, PROCMAP_QUERY, &q);
+ err = err < 0 ? -errno : 0;
+ assert(err == -ENOENT);
+ }
+
return 0;
}
#else
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API
2024-06-18 22:45 ` [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API Andrii Nakryiko
@ 2024-06-19 10:14 ` Alexey Dobriyan
2024-06-20 18:50 ` Andrii Nakryiko
0 siblings, 1 reply; 11+ messages in thread
From: Alexey Dobriyan @ 2024-06-19 10:14 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: linux-fsdevel, brauner, viro, akpm, linux-kernel, bpf, gregkh,
linux-mm, liam.howlett, surenb, rppt
On Tue, Jun 18, 2024 at 03:45:22PM -0700, Andrii Nakryiko wrote:
> The need to get ELF build ID reliably is an important aspect when
> dealing with profiling and stack trace symbolization, and
> /proc/<pid>/maps textual representation doesn't help with this.
> @@ -539,6 +543,21 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
> }
> }
>
> + if (karg.build_id_size) {
> + __u32 build_id_sz;
> +
> + err = build_id_parse(vma, build_id_buf, &build_id_sz);
This is not your bug but build_id_parse() assumes program headers
immediately follow ELF header which is not guaranteed.
> + * If this field is set to non-zero value, build_id_addr should point
> + * to valid user space memory buffer of at least build_id_size bytes.
> + * If set to zero, build_id_addr should be set to zero as well
> + */
> + __u32 build_id_size; /* in/out */
> /*
> * User-supplied address of a buffer of at least vma_name_size bytes
> * for kernel to fill with matched VMA's name (see vma_name_size field
> @@ -519,6 +539,14 @@ struct procmap_query {
> * Should be set to zero if VMA name should not be returned.
> */
> __u64 vma_name_addr; /* in */
> + /*
> + * User-supplied address of a buffer of at least build_id_size bytes
> + * for kernel to fill with matched VMA's ELF build ID, if available
> + * (see build_id_size field description above for details).
> + *
> + * Should be set to zero if build ID should not be returned.
> + */
> + __u64 build_id_addr; /* in */
Can this be simplified to 512-bit buffer in ioctl structure?
BUILD_ID_SIZE_MAX is 20 which is sha1.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API
2024-06-19 10:14 ` Alexey Dobriyan
@ 2024-06-20 18:50 ` Andrii Nakryiko
0 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-20 18:50 UTC (permalink / raw)
To: Alexey Dobriyan
Cc: Andrii Nakryiko, linux-fsdevel, brauner, viro, akpm, linux-kernel,
bpf, gregkh, linux-mm, liam.howlett, surenb, rppt
On Wed, Jun 19, 2024 at 3:14 AM Alexey Dobriyan <adobriyan@gmail.com> wrote:
>
> On Tue, Jun 18, 2024 at 03:45:22PM -0700, Andrii Nakryiko wrote:
> > The need to get ELF build ID reliably is an important aspect when
> > dealing with profiling and stack trace symbolization, and
> > /proc/<pid>/maps textual representation doesn't help with this.
>
> > @@ -539,6 +543,21 @@ static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
> > }
> > }
> >
> > + if (karg.build_id_size) {
> > + __u32 build_id_sz;
> > +
> > + err = build_id_parse(vma, build_id_buf, &build_id_sz);
>
> This is not your bug but build_id_parse() assumes program headers
> immediately follow ELF header which is not guaranteed.
Yes, I'm aware, and I think I stated somewhere that I want to
fix/improve that. The thing is, current build_id_parse() was built for
BPF under NMI context assumption, which is why it can't page in memory
and so on (and this "build ID has to be in the first page" was a
surprise to me, but probably just a technical shortcut to make it a
bit easier to implement). Regardless, my plan, once this API is
merged, is to follow up with make build_id_parse() variant that would
work reliably under sleepable context assumptions. Hopefully that's ok
not to bundle all that with these patches?
>
> > + * If this field is set to non-zero value, build_id_addr should point
> > + * to valid user space memory buffer of at least build_id_size bytes.
> > + * If set to zero, build_id_addr should be set to zero as well
> > + */
> > + __u32 build_id_size; /* in/out */
> > /*
> > * User-supplied address of a buffer of at least vma_name_size bytes
> > * for kernel to fill with matched VMA's name (see vma_name_size field
> > @@ -519,6 +539,14 @@ struct procmap_query {
> > * Should be set to zero if VMA name should not be returned.
> > */
> > __u64 vma_name_addr; /* in */
> > + /*
> > + * User-supplied address of a buffer of at least build_id_size bytes
> > + * for kernel to fill with matched VMA's ELF build ID, if available
> > + * (see build_id_size field description above for details).
> > + *
> > + * Should be set to zero if build ID should not be returned.
> > + */
> > + __u64 build_id_addr; /* in */
>
> Can this be simplified to 512-bit buffer in ioctl structure?
> BUILD_ID_SIZE_MAX is 20 which is sha1.
I'd prefer not to because vma_name can't use the same trick, so we'll
have to have this size+buffer address approach anyway. And because of
that I'd like to have all these optional variable-length/string output
arguments handled in a uniform way. In practice, it's really simple to
use this from user-space, the only mildly annoying part is casting
pointer to __u64. But as I said, for vma_name users will do this
anyways, so not much benefit simplifying the build_id part only.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps
2024-06-18 22:45 ` [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps Andrii Nakryiko
@ 2024-06-26 2:42 ` Liam R. Howlett
2024-06-26 16:37 ` Andrii Nakryiko
0 siblings, 1 reply; 11+ messages in thread
From: Liam R. Howlett @ 2024-06-26 2:42 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: linux-fsdevel, brauner, viro, akpm, linux-kernel, bpf, gregkh,
linux-mm, surenb, rppt, adobriyan
* Andrii Nakryiko <andrii@kernel.org> [240618 18:45]:
...
> +
> +static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
> +{
> + struct procmap_query karg;
> + struct vm_area_struct *vma;
> + struct mm_struct *mm;
> + const char *name = NULL;
> + char *name_buf = NULL;
> + __u64 usize;
> + int err;
> +
> + if (copy_from_user(&usize, (void __user *)uarg, sizeof(usize)))
> + return -EFAULT;
> + /* argument struct can never be that large, reject abuse */
> + if (usize > PAGE_SIZE)
> + return -E2BIG;
> + /* argument struct should have at least query_flags and query_addr fields */
> + if (usize < offsetofend(struct procmap_query, query_addr))
> + return -EINVAL;
> + err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
> + if (err)
> + return err;
> +
> + /* reject unknown flags */
> + if (karg.query_flags & ~PROCMAP_QUERY_VALID_FLAGS_MASK)
> + return -EINVAL;
> + /* either both buffer address and size are set, or both should be zero */
> + if (!!karg.vma_name_size != !!karg.vma_name_addr)
> + return -EINVAL;
> +
> + mm = priv->mm;
> + if (!mm || !mmget_not_zero(mm))
> + return -ESRCH;
> +
> + err = query_vma_setup(mm);
> + if (err) {
> + mmput(mm);
> + return err;
> + }
> +
> + vma = query_matching_vma(mm, karg.query_addr, karg.query_flags);
> + if (IS_ERR(vma)) {
> + err = PTR_ERR(vma);
> + vma = NULL;
> + goto out;
> + }
> +
> + karg.vma_start = vma->vm_start;
> + karg.vma_end = vma->vm_end;
> +
> + karg.vma_flags = 0;
> + if (vma->vm_flags & VM_READ)
> + karg.vma_flags |= PROCMAP_QUERY_VMA_READABLE;
> + if (vma->vm_flags & VM_WRITE)
> + karg.vma_flags |= PROCMAP_QUERY_VMA_WRITABLE;
> + if (vma->vm_flags & VM_EXEC)
> + karg.vma_flags |= PROCMAP_QUERY_VMA_EXECUTABLE;
> + if (vma->vm_flags & VM_MAYSHARE)
> + karg.vma_flags |= PROCMAP_QUERY_VMA_SHARED;
> +
> + karg.vma_page_size = vma_kernel_pagesize(vma);
> +
...
> +/*
> + * Input/output argument structured passed into ioctl() call. It can be used
> + * to query a set of VMAs (Virtual Memory Areas) of a process.
> + *
> + * Each field can be one of three kinds, marked in a short comment to the
> + * right of the field:
> + * - "in", input argument, user has to provide this value, kernel doesn't modify it;
> + * - "out", output argument, kernel sets this field with VMA data;
> + * - "in/out", input and output argument; user provides initial value (used
> + * to specify maximum allowable buffer size), and kernel sets it to actual
> + * amount of data written (or zero, if there is no data).
> + *
> + * If matching VMA is found (according to criterias specified by
> + * query_addr/query_flags, all the out fields are filled out, and ioctl()
> + * returns 0. If there is no matching VMA, -ENOENT will be returned.
> + * In case of any other error, negative error code other than -ENOENT is
> + * returned.
> + *
> + * Most of the data is similar to the one returned as text in /proc/<pid>/maps
> + * file, but procmap_query provides more querying flexibility. There are no
> + * consistency guarantees between subsequent ioctl() calls, but data returned
> + * for matched VMA is self-consistent.
> + */
> +struct procmap_query {
> + /* Query struct size, for backwards/forward compatibility */
> + __u64 size;
> + /*
> + * Query flags, a combination of enum procmap_query_flags values.
> + * Defines query filtering and behavior, see enum procmap_query_flags.
> + *
> + * Input argument, provided by user. Kernel doesn't modify it.
> + */
> + __u64 query_flags; /* in */
> + /*
> + * Query address. By default, VMA that covers this address will
> + * be looked up. PROCMAP_QUERY_* flags above modify this default
> + * behavior further.
> + *
> + * Input argument, provided by user. Kernel doesn't modify it.
> + */
> + __u64 query_addr; /* in */
> + /* VMA starting (inclusive) and ending (exclusive) address, if VMA is found. */
> + __u64 vma_start; /* out */
> + __u64 vma_end; /* out */
> + /* VMA permissions flags. A combination of PROCMAP_QUERY_VMA_* flags. */
> + __u64 vma_flags; /* out */
> + /* VMA backing page size granularity. */
> + __u32 vma_page_size; /* out */
The vma_kernel_pagesize() returns an unsigned long. We could
potentially be truncating the returned value (although probably not
today?). This is from the vm_operations_struct pagesize, which also
returns an unsigned long. Could we switch this to __u64?
...
Thanks,
Liam
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps
2024-06-26 2:42 ` Liam R. Howlett
@ 2024-06-26 16:37 ` Andrii Nakryiko
0 siblings, 0 replies; 11+ messages in thread
From: Andrii Nakryiko @ 2024-06-26 16:37 UTC (permalink / raw)
To: Liam R. Howlett, Andrii Nakryiko, linux-fsdevel, brauner, viro,
akpm, linux-kernel, bpf, gregkh, linux-mm, surenb, rppt,
adobriyan
On Tue, Jun 25, 2024 at 7:42 PM Liam R. Howlett <Liam.Howlett@oracle.com> wrote:
>
> * Andrii Nakryiko <andrii@kernel.org> [240618 18:45]:
> ...
>
> > +
> > +static int do_procmap_query(struct proc_maps_private *priv, void __user *uarg)
> > +{
> > + struct procmap_query karg;
> > + struct vm_area_struct *vma;
> > + struct mm_struct *mm;
> > + const char *name = NULL;
> > + char *name_buf = NULL;
> > + __u64 usize;
> > + int err;
> > +
> > + if (copy_from_user(&usize, (void __user *)uarg, sizeof(usize)))
> > + return -EFAULT;
> > + /* argument struct can never be that large, reject abuse */
> > + if (usize > PAGE_SIZE)
> > + return -E2BIG;
> > + /* argument struct should have at least query_flags and query_addr fields */
> > + if (usize < offsetofend(struct procmap_query, query_addr))
> > + return -EINVAL;
> > + err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
> > + if (err)
> > + return err;
> > +
> > + /* reject unknown flags */
> > + if (karg.query_flags & ~PROCMAP_QUERY_VALID_FLAGS_MASK)
> > + return -EINVAL;
> > + /* either both buffer address and size are set, or both should be zero */
> > + if (!!karg.vma_name_size != !!karg.vma_name_addr)
> > + return -EINVAL;
> > +
> > + mm = priv->mm;
> > + if (!mm || !mmget_not_zero(mm))
> > + return -ESRCH;
> > +
> > + err = query_vma_setup(mm);
> > + if (err) {
> > + mmput(mm);
> > + return err;
> > + }
> > +
> > + vma = query_matching_vma(mm, karg.query_addr, karg.query_flags);
> > + if (IS_ERR(vma)) {
> > + err = PTR_ERR(vma);
> > + vma = NULL;
> > + goto out;
> > + }
> > +
> > + karg.vma_start = vma->vm_start;
> > + karg.vma_end = vma->vm_end;
> > +
> > + karg.vma_flags = 0;
> > + if (vma->vm_flags & VM_READ)
> > + karg.vma_flags |= PROCMAP_QUERY_VMA_READABLE;
> > + if (vma->vm_flags & VM_WRITE)
> > + karg.vma_flags |= PROCMAP_QUERY_VMA_WRITABLE;
> > + if (vma->vm_flags & VM_EXEC)
> > + karg.vma_flags |= PROCMAP_QUERY_VMA_EXECUTABLE;
> > + if (vma->vm_flags & VM_MAYSHARE)
> > + karg.vma_flags |= PROCMAP_QUERY_VMA_SHARED;
> > +
> > + karg.vma_page_size = vma_kernel_pagesize(vma);
> > +
> ...
>
> > +/*
> > + * Input/output argument structured passed into ioctl() call. It can be used
> > + * to query a set of VMAs (Virtual Memory Areas) of a process.
> > + *
> > + * Each field can be one of three kinds, marked in a short comment to the
> > + * right of the field:
> > + * - "in", input argument, user has to provide this value, kernel doesn't modify it;
> > + * - "out", output argument, kernel sets this field with VMA data;
> > + * - "in/out", input and output argument; user provides initial value (used
> > + * to specify maximum allowable buffer size), and kernel sets it to actual
> > + * amount of data written (or zero, if there is no data).
> > + *
> > + * If matching VMA is found (according to criterias specified by
> > + * query_addr/query_flags, all the out fields are filled out, and ioctl()
> > + * returns 0. If there is no matching VMA, -ENOENT will be returned.
> > + * In case of any other error, negative error code other than -ENOENT is
> > + * returned.
> > + *
> > + * Most of the data is similar to the one returned as text in /proc/<pid>/maps
> > + * file, but procmap_query provides more querying flexibility. There are no
> > + * consistency guarantees between subsequent ioctl() calls, but data returned
> > + * for matched VMA is self-consistent.
> > + */
> > +struct procmap_query {
> > + /* Query struct size, for backwards/forward compatibility */
> > + __u64 size;
> > + /*
> > + * Query flags, a combination of enum procmap_query_flags values.
> > + * Defines query filtering and behavior, see enum procmap_query_flags.
> > + *
> > + * Input argument, provided by user. Kernel doesn't modify it.
> > + */
> > + __u64 query_flags; /* in */
> > + /*
> > + * Query address. By default, VMA that covers this address will
> > + * be looked up. PROCMAP_QUERY_* flags above modify this default
> > + * behavior further.
> > + *
> > + * Input argument, provided by user. Kernel doesn't modify it.
> > + */
> > + __u64 query_addr; /* in */
> > + /* VMA starting (inclusive) and ending (exclusive) address, if VMA is found. */
> > + __u64 vma_start; /* out */
> > + __u64 vma_end; /* out */
> > + /* VMA permissions flags. A combination of PROCMAP_QUERY_VMA_* flags. */
> > + __u64 vma_flags; /* out */
> > + /* VMA backing page size granularity. */
> > + __u32 vma_page_size; /* out */
>
> The vma_kernel_pagesize() returns an unsigned long. We could
> potentially be truncating the returned value (although probably not
> today?). This is from the vm_operations_struct pagesize, which also
> returns an unsigned long. Could we switch this to __u64?
>
Yep, fair point. It seemed excessive to use u64, but I guess nothing
prevents us (in the future) from having humongous pages with >4GB
sizes. I'll update to __u64.
I'll wait for a few more hours just in case I get some other feedback,
and will rebase and post a new revision later today, thanks.
> ...
>
> Thanks,
> Liam
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2024-06-26 16:38 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-18 22:45 [PATCH v5 0/6] ioctl()-based API to query VMAs from /proc/<pid>/maps Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 1/6] fs/procfs: extract logic for getting VMA name constituents Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 2/6] fs/procfs: implement efficient VMA querying API for /proc/<pid>/maps Andrii Nakryiko
2024-06-26 2:42 ` Liam R. Howlett
2024-06-26 16:37 ` Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 3/6] fs/procfs: add build ID fetching to PROCMAP_QUERY API Andrii Nakryiko
2024-06-19 10:14 ` Alexey Dobriyan
2024-06-20 18:50 ` Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 4/6] docs/procfs: call out ioctl()-based PROCMAP_QUERY command existence Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 5/6] tools: sync uapi/linux/fs.h header into tools subdir Andrii Nakryiko
2024-06-18 22:45 ` [PATCH v5 6/6] selftests/proc: add PROCMAP_QUERY ioctl tests Andrii Nakryiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).