From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (unknown [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id A2A2180726 for ; Wed, 14 Oct 2026 17:16:34 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69EHF7mo001479; Wed, 14 Oct 2026 20:15:08 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69EHF7mo001479 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="BK3Lw8nL" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69EHF5Rf001455 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 14 Oct 2026 20:15:06 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69EHF5Rf001455 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-48dea677afcso684789f8f.1 for ; Wed, 14 Oct 2026 10:15:06 -0700 (PDT) X-Gm-Message-State: AFq9FYJOueY6SMNrnS83trnAY9vJY4wAcLSPQQkS8mHFm63pfnUGeYQW S56vtvzQsqDgsR3U6Kf2/YuvslXx/9b2v5JKlPBTx6IAKCpYfccTVWDHpfwY3/L2a+pZX7omNjz LYXkxWV1B1p3CxV6BXY7vUKNmNFjv4zUZndIVYlYSNUpQ96rIkUtlLQ== X-Gm-Gg: AYBFou1x7mh0WY26DwMztCq7Lnn5c7jQyMFupbYzWg2WxmkXHXfAa9Dlmjng2zqbUa7 N9BSvkMKzVG777PJo+PpjfzTTlJkpbiOLXOw+ULjJ2TG9QOvRQNcG9t/a4Jp55ANstbco1JVXva 7Mxf7T+05yEhsaOIle15kwDeWKCgxrrHIgr6M/7Y2zxIfo/RmkhukA/JffNFZanVWUpFQIuzWuh LT0jFpDcNRywnzDzxhzKb/8s8h2wR+ifV7NJCU+/NPSy6FdnW3g6mVDTgX/eD7dLoh4vfhSUcFl +hMxrGLpBQPFtvH2Z+qeYmRrhwu3JCLukPGH5VWJLYOtVfju+6026uOeS1F5iUlZPO7kPrKqJQ9 3RO7ru8UKULmf1NKu2w== X-Received: by 2002:a05:6000:61c:b0:48c:58b9:f17c with SMTP id ffacd0b85a97d-48db947461fmr32907043f8f.12.1791998105616; Wed, 14 Oct 2026 10:15:05 -0700 (PDT) X-Received: by 2002:a05:6000:61c:b0:48c:58b9:f17c with SMTP id ffacd0b85a97d-48db947461fmr32906961f8f.12.1791998104848; Wed, 14 Oct 2026 10:15:04 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1791998104; cv=none; d=google.com; s=arc-20260327; b=H8BFXQA/o7+ZQAEOFM9G9a4P8rDqd5bvev3MMEBPPT+Oj0shByqWHWA2oWHISZlKDg Yv+75a7EieJUDtOT6X/AwPDKqnOrbFCJEtArTGuxx8Tjf+jyosHM1536uPWVw58a1RL8 TgDcy3E9h7npog0JlS7StUdrWbdf5d/oJHkPFbvb01BYbvbgj/aN0WEe95/KFhmbWetk uEQ72P++MokwNX8FZGrBU+SZtBUF51/SAcP9NLtz0HRQL1Iwl7XE8z5boU5tnoRvMpaO XHw5uc6luerIqCgSd3ed3vUP5Za8D4oVL4Qlei4DeH561DJbRu9rakMOhrybz5xqTC3+ sLvw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:dkim-signature; bh=FBPIPiihGEbM7nDfspZ6mECd7BTKEOczX6CluQBtrqs=; fh=xUcS46XhSaUOWFT7CPWNuABJ4EJ7/+S5u4N+e+rlWN4=; b=qWOh7rcv9rNXOWyH0aNY/niPXuw82iFG/CmYxvbeC5I/9A+JHZdex07cR006FiE3Ej HP/JYHp/vS1RyGBXYcOIoz1g9Uw+Vj4Qaq7TMg+9BStblJd6kv/tUKFC+1qYh9/tFz0G zsZtEZNR3rGL1Pglv2uLKzzgpaYjYPwpbcaPU/P+U8g7r3L7JWiwXRGi1tJ8DHVHFm0C jym4V/Bn2lmr2IYEZnUwSS+aoAg3czh+8GJ1uDotCx4kFA+ERU5PZWw4yE+iWwMjmD8e nwMvmVgLaabHpPH2mgj2ACnd3+q/uOPooU0ssQbMDc4aF2TcpxFFycRVIfZ+idfcuxjR j7XA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=BK3Lw8nL; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-48df0d0d5bfsi2307084f8f.164.2026.10.14.10.15.04 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 10:15:04 -0700 (PDT) Received-SPF: pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=BK3Lw8nL; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=Message-Id:Content-Type:MIME-Version:Subject:Date :From; bh=FBPIPiihGEbM7nDfspZ6mECd7BTKEOczX6CluQBtrqs=; b=BK3Lw8nLJyf4N2mPvlL t7y3nGfbKUKfcxYsDHtEXrmHmS3u3V3GdXF6xgTRxKhznwbLp8VPtkcYDGdnuuYjWk2ZaYVrR5++r enDEQOjfzl1fCEdQsY1ZI5KMEBzqlXslzUDAxl+GwW+Vn05d9pcDh1Qr0J/LhNWjVt6YI2oQEghA6 AxyHvYPkwohqab3WduGPvOffJ9w05PpcRYzeyHVOylYYsgSjjgDZTvdMp3frHxndY82mIymFeq+6t PA1Xu60/PHfuxFS7oYKYnTzcTaYCR7Mde1ZqSE1DstxzfjOtIlajhv36i6+dacV9w0GZqBKOe1rRQ 6EJxt4TRZ78ENVA==; Received: from ch-demo-asa.virtuozzo.com ([130.117.225.8] helo=f0.sw.ru) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xH2Vn-008657-16; Wed, 14 Oct 2026 19:15:03 +0200 From: Konstantin Khorenko Date: Wed, 14 Oct 2026 19:14:59 +0200 MIME-Version: 1.0 Message-Id: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-1-b23b454691db@virtuozzo.com> References: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-0-b23b454691db@virtuozzo.com> In-Reply-To: <20261014-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v1-0-b23b454691db@virtuozzo.com> To: Pavel Tikhomirov , Andrey Drobyshev , Vasileios Almpanis X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=21277; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=VUMLLKUY5LSGCrLSKJ/k9SFjnc+9CqXv7ZsM1XdMFIQ=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBqz7iVMG4mjjNDA+VuCeyNYvpo6A4Lfk+ULZdDR Vo8bd2GCrqJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCas+4lQAKCRBRlgpH+WFc KjkxD/0YWpTayPHtIy+cvVk9L9QyjX9H92c2g4/SsbnLWAvz/0rZn/B2ePECnPgByb7KoL9vv+l R0WGISYNK37rDwXMWjxOBdcHWDuFsgi5XdE77LAFYz6QOL89/nt3SZq6WwSCEn08J+fpdQ+NXSz A8N8/9HLKd/V35+zCo2rdqobVIIutuMhlZZsBW3wYSqRjDi/TMW+oTbEQiE3fo0E5GVX5NG7u2y xs2RWcUPtEcFuNhDsHg8qak1fovcmsoFAQPaDN/rfwu7JWKsXj/uruFLYhHqRuwwHZ99UZhfMun dKyP1+ge+9+YiSKj3FBCmP2sjkJ8dPAhV/3Rc5bpuCFYqBdlFjMvGB33kHZeU7dhyMlCB7I+1nN Iu18wisntbqw37TrsYDuLnOrTLRzPRV7a4DFy4MLrCBexodZEnTRQFfDpWtyP64/gYa/z3Q6cIh Ra8dzmlwy5+V43dFn5AAlFDUWH5TQDjbtrZq4PYoBtGWgqOHa4xrh2e0GNnmtrQlibC32iiIXBo 0OXtqZQe3mET28iFt0YuIqXfUck7JqMZD7FvDHEYPtVPnluoSXVTt8FcWO2A5wnsuDYK5NeXDFt 1swDmA+WR+rhLU5gCsO2+s9rZbPf+ezsQdCicOzs0R4D3lzDDH0BtVBrT/zAqQUVyzFf+K/cgnz BtT5ksbdGDRFz1A== X-Developer-Key: i=khorenko@virtuozzo.com; a=openpgp; fpr=560F6DC83DC5D3D063BD082C51960A47F9615C2A X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 1/7] selftests/mm: cover the MADV_DOEXEC interface and VMA attributes X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org Add cases for what madvise accepts and what the copy of a preserved VMA keeps: - /proc/sys/vm/madv_doexec_flag reports MADV_DOEXEC, and a kernel without the sysctl does not accept MADV_DOEXEC either. - Shared anonymous, private file, executable, MAP_GROWSDOWN and stack mappings are refused with EINVAL, and so is a hugetlb mapping when huge pages are available. - A range unmarked with MADV_DONTEXEC (twice, the second a no-op) is not preserved. - Marking the middle page of a mapping preserves that page only. - An mlocked range is preserved without VM_LOCKED. - MADV_DONTFORK and MADV_WIPEONFORK carry over, so a fork in the new image leaves the range out or wipes it. - A userfaultfd registration does not carry over. - The memory policy of the range carries over. A flag expected to disappear is checked in smaps before the exec as well, so that a failed lookup cannot pass for its absence. EINVAL from MADV_DOEXEC fails a case on a 64-bit kernel with the madv_doexec_flag sysctl, in the existing cases too; the cases skip only when something they need is missing: the feature, free huge pages, the mlock limit, userfaultfd, mbind. A failing case prints what failed, and the children running the cases, old and new, exit with exit(), so that the message reaches a pipe. New cases go to a table, and the checks run in the new image to a second one, so that main() stays small. https://virtuozzo.atlassian.net/browse/VSTOR-150234 Feature: oracle/mm: MADV_DOEXEC madvise() flag Assisted-by: LLM Signed-off-by: Konstantin Khorenko --- tools/testing/selftests/mm/doexec.c | 424 +++++++++++++++++++++++++++++++++--- 1 file changed, 392 insertions(+), 32 deletions(-) diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c index a89fe2f067df5..850b498e3d1dc 100644 --- a/tools/testing/selftests/mm/doexec.c +++ b/tools/testing/selftests/mm/doexec.c @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Verify that exec preserves the memory backing long-term DMA pins. */ +/* Verify what MADV_DOEXEC preserves across exec, long-term DMA pins first. */ #define _GNU_SOURCE #include #include #include #include +#include +#include #include #include #include @@ -25,12 +27,16 @@ #include #include #include +#include #include #include #include "../../../../mm/gup_test.h" #include "../kselftest.h" +#ifndef UFFD_USER_MODE_ONLY +#define UFFD_USER_MODE_ONLY 1 +#endif #ifndef MADV_DOEXEC #define MADV_DOEXEC 201 #endif @@ -40,6 +46,9 @@ #ifndef MADV_COLLAPSE #define MADV_COLLAPSE 25 #endif +#ifndef MADV_WIPEONFORK +#define MADV_WIPEONFORK 18 +#endif /* The downstream ELF loader requires this opt-in in a PT_NOTE segment. */ asm(".pushsection .note.doexec,\"a\",%note\n" @@ -117,6 +126,38 @@ static int check_bytes(char *ram, size_t size, unsigned char value) return KSFT_PASS; } +/* + * EINVAL from MADV_DOEXEC means no feature only on a kernel without the + * sysctl, or on a 32-bit one, which has no VM_EXEC_KEEP. + */ +static int unsupported(void) +{ + int err = errno; + struct utsname uts; + + if (err == EINVAL && + (access("/proc/sys/vm/madv_doexec_flag", F_OK) || + (!uname(&uts) && !strstr(uts.machine, "64") && + strcmp(uts.machine, "s390x") && strcmp(uts.machine, "alpha")))) + return KSFT_SKIP; + ksft_print_msg("MADV_DOEXEC: %s\n", strerror(err)); + fflush(stdout); + errno = err; + return KSFT_FAIL; +} + +static int fail(const char *what) +{ + ksft_print_msg("%s\n", what); + fflush(stdout); + return KSFT_FAIL; +} + +static int check_contents(char *ram, size_t size, unsigned char value) +{ + return check_bytes(ram, size, value) ? fail("contents lost") : KSFT_PASS; +} + static int after_cow_exec(char *ram, size_t size, pid_t sibling, int pipe_fd) { char ready = 1; @@ -146,7 +187,7 @@ static int before_cow_exec(void) return KSFT_FAIL; memset(ram, 0x31, size); if (madvise(ram, size, MADV_DOEXEC)) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); if (pipe(pipe_fd)) return KSFT_FAIL; pid = fork(); @@ -272,10 +313,8 @@ static int before_exec(int kind) return KSFT_SKIP; if (kind == LOCKED && mlock(ram, size)) return KSFT_SKIP; - if (madvise(ram, size, MADV_DOEXEC)) { - ksft_print_msg("MADV_DOEXEC: %s\n", strerror(errno)); - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; - } + if (madvise(ram, size, MADV_DOEXEC)) + return unsupported(); pin = (struct pin_longterm_test) { .addr = (uintptr_t)ram, .size = size, @@ -358,7 +397,7 @@ static int shared_mm_exec(bool own_thread) pid_t pid; if (!ram) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); if (pipe(pipe_fd)) return KSFT_FAIL; if (own_thread) { @@ -403,7 +442,7 @@ static int vhost_owner_exec(void) return KSFT_SKIP; ram = preserved_ram(size); if (!ram) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); if (ioctl(fd, VHOST_SET_OWNER)) return KSFT_SKIP; if (ioctl(fd, VHOST_RESET_OWNER)) @@ -473,7 +512,7 @@ static int unprivileged_exec(const char *path, bool preserve) } ram = preserved_ram(size); if (!ram) - _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL); + _exit(unsupported()); err = exec_path(path, ram, size, 1, true); /* Exec must return EPERM with the old mapping intact. */ if (err != EPERM || check_bytes(ram, size, 0x31)) @@ -520,7 +559,7 @@ static int devzero_exec(void) return KSFT_FAIL; memset(ram, 0x31, size); if (madvise(ram, size, MADV_DOEXEC)) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); check_exec(ram, size, 1, false); return KSFT_FAIL; } @@ -558,7 +597,7 @@ static int noexec_dontexec(void) goto out; } if (madvise(ram, size, MADV_DOEXEC)) { - ret = errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + ret = unsupported(); goto out; } if (!madvise(ram, size, MADV_DONTEXEC) && exec_refused(ram, size)) @@ -577,7 +616,7 @@ static int doexec_noexec(void) int ret; if (!ram) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); if (!exec_refused(ram, size) || madvise(ram, size, MADV_DONTEXEC) || mprotect(ram, size, PROT_READ | PROT_EXEC)) return KSFT_FAIL; @@ -631,7 +670,7 @@ static int devmem_refused(void) int fd; if (!preserved_ram(size)) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); /* Without FMODE_WRITE, do_mmap() drops VM_SHARED from the mapping. */ fd = open("/dev/mem", O_RDWR); if (fd < 0) @@ -681,8 +720,10 @@ static int vfio_bar_exec(void) uintptr_t aligned; int dev; - if (!bdf || !preserved_ram(page)) + if (!bdf) return KSFT_SKIP; + if (!preserved_ram(page)) + return unsupported(); dev = vfio_device_fd(bdf); if (dev < 0 || ioctl(dev, VFIO_DEVICE_GET_REGION_INFO, &info) || !(info.flags & VFIO_REGION_INFO_FLAG_MMAP) || info.size < page) { @@ -725,7 +766,7 @@ static int nonleader_exec(void) pthread_t thread; if (!ram) - return errno == EINVAL ? KSFT_SKIP : KSFT_FAIL; + return unsupported(); if (pthread_create(&thread, NULL, exec_thread, ram)) return KSFT_FAIL; pthread_join(thread, NULL); @@ -773,7 +814,7 @@ static int proc_readers_exec(void) close(ready[0]); close(go[1]); if (!ram) - _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL); + _exit(unsupported()); if (write(ready[1], "x", 1) != 1 || read(go[0], buffer, 1) != 1) _exit(KSFT_FAIL); close(ready[1]); @@ -910,7 +951,7 @@ static int clone_exec_race(void) close(report[0]); race.ram = preserved_ram(size); if (!race.ram) - _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL); + _exit(unsupported()); if (pthread_create(&thread, NULL, clone_race_thread, &race)) _exit(KSFT_FAIL); while (atomic_load(&race.rounds) < 4) @@ -985,7 +1026,7 @@ static int allocation_failures(void) for (i = 0; i < size; i += stride) ram[i] = 0x31; if (madvise(ram, size, MADV_DOEXEC)) - _exit(errno == EINVAL ? KSFT_SKIP : KSFT_FAIL); + _exit(unsupported()); /* Keep the path lookup, which /proc fails with ENOENT, out of the sweep. */ exe = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); fd = open("/proc/self/fail-nth", O_WRONLY | O_CLOEXEC); @@ -1034,6 +1075,308 @@ static int allocation_failures(void) return early && fatal ? KSFT_PASS : KSFT_SKIP; } +static void exec_mode(const char *exe, const char *mode, void *addr, size_t size) +{ + char addr_arg[32], size_arg[32]; + + snprintf(addr_arg, sizeof(addr_arg), "%llu", + (unsigned long long)(uintptr_t)addr); + snprintf(size_arg, sizeof(size_arg), "%zu", size); + execl(exe, "doexec", mode, addr_arg, size_arg, NULL); + ksft_print_msg("exec: %s\n", strerror(errno)); + fflush(stdout); +} + +static bool range_mapped(char *addr, size_t size) +{ + return !msync(addr, size, MS_ASYNC); +} + +static int sysctl_flag(void) +{ + FILE *file = fopen("/proc/sys/vm/madv_doexec_flag", "r"); + int flag = -1; + + if (!file) { + if (access("/proc/sys/vm", F_OK) || !preserved_ram(sysconf(_SC_PAGESIZE))) + return KSFT_SKIP; + return fail("MADV_DOEXEC works without the sysctl"); + } + if (fscanf(file, "%d", &flag) != 1) + flag = -1; + fclose(file); + return flag == MADV_DOEXEC ? KSFT_PASS : fail("unexpected madv_doexec_flag"); +} + +static bool doexec_refused(char *addr, size_t size, const char *what) +{ + if (addr == MAP_FAILED) { + ksft_print_msg("%s: mmap: %s\n", what, strerror(errno)); + /* A security policy may forbid the mapping itself. */ + return errno == EACCES || errno == EPERM; + } + if (madvise(addr, size, MADV_DOEXEC) && errno == EINVAL) + return true; + ksft_print_msg("%s not refused\n", what); + return false; +} + +/* Apart from VFIO PFN maps, only private non-stack anonymous memory can be marked. */ +static int refused_mappings(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + int fd = open("/proc/self/exe", O_RDONLY); + char stack_var = 0; + bool ok = true; + + if (!preserved_ram(size)) + return unsupported(); + ok &= doexec_refused(mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0), size, "shared anon"); + ok &= doexec_refused(mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0), size, + "private file"); + ok &= doexec_refused(mmap(NULL, size, PROT_READ | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0), size, "executable"); + ok &= doexec_refused(mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0), size, + "MAP_GROWSDOWN"); + ok &= doexec_refused((char *)((uintptr_t)&stack_var & ~(size - 1)), size, "stack"); + close(fd); + return ok ? KSFT_PASS : KSFT_FAIL; +} + +static int hugetlb_refused(void) +{ + size_t size = 2ul << 20; + char *addr; + + if (!preserved_ram(sysconf(_SC_PAGESIZE))) + return unsupported(); + addr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0); + if (addr == MAP_FAILED) + return KSFT_SKIP; + return doexec_refused(addr, size, "hugetlb") ? KSFT_PASS : KSFT_FAIL; +} + +static int unmarked_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + + if (!ram) + return unsupported(); + if (madvise(ram, size, MADV_DONTEXEC) || madvise(ram, size, MADV_DONTEXEC)) + return fail("MADV_DONTEXEC failed"); + exec_mode("/proc/self/exe", "--absent", ram, size); + return KSFT_FAIL; +} + +static int after_absent(char *ram, size_t size) +{ + return range_mapped(ram, size) ? fail("range still mapped") : KSFT_PASS; +} + +/* Marking the middle page splits the VMA; only that page carries over. */ +static int partial_exec(void) +{ + size_t page = sysconf(_SC_PAGESIZE); + char *ram = mmap(NULL, 3 * page, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (ram == MAP_FAILED) + return KSFT_FAIL; + memset(ram, 0x31, 3 * page); + if (madvise(ram + page, page, MADV_DOEXEC)) + return unsupported(); + exec_mode("/proc/self/exe", "--partial", ram, 3 * page); + return KSFT_FAIL; +} + +static int after_partial(char *ram, size_t size) +{ + size_t page = size / 3; + + if (range_mapped(ram, page) || range_mapped(ram + 2 * page, page)) + return fail("unmarked page preserved"); + if (!range_mapped(ram + page, page)) + return fail("marked page not preserved"); + return check_contents(ram + page, page, 0x31); +} + +/* The flag is checked before exec too, so that its absence after is meaningful. */ +static int exec_with_flag(char *ram, size_t size, const char *mode, const char *flag) +{ + if (!smaps_has_flag(ram, flag)) + return fail("flag missing before exec"); + exec_mode("/proc/self/exe", mode, ram, size); + return KSFT_FAIL; +} + +static int flagged_exec(const char *mode, int advice, const char *flag) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + + if (!ram) + return unsupported(); + if (madvise(ram, size, advice)) + return fail("madvise failed"); + return exec_with_flag(ram, size, mode, flag); +} + +static int locked_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + + if (!ram) + return unsupported(); + if (mlock(ram, size)) + return KSFT_SKIP; + return exec_with_flag(ram, size, "--unlocked", " lo "); +} + +static int after_unlocked(char *ram, size_t size) +{ + if (smaps_has_flag(ram, " lo ")) + return fail("VM_LOCKED preserved"); + return check_contents(ram, size, 0x31); +} + +static int dontfork_exec(void) +{ + return flagged_exec("--dontfork", MADV_DONTFORK, " dc "); +} + +static int wipeonfork_exec(void) +{ + return flagged_exec("--wipeonfork", MADV_WIPEONFORK, " wf "); +} + +/* The fork flags carry over: a child of the new image sees what they say. */ +static int after_fork_flag(char *ram, size_t size, const char *flag, bool wipe) +{ + pid_t pid; + + if (!smaps_has_flag(ram, flag)) + return fail("fork flag lost"); + if (check_bytes(ram, size, 0x31)) + return fail("contents lost"); + pid = fork(); + if (pid < 0) + return KSFT_FAIL; + if (!pid) { + if (wipe) + _exit(range_mapped(ram, size) ? check_bytes(ram, size, 0) : KSFT_FAIL); + _exit(range_mapped(ram, size) ? KSFT_FAIL : KSFT_PASS); + } + return wait_child(pid); +} + +static int after_dontfork(char *ram, size_t size) +{ + return after_fork_flag(ram, size, " dc ", false); +} + +static int after_wipeonfork(char *ram, size_t size) +{ + return after_fork_flag(ram, size, " wf ", true); +} + +/* The userfaultfd context belongs to the old mm and must not carry over. */ +static int uffd_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + struct uffdio_api api = { .api = UFFD_API }; + struct uffdio_register reg = { + .mode = UFFDIO_REGISTER_MODE_MISSING, + }; + int uffd; + + if (!ram) + return unsupported(); + uffd = syscall(__NR_userfaultfd, O_CLOEXEC | UFFD_USER_MODE_ONLY); + if (uffd < 0 || ioctl(uffd, UFFDIO_API, &api)) + return KSFT_SKIP; + reg.range.start = (uintptr_t)ram; + reg.range.len = size; + if (ioctl(uffd, UFFDIO_REGISTER, ®)) + return KSFT_SKIP; + if (!smaps_has_flag(ram, " um ")) + return fail("userfaultfd registration not in smaps"); + exec_mode("/proc/self/exe", "--no-uffd", ram, size); + return KSFT_FAIL; +} + +static int after_no_uffd(char *ram, size_t size) +{ + if (smaps_has_flag(ram, " um ")) + return fail("userfaultfd registration preserved"); + return check_contents(ram, size, 0x31); +} + +/* vma_dup_policy() carries the memory policy of the range over. */ +static int mempolicy_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + unsigned long nodes = 1; + + if (!ram) + return unsupported(); + if (syscall(__NR_mbind, ram, size, MPOL_PREFERRED, &nodes, 2, 0)) + return KSFT_SKIP; + exec_mode("/proc/self/exe", "--mempolicy", ram, size); + return KSFT_FAIL; +} + +static int after_mempolicy(char *ram, size_t size) +{ + unsigned long nodes[16] = { 0 }; + int mode = -1; + + if (syscall(__NR_get_mempolicy, &mode, nodes, 8 * sizeof(nodes), ram, MPOL_F_ADDR)) + return fail("get_mempolicy failed"); + if (mode != MPOL_PREFERRED || nodes[0] != 1) + return fail("memory policy lost"); + return check_contents(ram, size, 0x31); +} + +struct after_mode { + const char *mode; + int (*fn)(char *ram, size_t size); +}; + +static const struct after_mode after_modes[] = { + { "--absent", after_absent }, + { "--partial", after_partial }, + { "--unlocked", after_unlocked }, + { "--dontfork", after_dontfork }, + { "--wipeonfork", after_wipeonfork }, + { "--no-uffd", after_no_uffd }, + { "--mempolicy", after_mempolicy }, +}; + +struct more_case { + const char *name; + int (*fn)(void); +}; + +static const struct more_case more_cases[] = { + { "madv_doexec_flag sysctl matches MADV_DOEXEC", sysctl_flag }, + { "shared, file, executable and stack mappings refused", refused_mappings }, + { "hugetlb mapping refused", hugetlb_refused }, + { "MADV_DONTEXEC: range not preserved", unmarked_exec }, + { "partially marked VMA: only the marked part preserved", partial_exec }, + { "mlocked range: preserved unlocked", locked_exec }, + { "MADV_DONTFORK range: preserved, not given to a child", dontfork_exec }, + { "MADV_WIPEONFORK range: preserved, wiped in a child", wipeonfork_exec }, + { "userfaultfd registration: not preserved", uffd_exec }, + { "memory policy: preserved", mempolicy_exec }, +}; + int main(int argc, char **argv) { pid_t pid; @@ -1052,6 +1395,10 @@ int main(int argc, char **argv) strtoull(argv[3], NULL, 10), atoi(argv[4]), atoi(argv[5])); if (argc == 5 && !strcmp(argv[1], "--reject")) return KSFT_FAIL; + for (kind = 0; argc == 4 && kind < (int)ARRAY_SIZE(after_modes); kind++) + if (!strcmp(argv[1], after_modes[kind].mode)) + return after_modes[kind].fn((char *)(uintptr_t)strtoull(argv[2], NULL, 10), + strtoull(argv[3], NULL, 10)); if (argc == 2 && !strcmp(argv[1], "--euid")) return geteuid() ? KSFT_SKIP : KSFT_PASS; if (argc == 4 && !strcmp(argv[1], "--pfnmap")) @@ -1070,18 +1417,20 @@ int main(int argc, char **argv) write(fd, "0", 1); close(fd); } + if (!range_mapped(ram, size)) + return fail("range not preserved"); for (i = 0; i < size; i += stride) if (ram[i] != 0x31) return KSFT_FAIL; return KSFT_PASS; } ksft_print_header(); - ksft_set_plan(NR_TESTS + 13); + ksft_set_plan(NR_TESTS + 13 + ARRAY_SIZE(more_cases)); for (kind = 0; kind < NR_TESTS; kind++) { fflush(stdout); pid = fork(); if (!pid) - _exit(before_exec(kind)); + exit(before_exec(kind)); ret = pid < 0 ? KSFT_FAIL : wait_child(pid); if (ret == KSFT_SKIP) ksft_test_result_skip("%s: DOEXEC, GUP test or mapping unavailable\n", @@ -1093,7 +1442,7 @@ int main(int argc, char **argv) fflush(stdout); pid = fork(); if (!pid) - _exit(before_cow_exec()); + exit(before_cow_exec()); ret = pid < 0 ? KSFT_FAIL : wait_child(pid); if (ret == KSFT_SKIP) ksft_test_result_skip("pre-existing COW: DOEXEC unavailable\n"); @@ -1121,27 +1470,27 @@ int main(int argc, char **argv) switch (kind) { case 0: case 1: - _exit(shared_mm_exec(kind)); + exit(shared_mm_exec(kind)); case 2: - _exit(vhost_owner_exec()); + exit(vhost_owner_exec()); case 3: - _exit(allocation_failures()); + exit(allocation_failures()); case 4: - _exit(nonleader_exec()); + exit(nonleader_exec()); case 5: - _exit(proc_readers_exec()); + exit(proc_readers_exec()); case 6: - _exit(clone_exec_race()); + exit(clone_exec_race()); case 7: - _exit(setuid_exec()); + exit(setuid_exec()); case 8: - _exit(devzero_exec()); + exit(devzero_exec()); case 9: - _exit(doexec_noexec()); + exit(doexec_noexec()); case 10: - _exit(devmem_refused()); + exit(devmem_refused()); case 11: - _exit(vfio_bar_exec()); + exit(vfio_bar_exec()); } } ret = pid < 0 ? KSFT_FAIL : wait_child(pid); @@ -1150,5 +1499,16 @@ int main(int argc, char **argv) else ksft_test_result(ret == KSFT_PASS, "%s\n", extra_names[kind]); } + for (kind = 0; kind < (int)ARRAY_SIZE(more_cases); kind++) { + fflush(stdout); + pid = fork(); + if (!pid) + exit(more_cases[kind].fn()); + ret = pid < 0 ? KSFT_FAIL : wait_child(pid); + if (ret == KSFT_SKIP) + ksft_test_result_skip("%s: unavailable\n", more_cases[kind].name); + else + ksft_test_result(ret == KSFT_PASS, "%s\n", more_cases[kind].name); + } ksft_finished(); } -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel