From: "Jose A. Perez de Azpillaga" <azpijr@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>,
"Liam R. Howlett" <liam@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>,
David Hildenbrand <david@kernel.org>,
Shuah Khan <shuah@kernel.org>
Cc: Vlastimil Babka <vbabka@kernel.org>, Jann Horn <jannh@google.com>,
Pedro Falcato <pfalcato@suse.de>, Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org,
"Jose A. Perez de Azpillaga" <azpijr@gmail.com>
Subject: [PATCH 3/3] selftests/mm: test mlock() and munlock() range normalisation
Date: Mon, 5 Oct 2026 08:46:31 +0200 [thread overview]
Message-ID: <20261005065244.6935-4-azpijr@gmail.com> (raw)
In-Reply-To: <20261005065244.6935-1-azpijr@gmail.com>
Add mlock2-tests cases for a zero length at aligned and unaligned
addresses, for a length that wraps when rounded to pages, and for an
overflowing start + len, checking the return value and VmLck. The
overflow cases expect -EINVAL with and without CAP_IPC_LOCK, so running
the test unprivileged also covers the errno that used to depend on it.
The wrapping cases assume the test is built for the kernel's word size: a
32-bit binary on a 64-bit kernel cannot pass a range that wraps.
Suggested-by: David Hildenbrand <david@kernel.org>
Signed-off-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
---
tools/testing/selftests/mm/mlock2-tests.c | 191 +++++++++++++++++++++-
1 file changed, 190 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/mlock2-tests.c b/tools/testing/selftests/mm/mlock2-tests.c
index 144b550813a6..3dca349c4738 100644
--- a/tools/testing/selftests/mm/mlock2-tests.c
+++ b/tools/testing/selftests/mm/mlock2-tests.c
@@ -2,6 +2,7 @@
#define _GNU_SOURCE
#include <sys/mman.h>
#include <linux/mman.h>
+#include <limits.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
@@ -91,6 +92,7 @@ static bool is_vmflag_set(unsigned long addr, const char *vmflag)
#define SIZE "Size:"
#define RSS "Rss:"
#define LOCKED "lo"
+#define VMLCK "VmLck:"
static unsigned long get_value_for_name(unsigned long addr, const char *name)
{
@@ -177,6 +179,27 @@ static int unlock_lock_check(char *map, bool mlock_supported)
return 1;
}
+static unsigned long get_vmlck(void)
+{
+ unsigned long vmlck;
+ char line[1024];
+ FILE *file;
+
+ file = fopen("/proc/self/status", "r");
+ if (!file)
+ ksft_exit_fail_msg("fopen: %s\n", strerror(errno));
+
+ while (fgets(line, sizeof(line), file)) {
+ if (sscanf(line, VMLCK "\t%lu kB", &vmlck) == 1) {
+ fclose(file);
+ return vmlck << 10;
+ }
+ }
+
+ fclose(file);
+ ksft_exit_fail_msg("cannot parse VmLck in /proc/self/status\n");
+}
+
static void test_mlock_lock(void)
{
char *map;
@@ -204,6 +227,167 @@ static void test_mlock_lock(void)
munmap(map, 2 * page_size);
}
+static void test_mlock_zero_length(void)
+{
+ unsigned long page_size = getpagesize();
+ unsigned long vmlck_before, vmlck_after;
+ char *map;
+
+ map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
+
+ vmlck_before = get_vmlck();
+ ksft_test_result(!mlock(map, 0),
+ "%s: zero length mlock() at an aligned address\n", __func__);
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: zero length mlock() at an aligned address locked nothing\n",
+ __func__);
+
+ vmlck_before = get_vmlck();
+ ksft_test_result(!mlock(map + 5, 0),
+ "%s: zero length mlock() at an unaligned address\n", __func__);
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: zero length mlock() at an unaligned address locked nothing\n",
+ __func__);
+
+ munmap(map, 2 * page_size);
+}
+
+static void test_munlock_zero_length(void)
+{
+ unsigned long page_size = getpagesize();
+ unsigned long vmlck_before, vmlck_after;
+ char *map;
+
+ map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
+
+ if (mlock(map, 2 * page_size)) {
+ munmap(map, 2 * page_size);
+ ksft_exit_fail_msg("mlock(): %s\n", strerror(errno));
+ }
+
+ vmlck_before = get_vmlck();
+ ksft_test_result(vmlck_before > 0,
+ "%s: the range to release is accounted as locked\n",
+ __func__);
+ ksft_test_result(!munlock(map + 5, 0),
+ "%s: zero length munlock() at an unaligned address\n", __func__);
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: zero length munlock() at an unaligned address released nothing\n",
+ __func__);
+
+ munmap(map, 2 * page_size);
+}
+
+static void test_mlock_length_wrap(void)
+{
+ unsigned long page_size = getpagesize();
+ unsigned long vmlck_before, vmlck_after;
+ char *map;
+ int ret;
+
+ map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
+
+ vmlck_before = get_vmlck();
+ errno = 0;
+ ret = mlock(map, ULONG_MAX);
+ ksft_test_result(ret == -1 && errno == EINVAL,
+ "%s: mlock() with a wrapping length is rejected\n",
+ __func__);
+
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: mlock() with a wrapping length locked nothing\n",
+ __func__);
+
+ vmlck_before = get_vmlck();
+ errno = 0;
+ ret = mlock(map + 5, ULONG_MAX);
+ ksft_test_result(ret == -1 && errno == EINVAL,
+ "%s: unaligned mlock() with a wrapping length is rejected\n",
+ __func__);
+
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: unaligned mlock() with a wrapping length locked nothing\n",
+ __func__);
+
+ munmap(map, 2 * page_size);
+}
+
+static void test_mlock_addr_overflow(void)
+{
+ unsigned long page_size = getpagesize();
+ unsigned long vmlck_before, vmlck_after;
+ size_t len;
+ char *map;
+ int ret;
+
+ map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
+
+ /* addr + len wraps to 0 */
+ len = (size_t)(0UL - (unsigned long)map);
+
+ /* -EINVAL with or without CAP_IPC_LOCK */
+ vmlck_before = get_vmlck();
+ errno = 0;
+ ret = mlock(map, len);
+ ksft_test_result(ret == -1 && errno == EINVAL,
+ "%s: mlock() with an overflowing addr + len is rejected\n",
+ __func__);
+
+ vmlck_after = get_vmlck();
+ ksft_test_result(vmlck_before == vmlck_after,
+ "%s: mlock() with an overflowing addr + len locked nothing\n",
+ __func__);
+
+ munmap(map, 2 * page_size);
+}
+
+static void test_munlock_range_overflow(void)
+{
+ unsigned long page_size = getpagesize();
+ size_t len;
+ char *map;
+
+ map = mmap(NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (map == MAP_FAILED)
+ ksft_exit_fail_msg("mmap error: %s\n", strerror(errno));
+
+ errno = 0;
+ ksft_test_result(munlock(map, ULONG_MAX) == -1 && errno == EINVAL,
+ "%s: munlock() with a wrapping length is rejected\n",
+ __func__);
+
+ errno = 0;
+ ksft_test_result(munlock(map + 5, ULONG_MAX) == -1 && errno == EINVAL,
+ "%s: unaligned munlock() with a wrapping length is rejected\n",
+ __func__);
+
+ len = (size_t)(0UL - (unsigned long)map);
+ errno = 0;
+ ksft_test_result(munlock(map, len) == -1 && errno == EINVAL,
+ "%s: munlock() with an overflowing addr + len is rejected\n",
+ __func__);
+
+ munmap(map, 2 * page_size);
+}
+
static int onfault_check(char *map)
{
*map = 'a';
@@ -506,9 +690,14 @@ int main(int argc, char **argv)
munmap(map, size);
- ksft_set_plan(15);
+ ksft_set_plan(31);
test_mlock_lock();
+ test_mlock_zero_length();
+ test_munlock_zero_length();
+ test_mlock_length_wrap();
+ test_mlock_addr_overflow();
+ test_munlock_range_overflow();
test_mlock_onfault();
test_munlockall0();
test_munlockall1();
--
2.55.0
prev parent reply other threads:[~2026-10-05 6:53 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-05 6:46 [PATCH 0/3] mm/mlock: make zero length requests a no-op and reject wrapping ranges Jose A. Perez de Azpillaga
2026-10-05 6:46 ` [PATCH 1/3] mm/mlock: make a zero length request a no-op whatever the alignment Jose A. Perez de Azpillaga
2026-10-05 6:46 ` [PATCH 2/3] mm/mlock: reject ranges that cannot be represented Jose A. Perez de Azpillaga
2026-10-05 6:46 ` Jose A. Perez de Azpillaga [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261005065244.6935-4-azpijr@gmail.com \
--to=azpijr@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=david@kernel.org \
--cc=jannh@google.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=pfalcato@suse.de \
--cc=rppt@kernel.org \
--cc=shuah@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.