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 876DD80BDA for ; Wed, 21 Oct 2026 16:58:47 +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 69LGvOsU022977; Wed, 21 Oct 2026 19:57:25 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69LGvOsU022977 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="OgdJBRCS" Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69LGvMhI022937 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 21 Oct 2026 19:57:22 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69LGvMhI022937 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-4a16dc2323cso8144625e9.3 for ; Wed, 21 Oct 2026 09:57:22 -0700 (PDT) X-Gm-Message-State: AFq9FYL1PQNIBiOD0iW6l4jnM87zTtVuvHXEoUPryDZJKk+/zS14FPsZ ltY0+b4B3+TzL2jXfxbJkN2RzFgLkaIkuQPN31uwFDVQ9EEOP2z8TGbqMa5UfulmAmNvWU2etXe owWU0kFU0/FMrEgCmyB6Szv9cBmno3x11+gau1oxL/gF3Hzg9GmtsVw== X-Gm-Gg: AYBFou31vxQD6YplrpjtdMlLUdKN/sEJbBt6LhDKPOGjGgs6mJ4/ESNFiDO82Y+hAZr e3gl6VO/6dekjtneXP7QT39oXtijuPUFEnTqzqSUD2NczGp/7G+MeoNxuXhGlWv70LGKQzrl+/D lX5KQUMMrQlwSXI/JygXTAa8sR1Ceh5PI6wC5oa0pT83SUwlDC//ZhHby/pzBStIrMXGw9bmA76 8JDqnavWJ1Vz74+dNBIKe2Qs1sql6U0FC+9+1YFv/69BY1+8wI0geHUat+/deQLX5vPzly0wfqb U7D3FbVThn6OO06riJNMagdQdUP4E58pERwCbQsvNd2bZmFzHKcMkSN0kkWjuEVYS/fmSNro4aM IcxNrys20tR6of/YV X-Received: by 2002:a05:600c:4fd4:b0:4a1:7ba9:da2c with SMTP id 5b1f17b1804b1-4a1fdd4d220mr55260545e9.25.1792601842217; Wed, 21 Oct 2026 09:57:22 -0700 (PDT) X-Received: by 2002:a05:600c:4fd4:b0:4a1:7ba9:da2c with SMTP id 5b1f17b1804b1-4a1fdd4d220mr55259845e9.25.1792601841647; Wed, 21 Oct 2026 09:57:21 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1792601841; cv=none; d=google.com; s=arc-20260327; b=KnisLwVNbMgW8vuFSJ+vJuLfDbbyWy5z1k3czoUmUMuFEKJHr/btEpumIeGZYAtB3Z IYWIQKo/RHw21pxyPJUxSFvWVTh27ZDYPbf9+WjAZLaeZokK/KjCXT78cGHrAV/GoAn/ J7yzZ8TW54Gh/4yM4uRtP4szHZX4+zXMAyH3rpxEXSjyQRAinfm9RImY2fmBdCQNLQlc Yx/O9oFz/cl3g9JMisCuUkTAOv0kSPg9OBfcqfp2zcJgk7kHyItxC50P1n7DYptW5A7T zV6wDAV0jq5fU+x/Q8ag6XtwnI1/3JHVQne19CwYW/A6z+DOJ1UsAaxKdhwNSMXG66W1 YZ1g== 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=lZNOVCaFQjXih/kkh6f8iDGq2w2bdWLCJAxo0Al0ock=; fh=ORqO450YHYjnAvQVnHrQRoSXc/Ljts4WdUx12SqEe+w=; b=EB6258fmVQ+QbDnu04lOGjoDf/6yGyDGBv0Wf6i29BCLZC5bX9G6t9Spk0CPrAJXSE rny10IqwxyDZ6Vo3+XpY5G3cCf4WsOpz8UFPW24IUk5reC3PEI9DdKJJG3HXfFQY8HTq EHxCL/5N4WBma8PyzunFkPjwZjQoj5k8LPKWDMJekjTP2bmcGx0oIOPXGDxnWMaUNQx2 NJ/alYM5Pzn8JzJrmSAW3t6NY6P7cbYFg8pEVasxSHKjBTK5tAotLcCrfW2wrXWwLs3N yadvsbcftlqDnQjlFrJEGTLpZP3Y8qTylrNOCXI1UuiB2Uv6p4OnZVnC0Ux+E3MjkWso i6Uw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=OgdJBRCS; 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 5b1f17b1804b1-4a203b7189fsi2187425e9.15.2026.10.21.09.57.21 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 21 Oct 2026 09:57:21 -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=OgdJBRCS; 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=lZNOVCaFQjXih/kkh6f8iDGq2w2bdWLCJAxo0Al0ock=; b=OgdJBRCSeADQ99D+r1/ pVbgDPigZxUwV8nlpV79tiBac2/hXT4hLLP01uwf9Hvcup2LUjo8Z1F4vgK+KLUVnjL1Qrl/SZ/xt dPPXvm3+NxDnAJTGe5gckGuG1MwxtF0utK/jM2+DsG2g/tTFRG2vecKn2lr9yEBPJroWUNaOExLwf SofXAPbRq3mFmnb7pJDXBRY2M7SC42MIuY3JDTV32nEyLIn6p4ay80eSSGeEl3RdPeb3A1INYsnLF BZbsWHkxRVBlHMSsuDmIi5cPkxhyxqxswo8ErncIeE+OEXhOmb7pzQA87JKJh3JUQRamNDIyyzujN 2IRKFJMPekHjadw==; 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 1xJZZJ-008y7H-1P; Wed, 21 Oct 2026 18:57:20 +0200 From: Konstantin Khorenko Date: Wed, 21 Oct 2026 18:57:15 +0200 MIME-Version: 1.0 Message-Id: <20261021-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v2-2-97dbb5c6e0ab@virtuozzo.com> References: <20261021-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v2-0-97dbb5c6e0ab@virtuozzo.com> In-Reply-To: <20261021-b4-prep-khorenko-vstor-149847-5-doexec-selftests-v2-0-97dbb5c6e0ab@virtuozzo.com> To: Andrey Drobyshev , Pavel Tikhomirov , Vasileios Almpanis X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=15391; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=R1Lw9Aktzuqc0v/kr6ldSJ7/qyIakaN+CtohzZwTTjw=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBq2O7t94sSwMtQzEcA8HVhaQkWpnVDm9G2kNqaU 9rjOYt3ONWJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCatju7QAKCRBRlgpH+WFc KvSeEACHZIiagXemWNSihgLrBmNJRvRQ1OrUhLst9QbeNoW0rKOFkUD4ele+dEeZp7/O2tjFhBi /RfjEs2n0TU6fEU3+H7xS5BpxzX1wiOzpXpHAwgtY49ukYp6DgIy/tRh3r0W53QGsUrvACBCp4M IhUb+HHvuHY2XyuYB43oCQeK5lKh5eEMBbo7lOIFvLTAwNC0Sa+MHgqpEAd03hoC2ZX5nNOXLJN Uoyrdhgsgy0d4E91Eux10vhd71xZugiatrKICakrGeEeG1Rq6GXKHUOon7ZDlTRSiovVDQRzoE/ Nuq878/VI0BvPPN5laj1f8u0imndKt9wM5PVBM4QZ4Zc/RPyL+0ogBdQi/0SZfhKxQ3n84u6AMn Pbv59UzXnFyZV6b+thqCUmSWmDt6HDzPgr7hpqYveF4OTvyoX/3mLSlSHhD9Xqc2CgyNbzaqT+6 YSJoPcErLgQksptmqE1Cs+xB8XwJTRfzXaBGN0oPMtuw1/RLd7AooFGIr7oHFjG2JphJ0g1BISw /At/nvNoR7irqmoZ2y8Ku+7C3M9EwvQcWBhjC5ae6GRjzJKdvCZ/cHics6lxI33NowBLnp2GEps NiBc7/TeIh9AmQQvioIcOEWQsr2LcKizBmDHKmN+89HWb6eY/X83cczqrJ7YyAsaAHDN32RbUvP 7+PMQsAfnrSH/zA== 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 v2 2/9] selftests/mm: cover the MADV_DOEXEC opt-in note and the new stack 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 the exec side: - A copy of the test binary whose note type is changed carries no opt-in, and the marked range is absent in the new image, while an unchanged copy run the same way keeps it. - A copy whose note has the opt-in type but another name, in the last PT_NOTE segment, fails exec with ENOEXEC before the point of no return, and the marked range is still there. The copy is started in a mode that fails if the exec goes through. The same note in another segment is skipped: the exec goes through without the memory. - A copy whose PT_NOTE holding the opt-in comes before another PT_NOTE in the program headers still gets the memory: the loader has to look at every note segment, not only the last one. The test binary also carries an 8-aligned note, which ld.bfd and lld never put in the PT_NOTE of the 4-aligned opt-in, so there are two to reorder. - With ADDR_NO_RANDOMIZE the new stack starts at STACK_TOP and grows by 128K below the argument pages, plus the stack guard gap; a range preserved there makes setup_arg_pages() fail the exec, which kills the task with SIGSEGV. The place is computed from the size of the environment, and the same exec with the page far below the stack must succeed first. x86_64 only, as the address comes from its STACK_TOP; skipped when randomization is off, since the page then falls on the stack of the test itself. The note is found through the PT_NOTE program headers and patched in the byte order of the machine; a missing note fails the cases. The copies are written to the first of the current directory, /var/tmp and /tmp that is not mounted noexec, unlinked and executed through their fd. 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 | 373 +++++++++++++++++++++++++++++++++++- 1 file changed, 370 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/mm/doexec.c b/tools/testing/selftests/mm/doexec.c index 3d151dd901871..261cc57afa1da 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 what MADV_DOEXEC preserves across exec, long-term DMA pins first. */ #define _GNU_SOURCE +#include #include #include #include #include +#include #include #include #include @@ -22,9 +24,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -34,6 +38,9 @@ #include "../../../../mm/gup_test.h" #include "../kselftest.h" +#ifndef MAP_FIXED_NOREPLACE +#define MAP_FIXED_NOREPLACE 0x100000 +#endif #ifndef UFFD_USER_MODE_ONLY #define UFFD_USER_MODE_ONLY 1 #endif @@ -58,6 +65,14 @@ asm(".pushsection .note.doexec,\"a\",%note\n" ".balign 4\n" ".popsection\n"); +/* An 8-aligned note cannot share a PT_NOTE with the 4-aligned ones. */ +asm(".pushsection .note.doexec8,\"a\",%note\n" + ".balign 8\n" + ".long 4, 0, 1\n" + ".asciz \"DOE\"\n" + ".balign 8\n" + ".popsection\n"); + enum test_kind { BASE, HUGE, @@ -452,6 +467,8 @@ static int vhost_owner_exec(void) return KSFT_FAIL; } +static const char * const copy_dirs[] = { ".", "/var/tmp", "/tmp" }; + static void remove_setuid_copy(char *path) { unlink(path); @@ -526,14 +543,13 @@ static int unprivileged_exec(const char *path, bool preserve) /* The ELF opt-in must not pull memory into a more privileged image. */ static int setuid_exec(void) { - static const char * const dirs[] = { ".", "/var/tmp", "/tmp" }; char path[96]; int i, ret = KSFT_SKIP; if (geteuid()) return KSFT_SKIP; - for (i = 0; i < 3 && ret == KSFT_SKIP; i++) { - if (make_setuid_copy(dirs[i], path, sizeof(path))) + for (i = 0; i < (int)ARRAY_SIZE(copy_dirs) && ret == KSFT_SKIP; i++) { + if (make_setuid_copy(copy_dirs[i], path, sizeof(path))) continue; /* A nosuid mount or a private directory makes the copy useless. */ ret = unprivileged_exec(path, false); @@ -1184,6 +1200,18 @@ static int unmarked_exec(void) return KSFT_FAIL; } +static int after_present(char *ram, size_t size) +{ + if (!range_mapped(ram, size)) + return fail("range not preserved"); + return check_contents(ram, size, 0x31); +} + +static int after_never(char *ram, size_t size) +{ + return fail("exec was expected to fail"); +} + static int after_absent(char *ram, size_t size) { return range_mapped(ram, size) ? fail("range still mapped") : KSFT_PASS; @@ -1356,6 +1384,338 @@ static int after_mempolicy(char *ram, size_t size) return check_contents(ram, size, 0x31); } +#define DOEXEC_NOTE_TYPE 0x07c1feed +#define ELF_EHDR ElfW(Ehdr) +#define ELF_PHDR ElfW(Phdr) +#define ELF_NHDR ElfW(Nhdr) + +static char *read_self(size_t *size) +{ + int fd = open("/proc/self/exe", O_RDONLY); + char *image = NULL; + struct stat st; + + if (fd < 0) + return NULL; + if (!fstat(fd, &st)) + image = malloc(st.st_size); + if (image && read(fd, image, st.st_size) != st.st_size) { + free(image); + image = NULL; + } + close(fd); + if (image) + *size = st.st_size; + return image; +} + +static ELF_NHDR *find_opt_in_note(char *image, size_t size, int *seg) +{ + ELF_EHDR *ehdr = (ELF_EHDR *)image; + ELF_PHDR *phdr; + size_t off, end; + int i; + + if (size < sizeof(*ehdr) || memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) + return NULL; + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = (ELF_PHDR *)(image + ehdr->e_phoff) + i; + if (phdr->p_type != PT_NOTE || phdr->p_offset + phdr->p_filesz > size) + continue; + end = phdr->p_offset + phdr->p_filesz; + for (off = phdr->p_offset; off + sizeof(ELF_NHDR) <= end;) { + ELF_NHDR *note = (ELF_NHDR *)(image + off); + + if (note->n_type == DOEXEC_NOTE_TYPE && + note->n_namesz == sizeof("preserved-mem-ok") && + !memcmp(note + 1, "preserved-mem-ok", sizeof("preserved-mem-ok"))) { + *seg = i; + return note; + } + off += sizeof(*note) + ((note->n_namesz + 3) & ~3u) + + ((note->n_descsz + 3) & ~3u); + } + } + return NULL; +} + +enum note_patch { NOTE_KEEP, NOTE_RETYPE, NOTE_RENAME, NOTE_NOT_LAST, NOTE_RENAME_NOT_LAST }; + +/* Move the segment holding the opt-in before another PT_NOTE if it is the last. */ +static bool note_not_last(char *image, int seg) +{ + ELF_EHDR *ehdr = (ELF_EHDR *)image; + ELF_PHDR *phdr = (ELF_PHDR *)(image + ehdr->e_phoff); + ELF_PHDR tmp; + int i, other = -1; + + for (i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_type != PT_NOTE || i == seg) + continue; + if (i > seg) + return true; + if (other < 0) + other = i; + } + if (other < 0) + return false; + tmp = phdr[other]; + phdr[other] = phdr[seg]; + phdr[seg] = tmp; + return true; +} + +/* Make the segment holding the opt-in the last PT_NOTE. */ +static void note_last(char *image, int seg) +{ + ELF_EHDR *ehdr = (ELF_EHDR *)image; + ELF_PHDR *phdr = (ELF_PHDR *)(image + ehdr->e_phoff); + ELF_PHDR tmp; + int i, last = seg; + + for (i = 0; i < ehdr->e_phnum; i++) + if (phdr[i].p_type == PT_NOTE) + last = i; + tmp = phdr[last]; + phdr[last] = phdr[seg]; + phdr[seg] = tmp; +} + +/* An fd of a patched copy of this binary; -2: no opt-in note, -3: one PT_NOTE. */ +static int patched_copy(enum note_patch how) +{ + char path[64], *image; + ELF_NHDR *note; + struct statvfs vfs; + int fd = -1, i, seg; + size_t size; + + image = read_self(&size); + if (!image) + return -1; + note = find_opt_in_note(image, size, &seg); + if (!note) { + free(image); + return -2; + } + if (how == NOTE_RETYPE) + note->n_type = DOEXEC_NOTE_TYPE - 1; + else if (how == NOTE_RENAME) { + memcpy(note + 1, "preserved-mem-no", 16); + note_last(image, seg); + } else if ((how == NOTE_NOT_LAST || how == NOTE_RENAME_NOT_LAST) && + !note_not_last(image, seg)) { + free(image); + return -3; + } + if (how == NOTE_RENAME_NOT_LAST) + memcpy(note + 1, "preserved-mem-no", 16); + for (i = 0; i < (int)ARRAY_SIZE(copy_dirs) && fd < 0; i++) { + if (statvfs(copy_dirs[i], &vfs) || (vfs.f_flag & ST_NOEXEC)) + continue; + snprintf(path, sizeof(path), "%s/doexec-note.XXXXXX", copy_dirs[i]); + fd = mkstemp(path); + if (fd < 0) + continue; + if (write(fd, image, size) != (ssize_t)size || fchmod(fd, 0755)) { + close(fd); + unlink(path); + fd = -1; + continue; + } + close(fd); + fd = open(path, O_RDONLY | O_CLOEXEC); + unlink(path); + } + free(image); + return fd; +} + +static int exec_fd_mode(int exe, const char *mode, char *ram, size_t size) +{ + char addr_arg[32], size_arg[32]; + char *args[] = { "doexec", (char *)mode, addr_arg, size_arg, NULL }; + + snprintf(addr_arg, sizeof(addr_arg), "%llu", + (unsigned long long)(uintptr_t)ram); + snprintf(size_arg, sizeof(size_arg), "%zu", size); + syscall(__NR_execveat, exe, "", args, environ, AT_EMPTY_PATH); + return errno; +} + +/* An unpatched copy run the same way keeps the memory. */ +static int copy_control(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + pid_t pid = fork(); + char *ram; + int exe; + + if (pid < 0) + return KSFT_FAIL; + if (!pid) { + ram = preserved_ram(size); + if (!ram) + exit(unsupported()); + exe = patched_copy(NOTE_KEEP); + if (exe == -2) + exit(fail("opt-in note not found in the test binary")); + if (exe < 0) + exit(KSFT_SKIP); + exit(exec_fd_mode(exe, "--present", ram, size) == EACCES ? KSFT_SKIP : KSFT_FAIL); + } + return wait_child(pid); +} + +static int no_note_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram; + int exe, err; + + err = copy_control(); + if (err) + return err == KSFT_SKIP ? KSFT_SKIP : fail("unpatched copy lost the memory"); + ram = preserved_ram(size); + if (!ram) + return unsupported(); + exe = patched_copy(NOTE_RETYPE); + if (exe == -2) + return fail("opt-in note not found in the test binary"); + if (exe < 0) + return KSFT_SKIP; + err = exec_fd_mode(exe, "--absent", ram, size); + if (err == EACCES) + return KSFT_SKIP; + ksft_print_msg("exec: %s\n", strerror(err)); + return KSFT_FAIL; +} + +static int note_not_last_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + int exe, err; + + if (!ram) + return unsupported(); + exe = patched_copy(NOTE_NOT_LAST); + if (exe == -2) + return fail("opt-in note not found in the test binary"); + if (exe < 0) + return KSFT_SKIP; + err = exec_fd_mode(exe, "--present", ram, size); + if (err == EACCES) + return KSFT_SKIP; + ksft_print_msg("exec: %s\n", strerror(err)); + return KSFT_FAIL; +} + +/* Outside the last PT_NOTE a malformed opt-in is skipped, not refused. */ +static int bad_note_not_last_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + int exe, err; + + if (!ram) + return unsupported(); + exe = patched_copy(NOTE_RENAME_NOT_LAST); + if (exe == -2) + return fail("opt-in note not found in the test binary"); + if (exe < 0) + return KSFT_SKIP; + err = exec_fd_mode(exe, "--absent", ram, size); + if (err == EACCES) + return KSFT_SKIP; + ksft_print_msg("exec: %s\n", strerror(err)); + return KSFT_FAIL; +} + +static int bad_note_exec(void) +{ + size_t size = sysconf(_SC_PAGESIZE); + char *ram = preserved_ram(size); + int exe, err; + + if (!ram) + return unsupported(); + exe = patched_copy(NOTE_RENAME); + if (exe == -2) + return fail("opt-in note not found in the test binary"); + if (exe < 0) + return KSFT_SKIP; + err = exec_fd_mode(exe, "--never", ram, size); + if (err == EACCES) + return KSFT_SKIP; + if (err != ENOEXEC) { + ksft_print_msg("exec: %s\n", strerror(err)); + return fail("malformed note not refused with ENOEXEC"); + } + return check_contents(ram, size, 0x31); +} + +#if defined(__x86_64__) && !defined(__ILP32__) +/* With @below pages under the argument pages, exec the page preserved there. */ +static int stack_exec(size_t below) +{ + size_t size = sysconf(_SC_PAGESIZE), args = 2 * size; + struct rlimit no_core = { 0, 0 }; + char *top = (char *)((1ul << 47) - size), *ram, **env; + int status; + pid_t pid; + + for (env = environ; *env; env++) + args += strlen(*env) + 1; + args = (args + size - 1) & ~(size - 1); + pid = fork(); + if (pid < 0) + return -1; + if (!pid) { + setrlimit(RLIMIT_CORE, &no_core); + if (personality(ADDR_NO_RANDOMIZE) < 0) + _exit(KSFT_SKIP); + ram = mmap(top - args - below * size, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + if (ram == MAP_FAILED) + _exit(KSFT_SKIP); + memset(ram, 0x31, size); + if (madvise(ram, size, MADV_DOEXEC)) + _exit(unsupported()); + exec_mode("/proc/self/exe", "--present", ram, size); + _exit(KSFT_FAIL); + } + if (waitpid(pid, &status, 0) != pid) + return -1; + return status; +} + +static int stack_overlap_exec(void) +{ + int status; + + /* Far below the stack, past the default guard gap, the same exec works... */ + status = stack_exec(4096); + if (status < 0 || !WIFEXITED(status)) + return fail("control exec failed"); + if (WEXITSTATUS(status) != KSFT_PASS) + return WEXITSTATUS(status) == KSFT_SKIP ? KSFT_SKIP : fail("control exec failed"); + /* ...within the 128K the stack is grown by, it kills the task. */ + status = stack_exec(16); + if (status < 0) + return KSFT_FAIL; + if (WIFEXITED(status)) + return WEXITSTATUS(status) == KSFT_SKIP ? KSFT_SKIP : + fail("exec did not kill the task"); + return WTERMSIG(status) == SIGSEGV ? KSFT_PASS : fail("killed by another signal"); +} +#else +static int stack_overlap_exec(void) +{ + return KSFT_SKIP; +} +#endif + struct after_mode { const char *mode; int (*fn)(char *ram, size_t size); @@ -1363,6 +1723,8 @@ struct after_mode { static const struct after_mode after_modes[] = { { "--absent", after_absent }, + { "--never", after_never }, + { "--present", after_present }, { "--partial", after_partial }, { "--unlocked", after_unlocked }, { "--dontfork", after_dontfork }, @@ -1387,6 +1749,11 @@ static const struct more_case more_cases[] = { { "MADV_WIPEONFORK range: preserved, wiped in a child", wipeonfork_exec }, { "userfaultfd registration: not preserved", uffd_exec }, { "memory policy: preserved", mempolicy_exec }, + { "binary without the opt-in note: nothing preserved", no_note_exec }, + { "malformed opt-in note: ENOEXEC, memory intact", bad_note_exec }, + { "opt-in note not in the last PT_NOTE: preserved", note_not_last_exec }, + { "malformed opt-in note not in the last PT_NOTE: skipped", bad_note_not_last_exec }, + { "preserved range under the new stack: exec kills the task", stack_overlap_exec }, }; int main(int argc, char **argv) -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel