From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0C130C3ABBC for ; Fri, 9 May 2025 05:35:56 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id B54E26B0092; Fri, 9 May 2025 01:35:53 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id B083C6B0093; Fri, 9 May 2025 01:35:53 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id 9CE166B0095; Fri, 9 May 2025 01:35:53 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0011.hostedemail.com [216.40.44.11]) by kanga.kvack.org (Postfix) with ESMTP id 7F4F96B0092 for ; Fri, 9 May 2025 01:35:53 -0400 (EDT) Received: from smtpin07.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay03.hostedemail.com (Postfix) with ESMTP id B8836BD59C for ; Fri, 9 May 2025 05:35:54 +0000 (UTC) X-FDA: 83422257828.07.D59273D Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by imf16.hostedemail.com (Postfix) with ESMTP id F2B9A180007 for ; Fri, 9 May 2025 05:35:52 +0000 (UTC) Authentication-Results: imf16.hostedemail.com; dkim=none; dmarc=pass (policy=none) header.from=arm.com; spf=pass (imf16.hostedemail.com: domain of dev.jain@arm.com designates 217.140.110.172 as permitted sender) smtp.mailfrom=dev.jain@arm.com ARC-Seal: i=1; s=arc-20220608; d=hostedemail.com; t=1746768953; a=rsa-sha256; cv=none; b=rSSH/3ptjoTzBTVnMGLpLZaPaMrmYDHAcW7PyyaZAdlCOJbeLqBSFA/hGSuimTBhjU7mM9 MnuVl/VihBZYSKN4gmgPJwX2MDqY8KOaj1wsxg8o9NgBCho1cTLen016VIz/A/JFeIOd7Q 1teueEUSUj616mFtS3jwqJ7mCwxYIV4= ARC-Authentication-Results: i=1; imf16.hostedemail.com; dkim=none; dmarc=pass (policy=none) header.from=arm.com; spf=pass (imf16.hostedemail.com: domain of dev.jain@arm.com designates 217.140.110.172 as permitted sender) smtp.mailfrom=dev.jain@arm.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1746768953; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3KVoP2lfSNIu68+by2clVQbvNHObeN4cdanLkLHWDV0=; b=VeUBX/6Hfjdtmu5ehBVZVMWXIyVfBGosGG6lEeciPXU2+YQdT2hPXmnH1FSd0WvKIHRBD9 X8Mlc0slPHt9hVLY7kF9d4E4frCzPjw3CNqlPhp/XmTQdRQe/j4mztK3kTUF6Ru269Q151 bEBSuxw6eGXCLUL3dMDYr4NgR3UtMIw= Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 8A090153B; Thu, 8 May 2025 22:35:41 -0700 (PDT) Received: from [10.162.43.14] (K4MQJ0H1H2.blr.arm.com [10.162.43.14]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 3C5773F58B; Thu, 8 May 2025 22:35:48 -0700 (PDT) Message-ID: Date: Fri, 9 May 2025 11:05:45 +0530 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v1] selftests/mm: add simple VM_PFNMAP tests based on mmap'ing /dev/mem To: David Hildenbrand , linux-kernel@vger.kernel.org Cc: linux-mm@kvack.org, linux-kselftest@vger.kernel.org, Andrew Morton , Shuah Khan , Lorenzo Stoakes , Ingo Molnar , Peter Xu References: <20250508222041.1647645-1-david@redhat.com> Content-Language: en-US From: Dev Jain In-Reply-To: <20250508222041.1647645-1-david@redhat.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Rspamd-Server: rspam10 X-Rspamd-Queue-Id: F2B9A180007 X-Stat-Signature: 3o1oztxu17cswyc7afdaemky9jxx698p X-Rspam-User: X-HE-Tag: 1746768952-147402 X-HE-Meta: U2FsdGVkX19WwSUB1LpwCtNsIkuLEcLCkvb7xl+PpK+zf1Ui9xLTZHmgT91dy8p26FjpGDtzSNHEBJ5cgt3gNx7OwOofc8/L0lQQ8nowLvvqlN/j5DKfPCZTD87L3lHE/LrA20453W0hW55uw/Q7ZZVEGC3KYbtEzhP9CkwGBBzTtzkAoPYYJnTHDaJyXzI0gGfuYne07KH8kaZWdWaSvDIHFfBI2Qi1Xj7UsF/1S2dDdhATP1PqIraLybeigwZ3/kjpLEOM/zI5ZQzV4eWFFjI0F7qezN4eT7MJHgV95ABVrIYikZaoVit735SHTM2yGGaj6f97e5Y6/MEcsOK9qHkuNdr2Yv3jjTenImNeRa8dYBEF5w8PMj2HKZfwqW2mjJ5RDwloeJ4GxU1Y4jPxOUcXcDcD9viDeFu+QECILlnKfqyoL36vrqWu1Xk+O0e4jhsSfEZ/Ixd20+4PC8QD4ZkczScrzmqo7PdN8K0+H8so/QSg78xe/+4jy7XlBNhDmfNp8wi62Sh1tjtFqeWAK7BbQUuftxNu/KTsHRXV0loAuEwcCb0xgBporvZpkm+1EvsnnOmcwDz6kDV/aCockaJiRmtiELLKPvEXKRfGS8FbJRT7NlvfmUqU6p+5D4fOpH2vX234WMlOnNLJlq2gzu22yCcS8l8dVwVmu/v4rQEKlfTikksmHoyPosyAOzOsgN9fOdXKMnWoRuiTD9G4eMasRMBH3GTOin8bZGVoUiwXYuUEL5Iq1BO9Yq8ocEd8LpKqHuyzTfBiGvQZVHZUA6NhtoVA/yftXtwIOE73s1M8kfdL+zGx2yYid3gqm8mI+M+tc78jtifm8GCMzzFnXJT9LmZ9i+mErAg4Yf1BU6IawA2SNINUKirXlytfC2dh+8O0rjnSwFF4bz+j87XdJnlmATCgk0LfeQIgp47Hvg0H21V5du+2bvMyYsqOBGUPckvz2lD8R4cXEuDXGmA yHexib2p yIEwpcvRCf9QD/OgzWifPEt1sJrF2+oxBvsPDlXbB4Ne4O5tcWDm+jfHDBHVLt0tP8wSdltVMjlTIu4D6z0QFPd/1irObuBAIg8yelMSOAYQyutrCiXkpOO2IM1nY3/0S67i4Lyqg3/Rqn0GF6b4r2O9Sb8nAV5O9+OOEaDeLLXcbz53dt6gN0fdry1UZ3u8LWI7V7c9NRGyCXSup/sV2eS3V9gCOcrS0HvkElXnnJftqdnpT7kCcmBSwoumcL/8XLQPcfx4h7eMk/3N3z7kExELdhu8yPViP/v7S/mH1EO6Eht16eNFm/ORJ/tYBO7ORcJzJ8Lv7lyxxhwUSh69IdXNEug== X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: On 09/05/25 3:50 am, David Hildenbrand wrote: > Let's test some basic functionality using /dev/mem. These tests will > implicitly cover some PAT (Page Attribute Handling) handling on x86. > > These tests will only run when /dev/mem access to the first two pages > in physical address space is possible and allowed; otherwise, the tests > are skipped. Some generic comments: 1. I think you should also update .gitignore? 2. You can use ksft_exit_fail_perror() for wherever you want to print strerror(errno). > > On current x86-64 with PAT inside a VM, all tests pass: > > TAP version 13 > 1..19 > ok 1 madvise(MADV_DONTNEED) should be disallowed > ok 2 madvise(MADV_DONTNEED_LOCKED) should be disallowed > ok 3 madvise(MADV_FREE) should be disallowed > ok 4 madvise(MADV_WIPEONFORK) should be disallowed > ok 5 madvise(MADV_COLD) should be disallowed > ok 6 madvise(MADV_PAGEOUT) should be disallowed > ok 7 madvise(MADV_POPULATE_READ) should be disallowed > ok 8 madvise(MADV_POPULATE_WRITE) should be disallowed > ok 9 munmap() splitting > ok 10 mmap() after splitting > ok 11 mremap(MREMAP_FIXED) > ok 12 mremap() shrinking > ok 13 mremap() growing should be disallowed > ok 14 mprotect(PROT_NONE) > ok 15 SIGSEGV expected > ok 16 mprotect(PROT_READ) > ok 17 SIGSEGV not expected > ok 18 fork() > ok 19 SIGSEGV in child not expected > # Totals: pass:19 fail:0 xfail:0 xpass:0 skip:0 error:0 > > However, we are able to trigger: > > [ 27.888251] x86/PAT: pfnmap:1790 freeing invalid memtype [mem 0x00000000-0x00000fff] > > There are probably more things worth testing in the future, such as > MAP_PRIVATE handling. But this set of tests is sufficient to cover most of > the things we will rework regarding PAT handling. > > Cc: Andrew Morton > Cc: Shuah Khan > Cc: Lorenzo Stoakes > Cc: Ingo Molnar > Cc: Peter Xu > Signed-off-by: David Hildenbrand > --- > > On current mm-unstable, the MADV_POPULATE_READ test fails because > mm-unstable contains a patch [1] that must be dropped. > > [1] https://lore.kernel.org/all/20250507154105.763088-2-p.antoniou@partner.samsung.com/ > > --- > tools/testing/selftests/mm/Makefile | 1 + > tools/testing/selftests/mm/pfnmap.c | 278 ++++++++++++++++++++++++++++ > 2 files changed, 279 insertions(+) > create mode 100644 tools/testing/selftests/mm/pfnmap.c > > diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile > index ad4d6043a60f0..ae6f994d3add7 100644 > --- a/tools/testing/selftests/mm/Makefile > +++ b/tools/testing/selftests/mm/Makefile > @@ -84,6 +84,7 @@ TEST_GEN_FILES += mremap_test > TEST_GEN_FILES += mseal_test > TEST_GEN_FILES += on-fault-limit > TEST_GEN_FILES += pagemap_ioctl > +TEST_GEN_FILES += pfnmap > TEST_GEN_FILES += thuge-gen > TEST_GEN_FILES += transhuge-stress > TEST_GEN_FILES += uffd-stress > diff --git a/tools/testing/selftests/mm/pfnmap.c b/tools/testing/selftests/mm/pfnmap.c > new file mode 100644 > index 0000000000000..59be2f3221124 > --- /dev/null > +++ b/tools/testing/selftests/mm/pfnmap.c > @@ -0,0 +1,278 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Basic VM_PFNMAP tests relying on mmap() of '/dev/mem' > + * > + * Copyright 2025, Red Hat, Inc. > + * > + * Author(s): David Hildenbrand > + */ > +#define _GNU_SOURCE > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "../kselftest.h" > +#include "vm_util.h" > + > +static size_t pagesize; > +static int pagemap_fd; > +static int dev_mem_fd; > +static sigjmp_buf env; > + > +static void signal_handler(int sig) > +{ > + if (sig == SIGSEGV) > + siglongjmp(env, 1); > + siglongjmp(env, 2); > +} > + > +static void sense_support(void) > +{ > + char *addr, tmp; > + int ret; > + > + dev_mem_fd = open("/dev/mem", O_RDONLY); > + if (dev_mem_fd < 0) > + ksft_exit_skip("Cannot open '/dev/mem': %s\n", strerror(errno)); > + > + /* We'll require the first two pages throughout our tests ... */ > + addr = mmap(0, pagesize * 2, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_skip("Cannot mmap '/dev/mem'"); > + > + /* ... and want to be able to read from them. */ > + ret = sigsetjmp(env, 1); > + if (!ret) { > + tmp = *addr + *(addr + pagesize); > + asm volatile("" : "+r" (tmp)); > + } > + if (ret) > + ksft_exit_skip("Cannot read-access mmap'ed '/dev/mem'"); > + > + munmap(addr, pagesize * 2); > +} > + > +static void test_madvise(void) > +{ > +#define INIT_ADVICE(nr) { nr, #nr} > + const struct { > + int nr; > + const char *name; > + } advices[] = { > + INIT_ADVICE(MADV_DONTNEED), > + INIT_ADVICE(MADV_DONTNEED_LOCKED), > + INIT_ADVICE(MADV_FREE), > + INIT_ADVICE(MADV_WIPEONFORK), > + INIT_ADVICE(MADV_COLD), > + INIT_ADVICE(MADV_PAGEOUT), > + INIT_ADVICE(MADV_POPULATE_READ), > + INIT_ADVICE(MADV_POPULATE_WRITE), > + }; > + char *addr; > + int ret, i; > + > + addr = mmap(0, pagesize, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* All these advices must be rejected. */ > + for (i = 0; i < ARRAY_SIZE(advices); i++) { > + ret = madvise(addr, pagesize, advices[i].nr); > + ksft_test_result(ret && errno == EINVAL, > + "madvise(%s) should be disallowed\n", > + advices[i].name); > + } > + > + munmap(addr, pagesize); > +} > + > +static void test_munmap_splitting(void) > +{ > + char *addr1, *addr2; > + int ret; > + > + addr1 = mmap(0, pagesize * 2, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr1 == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* Unmap the first pages. */ > + ret = munmap(addr1, pagesize); > + ksft_test_result(!ret, "munmap() splitting\n"); > + > + /* Remap the first page while the second page is still mapped. */ > + addr2 = mmap(0, pagesize, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + ksft_test_result(addr2 != MAP_FAILED, "mmap() after splitting\n"); > + > + if (addr2 != MAP_FAILED) > + munmap(addr2, pagesize); > + if (!ret) > + munmap(addr1 + pagesize, pagesize); > + else > + munmap(addr1, pagesize * 2); > +} > + > +static void test_mremap_fixed(void) > +{ > + char *addr, *new_addr, *ret; > + > + addr = mmap(0, pagesize * 2, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* Reserve a destination area. */ > + new_addr = mmap(0, pagesize * 2, PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0); > + if (new_addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* mremap() over our destination. */ > + ret = mremap(addr, pagesize * 2, pagesize * 2, > + MREMAP_FIXED | MREMAP_MAYMOVE, new_addr); > + ksft_test_result(ret == new_addr, "mremap(MREMAP_FIXED)\n"); > + if (ret != new_addr) > + munmap(new_addr, pagesize * 2); > + munmap(addr, pagesize * 2); > +} > + > +static void test_mremap_shrinking(void) > +{ > + char *addr, *ret; > + > + addr = mmap(0, pagesize * 2, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* Shrinking is expected to work. */ > + ret = mremap(addr, pagesize * 2, pagesize, 0); > + ksft_test_result(ret == addr, "mremap() shrinking\n"); > + if (ret != addr) > + munmap(addr, pagesize * 2); > + else > + munmap(addr, pagesize); > +} > + > +static void test_mremap_growing(void) > +{ > + char *addr, *ret; > + > + addr = mmap(0, pagesize, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* Growing is not expected to work. */ > + ret = mremap(addr, pagesize, pagesize * 2, MREMAP_MAYMOVE); > + ksft_test_result(ret == MAP_FAILED, > + "mremap() growing should be disallowed\n"); > + if (ret == MAP_FAILED) > + munmap(addr, pagesize); > + else > + munmap(ret, pagesize * 2); > +} > + > +static void test_mprotect(void) > +{ > + char *addr, tmp; > + int ret; > + > + addr = mmap(0, pagesize, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* With PROT_NONE, read access must result in SIGSEGV. */ > + ret = mprotect(addr, pagesize, PROT_NONE); > + ksft_test_result(!ret, "mprotect(PROT_NONE)\n"); > + > + ret = sigsetjmp(env, 1); > + if (!ret) { > + tmp = *addr; > + asm volatile("" : "+r" (tmp)); > + } > + ksft_test_result(ret == 1, "SIGSEGV expected\n"); > + > + /* With PROT_READ, read access must again succeed. */ > + ret = mprotect(addr, pagesize, PROT_READ); > + ksft_test_result(!ret, "mprotect(PROT_READ)\n"); > + > + ret = sigsetjmp(env, 1); > + if (!ret) { > + tmp = *addr; > + asm volatile("" : "+r" (tmp)); > + } > + ksft_test_result(!ret, "SIGSEGV not expected\n"); > + > + munmap(addr, pagesize); > +} > + > +static void test_fork(void) > +{ > + char *addr, tmp; > + int ret; > + > + addr = mmap(0, pagesize, PROT_READ, MAP_SHARED, dev_mem_fd, 0); > + if (addr == MAP_FAILED) > + ksft_exit_fail_msg("mmap() failed: %s\n", strerror(errno)); > + > + /* fork() a child and test if the child can access the page. */ > + ret = fork(); > + if (ret < 0) { > + ksft_test_result_fail("fork()\n"); > + goto out; > + } else if (!ret) { > + ret = sigsetjmp(env, 1); > + if (!ret) { > + tmp = *addr; > + asm volatile("" : "+r" (tmp)); > + } > + /* Return the result to the parent. */ > + exit(ret); > + } > + ksft_test_result_pass("fork()\n"); > + > + /* Wait for our child and obtain the result. */ > + wait(&ret); > + if (WIFEXITED(ret)) > + ret = WEXITSTATUS(ret); > + else > + ret = -EINVAL; > + > + ksft_test_result(!ret, "SIGSEGV in child not expected\n"); > +out: > + munmap(addr, pagesize); > +} > + > +int main(int argc, char **argv) > +{ > + int err; > + > + ksft_print_header(); > + ksft_set_plan(19); > + > + pagesize = getpagesize(); > + pagemap_fd = open("/proc/self/pagemap", O_RDONLY); > + if (pagemap_fd < 0) > + ksft_exit_fail_msg("opening pagemap failed\n"); > + if (signal(SIGSEGV, signal_handler) == SIG_ERR) > + ksft_exit_fail_msg("signal() failed: %s\n", strerror(errno)); > + > + sense_support(); > + test_madvise(); > + test_munmap_splitting(); > + test_mremap_fixed(); > + test_mremap_shrinking(); > + test_mremap_growing(); > + test_mprotect(); > + test_fork(); > + > + err = ksft_get_fail_cnt(); > + if (err) > + ksft_exit_fail_msg("%d out of %d tests failed\n", > + err, ksft_test_num()); > + ksft_exit_pass(); > +}