diff mbox series

[v3,7/7] selftest/mm: ksm_functional_tests: Add PROT_NONE test

Message ID 20230803143208.383663-8-david@redhat.com (mailing list archive)
State New
Headers show
Series smaps / mm/gup: fix gup_can_follow_protnone fallout | expand

Commit Message

David Hildenbrand Aug. 3, 2023, 2:32 p.m. UTC
Let's test whether merging and unmerging in PROT_NONE areas works as
expected.

Pass a page protection to mmap_and_merge_range(), which will trigger
an mprotect() after writing to the pages, but before enabling merging.

Make sure that unsharing works as expected, by performing a ptrace write
(using /proc/self/mem) and by setting MADV_UNMERGEABLE.

Note that this implicitly tests that ptrace writes in an inaccessible
(PROT_NONE) mapping work as expected.

Signed-off-by: David Hildenbrand <david@redhat.com>
---
 .../selftests/mm/ksm_functional_tests.c       | 59 ++++++++++++++++---
 1 file changed, 52 insertions(+), 7 deletions(-)

Comments

Peter Xu Aug. 3, 2023, 7:06 p.m. UTC | #1
On Thu, Aug 03, 2023 at 04:32:08PM +0200, David Hildenbrand wrote:
> Let's test whether merging and unmerging in PROT_NONE areas works as
> expected.
> 
> Pass a page protection to mmap_and_merge_range(), which will trigger
> an mprotect() after writing to the pages, but before enabling merging.
> 
> Make sure that unsharing works as expected, by performing a ptrace write
> (using /proc/self/mem) and by setting MADV_UNMERGEABLE.
> 
> Note that this implicitly tests that ptrace writes in an inaccessible
> (PROT_NONE) mapping work as expected.
> 
> Signed-off-by: David Hildenbrand <david@redhat.com>

[...]

