From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A5B7860A260F for ; Tue, 20 Oct 2026 18:20:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=lists.infradead.org header.i=@lists.infradead.org header.a=rsa-sha256 header.s=bombadil.20210309 header.b=JILUsTKN; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=FSxBKGE0 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=ODwfS8M8SXuoEAx3U0UwYdL7MiECVNqk0msYC5n17UI=; b=JILUsTKNVRhuKcBTdbyThomwoN TO4viEaQkDP02pcoBShqgRUrKRR10GPfVMQxovw5KUi5B+jXqd8/3AGgSyghQIlWzbxn83nn4FH61 NwwRYolYZf96hw2CtAFgOP1nORXChYKKg7EzZxqhbXFVpS0xTuwrYLPYJ0lK386nOjtXbsj35hT3E bH9THAmF5EDi4IjmeQN7qSUpRhizvcOmAami+nQKgpCypiDuYr/UkHNVIsbTmwaK/KJYT0Rd8M9I5 Gq7I/pi7t9GNN9XUFHJ8F6Om4QpMI1MMqJ6YXDd78a7yz+BEnQOhCekxCokthlkaFJxJ2WvNR07ge 1QyYm0ng==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.99.1 #2 (Red Hat Linux)) id 1xJERR-0000000AQVX-1xdn; Tue, 20 Oct 2026 18:20:05 +0000 Received: from mail-dy1-x132d.google.com ([2607:f8b0:4864:20::132d]) by bombadil.infradead.org with esmtps (Exim 4.99.1 #2 (Red Hat Linux)) id 1xJERI-0000000AQNW-39f4 for linux-arm-kernel@lists.infradead.org; Tue, 20 Oct 2026 18:19:58 +0000 Received: by mail-dy1-x132d.google.com with SMTP id 5a478bee46e88-33bf40b0c8bso1845069eec.0 for ; Tue, 20 Oct 2026 11:19:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792520396; x=1793125196; darn=lists.infradead.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=ODwfS8M8SXuoEAx3U0UwYdL7MiECVNqk0msYC5n17UI=; b=FSxBKGE09LT5Xfh0ravSsSfp8xi+Jv7PnMY/QeG1k0e8SwaStAGRShpNoYBF+2K0O+ 1DgD/mKvmv6o1S5Bxhq96yHFNLUTibhunknf8Q3VCg4LAJF+uuOGtSnV0S2jd/tQLfv1 IgOY3YLRRzPXSqRlp8EKwsPO4BhEPeky/KuY5XKA7QIbqoW89HwUKHGgrqo4m4zmgZY0 IO0Rs95z18Sll69/Tq3IBlMbnnoBrVZUp/x83O9Lc3jztWQOeDjav3zyewIQ549F4umt i1lFyDbUjLPA/oq32paCgQ6cvEAfsnuwo3V5RJ7yZ/RIn5AWl2+s5GKGn8VX5PZj+waP 2h6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792520396; x=1793125196; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=ODwfS8M8SXuoEAx3U0UwYdL7MiECVNqk0msYC5n17UI=; b=LRvGTPIs7u+XsugXQE8D8RZj/84BbFq0NexCw2YUob1l7w+1WGDeuETpJjT9DhyOQ5 ZDTTYkNSQbu32c/MZiYxEtpkYFcDntBMpLGLbOl8fGdOaaX2Ey+H6wjcRoTTXDOMlgyn TwTn2sbbN5rZ+lq4/usjBQ/HR8guvc/Gwx/xKQc7VxdnFpFnKU0ybypTuBdpkXxZozYo /EsAndja5BdXokExSs1xEb34ZQNgQMD98YtTB2NGtFnP1nQlSovZ4D97VXgYh0FP7XeZ hUO6AibqfPZd9+FDA7sy1lge6axpfkzalR6LnEjdMsxDR1PUzWkkEKw6yutMVZzvOznF Se7A== X-Forwarded-Encrypted: i=1; AKwUvBxvkDC21C4VIlq3puYa5dhD46OKZ1DStEi/KmlRUUKBJPVA4zL2Dm/xvwrJPFWXkKZXEtWQStmH2g+onRV7d4R1@lists.infradead.org X-Gm-Message-State: AFq9FYLLPoK7f9vD+GnaJ7aWvDCps+fdN0msyo0qjuAyajKOzB/hbAKz PbSlE1zrNz9MID+MVZxOu4VssMIJWO7sEWfy0mlYaI31j+Dr4qdNPuYwuE6UTQE8w/5lqw== X-Gm-Gg: AYBFou2lZXCq/PV66lHlJ8Y2zm6qPkKeaJO1+XUpW556/4juje1OMhpK/xdy4w2D74d m51/h6dBa3BuvC0tJbmLQGX/aqfBgwhQEVOafClK1ZqiHziQHJ+iKFDS+LBBipSHkrhPDc3Hwxj HFGp/bFJo+AgUa54Nejrd/N9wzHemQSGiyv3sucKJ77mvb68D84mcaTItfFMiKf0JJ3KwxHDUWw sPxwc469qnTliDMgsJmcy5qQFX7esAlklf0CTjkancw6l19ZuoZePhYzb/SopjMZXAd1pqsMY17 /WUJvGkRP9MptvUCHSeVNf49O/L5TpirWk99ZcdWBKYKvc8QP1JdbY/gUzii+e4bSxRmnH+pvEr /ceTZexqJpivET7pZOLaRCw7z2N98PdYOp+fg3OLisaI+WkIbYuCT+E2g0hOowe3WBJbLno+5RI G3t8yKIAMt4DunHDDIdXcP77SE2lFEn1iQ6YyN2Nt3KK9FnhsWOIxuFXqQZZisoloOEKsk8lH20 OE= X-Received: by 2002:a05:7300:8812:b0:35b:da8b:f88c with SMTP id 5a478bee46e88-367508eb4dbmr34905eec.41.1792520395576; Tue, 20 Oct 2026 11:19:55 -0700 (PDT) Received: from unix.. ([181.229.23.179]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3673e15353csm523728eec.2.2026.10.20.11.19.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Oct 2026 11:19:55 -0700 (PDT) From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= To: Catalin Marinas , Will Deacon , Paul Walmsley , Palmer Dabbelt , linux-arm-kernel@lists.infradead.org, linux-riscv@lists.infradead.org Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Mark Rutland , Mark Brown , Deepak Gupta , Alexandre Ghiti , Albert Ou , Shuah Khan , Christian Brauner , James Morse , Yury Khrustalev , Thiago Jung Bauermann , Samuel Holland , Zong Li , Charlie Jenkins , Shaopeng Tan , Zishun Yi , "Peter Zijlstra (Intel)" Subject: [PATCH v2 3/4] selftests/arm64: GCS must not leak on a failed clone3 Date: Tue, 20 Oct 2026 15:19:32 -0300 Message-ID: <420e012252ed09cdb83d18512488fd2535ffc7fa.1792519121.git.ivanrwcm25@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: References: X-Archived-By: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.9.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20261020_111956_796697_6602E324 X-CRM114-Status: GOOD ( 25.57 ) X-Spam-Score: -0.1 (/) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" X-Spam-Status: No, score=-14.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS,HK_RANDOM_FROM, INCLUDES_CR_TRAILER,INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE, SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=4.0.1 X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on aws-us-west-2-korg-listserv-alma10-1 clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer fails in put_user() after copy_thread() has mapped a GCS. copy_process() then calls exit_thread(). Without HAVE_EXIT_THREAD that free is a no-op and the VMA stays in the parent's mm. Use kselftest.h / KTAP. Skip paths that cannot run the regression run before ksft_set_plan(). Then confirm a valid clone3 writes a pidfd, reject 16 bad-pidfd clones with -EFAULT, and require the shadow-stack VMA count (VmFlags " ss") to stay unchanged. ENOSYS, EINVAL, and EPERM from the valid clone skip; any other error, or a success that leaves pidfd unset, fails. The rejected child exits with a raw svc. The test expects /proc already mounted. Move my_syscall2 into gcs-util.h and reuse it from gcs-locking.c and libc-gcs.c. Assisted-by: LLM Signed-off-by: Iván Ezequiel Rodriguez --- tools/testing/selftests/arm64/gcs/Makefile | 3 +- .../selftests/arm64/gcs/gcs-clone-pidfd.c | 168 ++++++++++++++++++ .../testing/selftests/arm64/gcs/gcs-locking.c | 20 --- tools/testing/selftests/arm64/gcs/gcs-util.h | 24 +++ tools/testing/selftests/arm64/gcs/libc-gcs.c | 20 --- 5 files changed, 194 insertions(+), 41 deletions(-) create mode 100644 tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c diff --git a/tools/testing/selftests/arm64/gcs/Makefile b/tools/testing/selftests/arm64/gcs/Makefile index 1fbbf0ca1f02..f1365eea31c9 100644 --- a/tools/testing/selftests/arm64/gcs/Makefile +++ b/tools/testing/selftests/arm64/gcs/Makefile @@ -6,7 +6,8 @@ # nolibc. # -TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr +TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr \ + gcs-clone-pidfd TEST_GEN_PROGS_EXTENDED := gcs-stress-thread LDLIBS+=-lpthread diff --git a/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c b/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c new file mode 100644 index 000000000000..96400ea376c0 --- /dev/null +++ b/tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must + * not leak a GCS VMA. copy_thread() maps one, then put_user() fails + * and copy_process() calls exit_thread(). Without HAVE_EXIT_THREAD + * that free is a no-op and the VMA stays in the parent's mm. + * + * After enabling GCS, do not return from main(): libc frames above + * that point have no GCS entries. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "kselftest.h" +#include "gcs-util.h" + +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef __NR_clone3 +#define __NR_clone3 435 +#endif + +#define NCLONE 16 +#define STACK_SIZE (64 * 1024) + +static int count_ss_vmas(void) +{ + FILE *f; + char line[256]; + int vmas = 0; + + f = fopen("/proc/self/smaps", "r"); + if (!f) + ksft_exit_fail_msg("fopen /proc/self/smaps: %s\n", + strerror(errno)); + + while (fgets(line, sizeof(line), f)) { + if (!strncmp(line, "VmFlags:", 8) && strstr(line, " ss")) + vmas++; + } + fclose(f); + return vmas; +} + +/* + * CLONE_VM maps a new GCS. CLONE_PIDFD lets put_user() fail after that. + * Use a raw svc: the child resumes on an empty GCS, so a libc return + * after clone3 would fault; exit the same way. + */ +static long clone_pidfd(void *stack, size_t stack_size, int *pidfd_ptr) +{ + struct clone_args args = { + .flags = CLONE_VM | CLONE_PIDFD, + .pidfd = (uint64_t)(uintptr_t)pidfd_ptr, + .exit_signal = SIGCHLD, + .stack = (uint64_t)(uintptr_t)stack, + .stack_size = stack_size, + }; + register long num __asm__("x8") = __NR_clone3; + register long ret __asm__("x0") = (long)&args; + register long sz __asm__("x1") = sizeof(args); + int st; + + asm volatile("svc #0" + : "+r"(ret) + : "r"(num), "r"(sz) + : "memory", "cc"); + if (ret == 0) { + register long n __asm__("x8") = __NR_exit; + register long e __asm__("x0") = 0; + + asm volatile("svc #0" : : "r"(n), "r"(e) : "memory"); + __builtin_unreachable(); + } + if (ret < 0) + return ret; + if (waitpid((pid_t)ret, &st, 0) < 0) + return -errno; + if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) + return 1; + return 0; +} + +int main(void) +{ + void *stack; + void *bad_pidfd; + unsigned long gcs_mode; + long rc; + int pidfd = -1; + int vmas0, vmas1; + int i, n_efault = 0; + + ksft_print_header(); + + if (!(getauxval(AT_HWCAP) & HWCAP_GCS)) + ksft_exit_skip("GCS not supported\n"); + + stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) + ksft_exit_fail_msg("mmap stack: %s\n", strerror(errno)); + + bad_pidfd = mmap(NULL, 4096, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (bad_pidfd == MAP_FAILED) + ksft_exit_fail_msg("mmap pidfd page: %s\n", strerror(errno)); + + rc = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (rc) + ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc); + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) { + rc = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, + PR_SHADOW_STACK_ENABLE); + if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY) + ksft_exit_skip("cannot enable GCS: %ld\n", rc); + if (rc) + ksft_exit_fail_msg("PR_SET_SHADOW_STACK_STATUS: %ld\n", + rc); + } + + rc = clone_pidfd(stack, STACK_SIZE, &pidfd); + if (rc == -ENOSYS || rc == -EINVAL || rc == -EPERM) + ksft_exit_skip("clone3 valid pidfd unavailable: %s\n", + strerror(-rc)); + + ksft_set_plan(3); + + ksft_test_result(rc == 0 && pidfd >= 0, + "clone3 valid pidfd (rc=%ld pidfd=%d)\n", + rc, pidfd); + if (pidfd >= 0) + close(pidfd); + + vmas0 = count_ss_vmas(); + + for (i = 0; i < NCLONE; i++) { + rc = clone_pidfd(stack, STACK_SIZE, bad_pidfd); + if (rc == -EFAULT) + n_efault++; + } + ksft_test_result(n_efault == NCLONE, + "clone3 bad pidfd returns -EFAULT x%d\n", NCLONE); + + vmas1 = count_ss_vmas(); + ksft_test_result(vmas1 == vmas0, + "no GCS VMA leak (%d -> %d)\n", vmas0, vmas1); + + ksft_finished(); +} diff --git a/tools/testing/selftests/arm64/gcs/gcs-locking.c b/tools/testing/selftests/arm64/gcs/gcs-locking.c index 1e6abb136ffd..61789bdd2744 100644 --- a/tools/testing/selftests/arm64/gcs/gcs-locking.c +++ b/tools/testing/selftests/arm64/gcs/gcs-locking.c @@ -18,26 +18,6 @@ #include "gcs-util.h" -#define my_syscall2(num, arg1, arg2) \ -({ \ - register long _num __asm__ ("x8") = (num); \ - register long _arg1 __asm__ ("x0") = (long)(arg1); \ - register long _arg2 __asm__ ("x1") = (long)(arg2); \ - register long _arg3 __asm__ ("x2") = 0; \ - register long _arg4 __asm__ ("x3") = 0; \ - register long _arg5 __asm__ ("x4") = 0; \ - \ - __asm__ volatile ( \ - "svc #0\n" \ - : "=r"(_arg1) \ - : "r"(_arg1), "r"(_arg2), \ - "r"(_arg3), "r"(_arg4), \ - "r"(_arg5), "r"(_num) \ - : "memory", "cc" \ - ); \ - _arg1; \ -}) - /* No mode bits are rejected for locking */ TEST(lock_all_modes) { diff --git a/tools/testing/selftests/arm64/gcs/gcs-util.h b/tools/testing/selftests/arm64/gcs/gcs-util.h index 7a81bb07ed4b..46f6ed9cf54d 100644 --- a/tools/testing/selftests/arm64/gcs/gcs-util.h +++ b/tools/testing/selftests/arm64/gcs/gcs-util.h @@ -43,6 +43,30 @@ #define GCS_CAP(x) (((unsigned long)(x) & GCS_CAP_ADDR_MASK) | \ GCS_CAP_VALID_TOKEN) +/* + * prctl(..., arg1, arg2) with unused registers forced to zero. The + * kernel rejects non-zero unused args; libc may leave garbage there. + */ +#define my_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("x8") = (num); \ + register long _arg1 __asm__ ("x0") = (long)(arg1); \ + register long _arg2 __asm__ ("x1") = (long)(arg2); \ + register long _arg3 __asm__ ("x2") = 0; \ + register long _arg4 __asm__ ("x3") = 0; \ + register long _arg5 __asm__ ("x4") = 0; \ + \ + __asm__ volatile ( \ + "svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), \ + "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc" \ + ); \ + _arg1; \ +}) + static inline unsigned long *get_gcspr(void) { unsigned long *gcspr; diff --git a/tools/testing/selftests/arm64/gcs/libc-gcs.c b/tools/testing/selftests/arm64/gcs/libc-gcs.c index ddb0b2b20155..4e9fa9b82f0f 100644 --- a/tools/testing/selftests/arm64/gcs/libc-gcs.c +++ b/tools/testing/selftests/arm64/gcs/libc-gcs.c @@ -24,26 +24,6 @@ #include "gcs-util.h" -#define my_syscall2(num, arg1, arg2) \ -({ \ - register long _num __asm__ ("x8") = (num); \ - register long _arg1 __asm__ ("x0") = (long)(arg1); \ - register long _arg2 __asm__ ("x1") = (long)(arg2); \ - register long _arg3 __asm__ ("x2") = 0; \ - register long _arg4 __asm__ ("x3") = 0; \ - register long _arg5 __asm__ ("x4") = 0; \ - \ - __asm__ volatile ( \ - "svc #0\n" \ - : "=r"(_arg1) \ - : "r"(_arg1), "r"(_arg2), \ - "r"(_arg3), "r"(_arg4), \ - "r"(_arg5), "r"(_num) \ - : "memory", "cc" \ - ); \ - _arg1; \ -}) - static noinline void gcs_recurse(int depth) { if (depth) -- 2.43.0