[LTP] [PATCH v2] ioctl10.c: New case test PROCMAP_QUERY ioctl()

Wei Gao wegao@suse.com
Wed Feb 26 13:51:41 CET 2025


Signed-off-by: Wei Gao <wegao@suse.com>
---
 configure.ac                               |   1 +
 include/lapi/ioctl.h                       | 133 ++++++++++++++++
 runtest/syscalls                           |   1 +
 testcases/kernel/syscalls/ioctl/.gitignore |   1 +
 testcases/kernel/syscalls/ioctl/ioctl10.c  | 177 +++++++++++++++++++++
 5 files changed, 313 insertions(+)
 create mode 100644 testcases/kernel/syscalls/ioctl/ioctl10.c

diff --git a/configure.ac b/configure.ac
index 6992d75ca..56380d41e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -179,6 +179,7 @@ AC_CHECK_TYPES([struct fanotify_event_info_fid, struct fanotify_event_info_error
 		struct fanotify_event_info_header, struct fanotify_event_info_pidfd],,,[#include <sys/fanotify.h>])
 AC_CHECK_TYPES([struct file_clone_range],,,[#include <linux/fs.h>])
 AC_CHECK_TYPES([struct file_dedupe_range],,,[#include <linux/fs.h>])
+AC_CHECK_TYPES([struct procmap_query],,,[#include <linux/fs.h>])
 
 AC_CHECK_TYPES([struct file_handle],,,[
 #define _GNU_SOURCE
diff --git a/include/lapi/ioctl.h b/include/lapi/ioctl.h
index f91a9e68c..d0f8bf254 100644
--- a/include/lapi/ioctl.h
+++ b/include/lapi/ioctl.h
@@ -9,6 +9,7 @@
 
 #include "config.h"
 #include <sys/ioctl.h>
+#include <stdint.h>
 
 /* musl not including it in <sys/ioctl.h> */
 #include <sys/ttydefaults.h>
@@ -37,4 +38,136 @@ struct termio
 };
 #endif /* HAVE_STRUCT_TERMIO */
 
+#ifndef HAVE_STRUCT_PROCMAP_QUERY
+#define PROCFS_IOCTL_MAGIC 'f'
+#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,
+};
+
+struct procmap_query {
+        /* Query struct size, for backwards/forward compatibility */
+        uint64_t 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.
+         */
+        uint64_t 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.
+         */
+        uint64_t query_addr;               /* in */
+        /* VMA starting (inclusive) and ending (exclusive) address, if VMA is found. */
+        uint64_t vma_start;                /* out */
+        uint64_t vma_end;                  /* out */
+        /* VMA permissions flags. A combination of PROCMAP_QUERY_VMA_* flags. */
+        uint64_t vma_flags;                /* out */
+        /* VMA backing page size granularity. */
+        uint64_t 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.
+         */
+        uint64_t vma_offset;               /* out */
+        /* Backing file's inode number, or zero, if VMA has no backing file. */
+        uint64_t inode;                    /* out */
+        /* Backing file's device major/minor number, or zero, if VMA has no backing file. */
+        uint32_t dev_major;                /* out */
+        uint32_t 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
+         */
+        uint32_t 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
+         */
+        uint32_t 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.
+         */
+        uint64_t 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.
+         */
+        uint64_t build_id_addr;            /* in */
+};
+#endif /* HAVE_STRUCT_PROCMAP_QUERY */
+
 #endif /* LAPI_IOCTL_H__ */
diff --git a/runtest/syscalls b/runtest/syscalls
index ded035ee8..a13811855 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -583,6 +583,7 @@ ioctl06      ioctl06
 ioctl07      ioctl07
 ioctl08      ioctl08
 ioctl09      ioctl09
+ioctl10      ioctl10
 
 ioctl_loop01 ioctl_loop01
 ioctl_loop02 ioctl_loop02
diff --git a/testcases/kernel/syscalls/ioctl/.gitignore b/testcases/kernel/syscalls/ioctl/.gitignore
index 1f099ff95..9c3f66bf1 100644
--- a/testcases/kernel/syscalls/ioctl/.gitignore
+++ b/testcases/kernel/syscalls/ioctl/.gitignore
@@ -7,6 +7,7 @@
 /ioctl07
 /ioctl08
 /ioctl09
+/ioctl10
 /ioctl_loop01
 /ioctl_loop02
 /ioctl_loop03
diff --git a/testcases/kernel/syscalls/ioctl/ioctl10.c b/testcases/kernel/syscalls/ioctl/ioctl10.c
new file mode 100644
index 000000000..7ab3e4c43
--- /dev/null
+++ b/testcases/kernel/syscalls/ioctl/ioctl10.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2024 Wei Gao <wegao@suse.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * Test PROCMAP_QUERY ioctl() for /proc/$PID/maps.
+ * Test base on kernel selftests proc-pid-vm.c.
+ *
+ * 1. Ioctl with exact match query_addr
+ * 2. Ioctl without match query_addr
+ * 3. Check COVERING_OR_NEXT_VMA query_flags
+ * 4. Check PROCMAP_QUERY_VMA_WRITABLE query_flags
+ * 5. Check vma_name_addr content
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include "tst_test.h"
+#include "tst_safe_stdio.h"
+#include <sys/sysmacros.h>
+#include <linux/fs.h>
+#include "lapi/ioctl.h"
+
+#define PROC_MAP_PATH "/proc/self/maps"
+
+struct map_entry {
+	unsigned long vm_start;
+	unsigned long vm_end;
+	char vm_flags_str[5];
+	unsigned long vm_pgoff;
+	unsigned int vm_major;
+	unsigned int vm_minor;
+	unsigned long vm_inode;
+	char vm_name[256];
+	unsigned int vm_flags;
+};
+
+static unsigned int parse_vm_flags(const char *vm_flags_str)
+{
+	unsigned int flags = 0;
+
+	if (strchr(vm_flags_str, 'r'))
+		flags |= PROCMAP_QUERY_VMA_READABLE;
+	if (strchr(vm_flags_str, 'w'))
+		flags |= PROCMAP_QUERY_VMA_WRITABLE;
+	if (strchr(vm_flags_str, 'x'))
+		flags |= PROCMAP_QUERY_VMA_EXECUTABLE;
+	if (strchr(vm_flags_str, 's'))
+		flags |= PROCMAP_QUERY_VMA_SHARED;
+
+	return flags;
+
+}
+
+static void parse_maps_file(const char *filename, const char *keyword, struct map_entry *entry)
+{
+	FILE *fp = SAFE_FOPEN(filename, "r");
+
+	char line[1024];
+
+	while (fgets(line, sizeof(line), fp) != NULL) {
+		if (fnmatch(keyword, line, 0) == 0) {
+			if (sscanf(line, "%lx-%lx %s %lx %x:%x %lu %s",
+						&entry->vm_start, &entry->vm_end, entry->vm_flags_str,
+						&entry->vm_pgoff, &entry->vm_major, &entry->vm_minor,
+						&entry->vm_inode, entry->vm_name) < 7)
+				tst_brk(TFAIL, "parse maps file /proc/self/maps failed");
+
+			entry->vm_flags = parse_vm_flags(entry->vm_flags_str);
+
+			SAFE_FCLOSE(fp);
+			return;
+		}
+	}
+
+	SAFE_FCLOSE(fp);
+	tst_brk(TFAIL, "parse maps file /proc/self/maps failed");
+}
+
+static void verify_ioctl(void)
+{
+	struct procmap_query q;
+	int fd;
+	struct map_entry entry;
+
+	memset(&entry, 0, sizeof(entry));
+
+	fd = SAFE_OPEN("/proc/self/maps", O_RDONLY);
+
+	parse_maps_file(PROC_MAP_PATH, "*", &entry);
+
+	/* CASE 1: exact MATCH at query_addr */
+	memset(&q, 0, sizeof(q));
+	q.size = sizeof(q);
+	q.query_addr = (uint64_t)entry.vm_start;
+	q.query_flags = 0;
+
+	TEST(ioctl(fd, PROCMAP_QUERY, &q));
+
+	if ((TST_RET == -1) && (TST_ERR == ENOTTY))
+		tst_brk(TCONF,
+			"This system does not provide support for ioctl(PROCMAP_QUERY)");
+
+
+	TST_EXP_PASS(ioctl(fd, PROCMAP_QUERY, &q));
+
+	TST_EXP_EQ_LU(q.query_addr, entry.vm_start);
+	TST_EXP_EQ_LU(q.query_flags, 0);
+	TST_EXP_EQ_LU(q.vma_flags, entry.vm_flags);
+	TST_EXP_EQ_LU(q.vma_start, entry.vm_start);
+	TST_EXP_EQ_LU(q.vma_end, entry.vm_end);
+	TST_EXP_EQ_LU(q.vma_page_size, getpagesize());
+	TST_EXP_EQ_LU(q.vma_offset, entry.vm_pgoff);
+	TST_EXP_EQ_LU(q.inode, entry.vm_inode);
+	TST_EXP_EQ_LU(q.dev_major, entry.vm_major);
+	TST_EXP_EQ_LU(q.dev_minor, entry.vm_minor);
+
+	/* CASE 2: NO MATCH at query_addr */
+	memset(&q, 0, sizeof(q));
+	q.size = sizeof(q);
+	q.query_addr = entry.vm_start - 1;
+	q.query_flags = 0;
+
+	TST_EXP_FAIL(ioctl(fd, PROCMAP_QUERY, &q), ENOENT);
+
+	/* CASE 3: MATCH COVERING_OR_NEXT_VMA */
+	memset(&q, 0, sizeof(q));
+	q.size = sizeof(q);
+	q.query_addr = entry.vm_start - 1;
+	q.query_flags = PROCMAP_QUERY_COVERING_OR_NEXT_VMA;
+
+	TST_EXP_PASS(ioctl(fd, PROCMAP_QUERY, &q));
+
+	/* CASE 4: NO MATCH WRITABLE at query_addr */
+	memset(&entry, 0, sizeof(entry));
+	parse_maps_file(PROC_MAP_PATH, "*r-?p *", &entry);
+
+	memset(&q, 0, sizeof(q));
+	q.size = sizeof(q);
+	q.query_addr = entry.vm_start;
+	q.query_flags = PROCMAP_QUERY_VMA_WRITABLE;
+	TST_EXP_FAIL(ioctl(fd, PROCMAP_QUERY, &q), ENOENT);
+
+	/* CASE 5: check vma_name_addr content */
+	char process_name[256];
+	char pattern[256];
+	char buf[256];
+
+	SAFE_READLINK("/proc/self/exe", process_name, sizeof(process_name));
+	sprintf(pattern, "*%s*", process_name);
+	memset(&entry, 0, sizeof(entry));
+	parse_maps_file(PROC_MAP_PATH, pattern, &entry);
+
+	memset(&q, 0, sizeof(q));
+	q.size = sizeof(q);
+	q.query_addr = entry.vm_start;
+	q.query_flags = 0;
+	q.vma_name_addr = (uint64_t)(unsigned long)buf;
+	q.vma_name_size = sizeof(buf);
+
+	TST_EXP_PASS(ioctl(fd, PROCMAP_QUERY, &q));
+	TST_EXP_EQ_LU(q.vma_name_size, strlen(process_name) + 1);
+	TST_EXP_EQ_STR((char *)q.vma_name_addr, process_name);
+
+	SAFE_CLOSE(fd);
+}
+
+static struct tst_test test = {
+	.test_all = verify_ioctl,
+	.needs_root = 1,
+};
-- 
2.35.3



More information about the ltp mailing list