> +static void test_prot_none(void)
> +{
> +	const unsigned int size = 2 * MiB;
> +	char *map;
> +	int i;
> +
> +	ksft_print_msg("[RUN] %s\n", __func__);
> +
> +	map = mmap_and_merge_range(0x11, size, PROT_NONE, false);
> +	if (map == MAP_FAILED)
> +		goto unmap;
> +
> +	/* Store a unique value in each page on one half using ptrace */
> +	for (i = 0; i < size / 2; i += pagesize) {
> +		lseek(mem_fd, (uintptr_t) map + i, SEEK_SET);
> +		if (write(mem_fd, &i, sizeof(size)) != sizeof(size)) {

sizeof(i)?  May not matter a huge lot, though..

> +			ksft_test_result_fail("ptrace write failed\n");
> +			goto unmap;
> +		}
> +	}

Acked-by: Peter Xu <peterx@redhat.com>
David Hildenbrand Aug. 4, 2023, 6 p.m. UTC | #2
On 03.08.23 21:06, Peter Xu wrote:
> On Thu, Aug 03, 2023 at 04:32:08PM +0200, David Hildenbrand wrote:
>> Let's test whether merging and unmerging in PROT_NONE areas works as
>> expected.
>>
>> Pass a page protection to mmap_and_merge_range(), which will trigger
>> an mprotect() after writing to the pages, but before enabling merging.
>>
>> Make sure that unsharing works as expected, by performing a ptrace write
>> (using /proc/self/mem) and by setting MADV_UNMERGEABLE.
>>
>> Note that this implicitly tests that ptrace writes in an inaccessible
>> (PROT_NONE) mapping work as expected.
>>
>> Signed-off-by: David Hildenbrand <david@redhat.com>
> 
> [...]
> 
>> +static void test_prot_none(void)
>> +{
>> +	const unsigned int size = 2 * MiB;
>> +	char *map;
>> +	int i;
>> +
>> +	ksft_print_msg("[RUN] %s\n", __func__);
>> +
>> +	map = mmap_and_merge_range(0x11, size, PROT_NONE, false);
>> +	if (map == MAP_FAILED)
>> +		goto unmap;
>> +
>> +	/* Store a unique value in each page on one half using ptrace */
>> +	for (i = 0; i < size / 2; i += pagesize) {
>> +		lseek(mem_fd, (uintptr_t) map + i, SEEK_SET);
>> +		if (write(mem_fd, &i, sizeof(size)) != sizeof(size)) {
> 
> sizeof(i)?  May not matter a huge lot, though..

Oh, indeed, thanks!

> 
>> +			ksft_test_result_fail("ptrace write failed\n");
>> +			goto unmap;
>> +		}
>> +	}
> 
> Acked-by: Peter Xu <peterx@redhat.com>
> 

Thanks!
David Hildenbrand Aug. 7, 2023, 3:36 p.m. UTC | #3
On 03.08.23 16:32, David Hildenbrand wrote:
> Let's test whether merging and unmerging in PROT_NONE areas works as
> expected.
> 
> Pass a page protection to mmap_and_merge_range(), which will trigger
> an mprotect() after writing to the pages, but before enabling merging.
> 
> Make sure that unsharing works as expected, by performing a ptrace write
> (using /proc/self/mem) and by setting MADV_UNMERGEABLE.
> 
> Note that this implicitly tests that ptrace writes in an inaccessible
> (PROT_NONE) mapping work as expected.
> 
> Signed-off-by: David Hildenbrand <david@redhat.com>
> ---

Andrew, can you squash the following?

 From c2be7c02cb96b9189a52a5937821600ef4e259bd Mon Sep 17 00:00:00 2001
From: David Hildenbrand <david@redhat.com>
Date: Mon, 7 Aug 2023 17:33:54 +0200
Subject: [PATCH] Fixup: selftest/mm: ksm_functional_tests: Add PROT_NONE test

As noted by Peter, we should be using sizeof(i) in test_prot_none().

Signed-off-by: David Hildenbrand <david@redhat.com>
---
  tools/testing/selftests/mm/ksm_functional_tests.c | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
index 8fa4889ab4f3..901e950f9138 100644
--- a/tools/testing/selftests/mm/ksm_functional_tests.c
+++ b/tools/testing/selftests/mm/ksm_functional_tests.c
@@ -516,7 +516,7 @@ static void test_prot_none(void)
  	/* Store a unique value in each page on one half using ptrace */
  	for (i = 0; i < size / 2; i += pagesize) {
  		lseek(mem_fd, (uintptr_t) map + i, SEEK_SET);
-		if (write(mem_fd, &i, sizeof(size)) != sizeof(size)) {
+		if (write(mem_fd, &i, sizeof(i)) != sizeof(i)) {
  			ksft_test_result_fail("ptrace write failed\n");
  			goto unmap;
  		}
diff mbox series

Patch

diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
index cb63b600cb4f..8fa4889ab4f3 100644
--- a/tools/testing/selftests/mm/ksm_functional_tests.c
+++ b/tools/testing/selftests/mm/ksm_functional_tests.c
@@ -27,6 +27,7 @@ 
 #define KiB 1024u
 #define MiB (1024 * KiB)
 
+static int mem_fd;
 static int ksm_fd;
 static int ksm_full_scans_fd;
 static int proc_self_ksm_stat_fd;
@@ -144,7 +145,8 @@  static int ksm_unmerge(void)
 	return 0;
 }
 
-static char *mmap_and_merge_range(char val, unsigned long size, bool use_prctl)
+static char *mmap_and_merge_range(char val, unsigned long size, int prot,
+				  bool use_prctl)
 {
 	char *map;
 	int ret;
@@ -176,6 +178,11 @@  static char *mmap_and_merge_range(char val, unsigned long size, bool use_prctl)
 	/* Make sure each page contains the same values to merge them. */
 	memset(map, val, size);
 
+	if (mprotect(map, size, prot)) {
+		ksft_test_result_skip("mprotect() failed\n");
+		goto unmap;
+	}
+
 	if (use_prctl) {
 		ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0);
 		if (ret < 0 && errno == EINVAL) {
@@ -218,7 +225,7 @@  static void test_unmerge(void)
 
 	ksft_print_msg("[RUN] %s\n", __func__);
 
-	map = mmap_and_merge_range(0xcf, size, false);
+	map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false);
 	if (map == MAP_FAILED)
 		return;
 
@@ -256,7 +263,7 @@  static void test_unmerge_zero_pages(void)
 	}
 
 	/* Let KSM deduplicate zero pages. */
-	map = mmap_and_merge_range(0x00, size, false);
+	map = mmap_and_merge_range(0x00, size, PROT_READ | PROT_WRITE, false);
 	if (map == MAP_FAILED)
 		return;
 
@@ -304,7 +311,7 @@  static void test_unmerge_discarded(void)
 
 	ksft_print_msg("[RUN] %s\n", __func__);
 
-	map = mmap_and_merge_range(0xcf, size, false);
+	map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false);
 	if (map == MAP_FAILED)
 		return;
 
@@ -336,7 +343,7 @@  static void test_unmerge_uffd_wp(void)
 
 	ksft_print_msg("[RUN] %s\n", __func__);
 
-	map = mmap_and_merge_range(0xcf, size, false);
+	map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, false);
 	if (map == MAP_FAILED)
 		return;
 
@@ -479,7 +486,7 @@  static void test_prctl_unmerge(void)
 
 	ksft_print_msg("[RUN] %s\n", __func__);
 
-	map = mmap_and_merge_range(0xcf, size, true);
+	map = mmap_and_merge_range(0xcf, size, PROT_READ | PROT_WRITE, true);
 	if (map == MAP_FAILED)
 		return;
 
@@ -494,9 +501,42 @@  static void test_prctl_unmerge(void)
 	munmap(map, size);
 }
 
+static void test_prot_none(void)
+{
+	const unsigned int size = 2 * MiB;
+	char *map;
+	int i;
+
+	ksft_print_msg("[RUN] %s\n", __func__);
+
+	map = mmap_and_merge_range(0x11, size, PROT_NONE, false);
+	if (map == MAP_FAILED)
+		goto unmap;
+
+	/* Store a unique value in each page on one half using ptrace */
+	for (i = 0; i < size / 2; i += pagesize) {
+		lseek(mem_fd, (uintptr_t) map + i, SEEK_SET);
+		if (write(mem_fd, &i, sizeof(size)) != sizeof(size)) {
+			ksft_test_result_fail("ptrace write failed\n");
+			goto unmap;
+		}
+	}
+
+	/* Trigger unsharing on the other half. */
+	if (madvise(map + size / 2, size / 2, MADV_UNMERGEABLE)) {
+		ksft_test_result_fail("MADV_UNMERGEABLE failed\n");
+		goto unmap;
+	}
+
+	ksft_test_result(!range_maps_duplicates(map, size),
+			 "Pages were unmerged\n");
+unmap:
+	munmap(map, size);
+}
+
 int main(int argc, char **argv)
 {
-	unsigned int tests = 6;
+	unsigned int tests = 7;
 	int err;
 
 #ifdef __NR_userfaultfd
@@ -508,6 +548,9 @@  int main(int argc, char **argv)
 
 	pagesize = getpagesize();
 
+	mem_fd = open("/proc/self/mem", O_RDWR);
+	if (mem_fd < 0)
+		ksft_exit_fail_msg("opening /proc/self/mem failed\n");
 	ksm_fd = open("/sys/kernel/mm/ksm/run", O_RDWR);
 	if (ksm_fd < 0)
 		ksft_exit_skip("open(\"/sys/kernel/mm/ksm/run\") failed\n");
@@ -529,6 +572,8 @@  int main(int argc, char **argv)
 	test_unmerge_uffd_wp();
 #endif
 
+	test_prot_none();
+
 	test_prctl();
 	test_prctl_fork();
 	test_prctl_unmerge();