* [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
clone3(CLONE_VM|CLONE_PIDFD) can fail in put_user() after copy_thread()
has mapped a shadow stack into the shared mm. copy_process() then calls
exit_thread(). arm64 and riscv did not implement it, so the VMA leaked.
x86 already frees via exit_thread() -> shstk_free(). Its separate TLS
leak is when copy_thread() fails after the alloc and skips exit_thread().
Changes since v2:
- Move gcs_reset_thread() into process.c (Mark Brown).
- Spell out the x86 vs arm64/riscv difference above.
v2: https://lore.kernel.org/all/cover.1792519121.git.ivanrwcm25@gmail.com/
Assisted-by: LLM
Iván Ezequiel Rodriguez (4):
arm64/gcs: release GCS on failed fork
riscv/shstk: release shadow stack on failed fork
selftests/arm64: GCS must not leak on a failed clone3
selftests/riscv: shadow stack must not leak on a failed clone3
arch/arm64/Kconfig | 1 +
arch/arm64/kernel/process.c | 16 ++
arch/riscv/Kconfig | 1 +
arch/riscv/kernel/process.c | 9 +
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 --
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
11 files changed, 398 insertions(+), 42 deletions(-)
create mode 100644 tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
--
2.43.0
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
clone3(CLONE_VM|CLONE_PIDFD) can fail in put_user() after copy_thread()
has mapped a shadow stack into the shared mm. copy_process() then calls
exit_thread(). arm64 and riscv did not implement it, so the VMA leaked.
x86 already frees via exit_thread() -> shstk_free(). Its separate TLS
leak is when copy_thread() fails after the alloc and skips exit_thread().
Changes since v2:
- Move gcs_reset_thread() into process.c (Mark Brown).
- Spell out the x86 vs arm64/riscv difference above.
v2: https://lore.kernel.org/all/cover.1792519121.git.ivanrwcm25@gmail.com/
Assisted-by: LLM
Iván Ezequiel Rodriguez (4):
arm64/gcs: release GCS on failed fork
riscv/shstk: release shadow stack on failed fork
selftests/arm64: GCS must not leak on a failed clone3
selftests/riscv: shadow stack must not leak on a failed clone3
arch/arm64/Kconfig | 1 +
arch/arm64/kernel/process.c | 16 ++
arch/riscv/Kconfig | 1 +
arch/riscv/kernel/process.c | 9 +
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 --
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
11 files changed, 398 insertions(+), 42 deletions(-)
create mode 100644 tools/testing/selftests/arm64/gcs/gcs-clone-pidfd.c
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
-1 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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), stable
copy_thread() can map a CLONE_VM GCS and still return 0. A later
copy_process() failure calls exit_thread(), but arm64 did not
implement it, so the VMA stayed in the parent's mm. Wire
HAVE_EXIT_THREAD to gcs_free().
x86 already does this via exit_thread() -> shstk_free(). The separate
x86 TLS leak is different: copy_thread() itself fails after the
alloc, so copy_process() skips exit_thread().
args->fn tasks inherit GCS tracking without allocating; clear it so
a failed CLONE_VM copy_process() cannot gcs_free() the caller's
stack. Keep that helper in process.c with the other fork helpers.
Fixes: 506496bcbb42 ("arm64/gcs: Ensure that new threads have a GCS")
Cc: stable@vger.kernel.org
Cc: Mark Brown <broonie@kernel.org>
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
arch/arm64/Kconfig | 1 +
arch/arm64/kernel/process.c | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index cfef22aee709..3010ced4b4f2 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -177,6 +177,7 @@ config ARM64
select HAVE_ARM_SMCCC
select HAVE_ASM_MODVERSIONS
select HAVE_EBPF_JIT
+ select HAVE_EXIT_THREAD
select HAVE_C_RECORDMCOUNT
select HAVE_CMPXCHG_DOUBLE
select HAVE_CMPXCHG_LOCAL
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 581f80e9b9b7..d17172e60acb 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -320,6 +320,13 @@ static int copy_thread_gcs(struct task_struct *p,
return 0;
}
+static void gcs_reset_thread(struct task_struct *task)
+{
+ task->thread.gcs_base = 0;
+ task->thread.gcs_size = 0;
+ task->thread.gcspr_el0 = 0;
+}
+
#else
static void flush_gcs(void) { }
@@ -328,6 +335,7 @@ static int copy_thread_gcs(struct task_struct *p,
{
return 0;
}
+static void gcs_reset_thread(struct task_struct *task) { }
#endif
@@ -341,6 +349,12 @@ void flush_thread(void)
flush_gcs();
}
+void exit_thread(struct task_struct *tsk)
+{
+ /* Failed fork after copy_thread(); deactivate_mm() does not run. */
+ gcs_free(tsk);
+}
+
void arch_release_task_struct(struct task_struct *tsk)
{
fpsimd_release_task(tsk);
@@ -503,6 +517,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
p->thread.cpu_context.x19 = (unsigned long)args->fn;
p->thread.cpu_context.x20 = (unsigned long)args->fn_arg;
+ /* No user GCS alloc here; drop inherited tracking for CLONE_VM. */
+ gcs_reset_thread(p);
if (system_supports_poe())
p->thread.por_el0 = POR_EL0_INIT;
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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), stable
copy_thread() can map a CLONE_VM GCS and still return 0. A later
copy_process() failure calls exit_thread(), but arm64 did not
implement it, so the VMA stayed in the parent's mm. Wire
HAVE_EXIT_THREAD to gcs_free().
x86 already does this via exit_thread() -> shstk_free(). The separate
x86 TLS leak is different: copy_thread() itself fails after the
alloc, so copy_process() skips exit_thread().
args->fn tasks inherit GCS tracking without allocating; clear it so
a failed CLONE_VM copy_process() cannot gcs_free() the caller's
stack. Keep that helper in process.c with the other fork helpers.
Fixes: 506496bcbb42 ("arm64/gcs: Ensure that new threads have a GCS")
Cc: stable@vger.kernel.org
Cc: Mark Brown <broonie@kernel.org>
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
arch/arm64/Kconfig | 1 +
arch/arm64/kernel/process.c | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index cfef22aee709..3010ced4b4f2 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -177,6 +177,7 @@ config ARM64
select HAVE_ARM_SMCCC
select HAVE_ASM_MODVERSIONS
select HAVE_EBPF_JIT
+ select HAVE_EXIT_THREAD
select HAVE_C_RECORDMCOUNT
select HAVE_CMPXCHG_DOUBLE
select HAVE_CMPXCHG_LOCAL
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 581f80e9b9b7..d17172e60acb 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -320,6 +320,13 @@ static int copy_thread_gcs(struct task_struct *p,
return 0;
}
+static void gcs_reset_thread(struct task_struct *task)
+{
+ task->thread.gcs_base = 0;
+ task->thread.gcs_size = 0;
+ task->thread.gcspr_el0 = 0;
+}
+
#else
static void flush_gcs(void) { }
@@ -328,6 +335,7 @@ static int copy_thread_gcs(struct task_struct *p,
{
return 0;
}
+static void gcs_reset_thread(struct task_struct *task) { }
#endif
@@ -341,6 +349,12 @@ void flush_thread(void)
flush_gcs();
}
+void exit_thread(struct task_struct *tsk)
+{
+ /* Failed fork after copy_thread(); deactivate_mm() does not run. */
+ gcs_free(tsk);
+}
+
void arch_release_task_struct(struct task_struct *tsk)
{
fpsimd_release_task(tsk);
@@ -503,6 +517,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
p->thread.cpu_context.x19 = (unsigned long)args->fn;
p->thread.cpu_context.x20 = (unsigned long)args->fn_arg;
+ /* No user GCS alloc here; drop inherited tracking for CLONE_VM. */
+ gcs_reset_thread(p);
if (system_supports_poe())
p->thread.por_el0 = POR_EL0_INIT;
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 2/4] riscv/shstk: release shadow stack on failed fork
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
-1 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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), stable
Same hole as arm64: copy_thread() maps a CLONE_VM shadow stack,
copy_process() fails later, exit_thread() ran but was never
implemented. Select HAVE_EXIT_THREAD and call shstk_release().
shstk_release() already documented this path; connect it. Clear
inherited tracking on the args->fn path for the same CLONE_VM
reason as arm64.
Fixes: fd44a4a85516 ("riscv/shstk: If needed allocate a new shadow stack on clone")
Cc: stable@vger.kernel.org
Cc: Deepak Gupta <debug@rivosinc.com>
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
arch/riscv/Kconfig | 1 +
arch/riscv/kernel/process.c | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 6c9d61d83d71..ba1ee563aa54 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -168,6 +168,7 @@ config RISCV
select HAVE_FUNCTION_GRAPH_FREGS
select HAVE_FUNCTION_TRACER if HAVE_DYNAMIC_FTRACE
select HAVE_EBPF_JIT if MMU
+ select HAVE_EXIT_THREAD
select HAVE_GENERIC_TIF_BITS
select HAVE_GUP_FAST if MMU
select HAVE_FUNCTION_ARG_ACCESS_API
diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
index 7cc5a6a5c020..5bb93377d67f 100644
--- a/arch/riscv/kernel/process.c
+++ b/arch/riscv/kernel/process.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
+#include <linux/sched/task.h>
#include <linux/sched/task_stack.h>
#include <linux/tick.h>
#include <linux/ptrace.h>
@@ -213,6 +214,12 @@ void arch_release_task_struct(struct task_struct *tsk)
riscv_v_thread_free(tsk);
}
+void exit_thread(struct task_struct *tsk)
+{
+ /* Failed fork after copy_thread(); deactivate_mm() does not run. */
+ shstk_release(tsk);
+}
+
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
{
fstate_save(src, task_pt_regs(src));
@@ -261,6 +268,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
p->thread.s[0] = (unsigned long)args->fn;
p->thread.s[1] = (unsigned long)args->fn_arg;
p->thread.ra = (unsigned long)ret_from_fork_kernel_asm;
+ /* No user shstk alloc here; drop inherited tracking for CLONE_VM. */
+ set_shstk_base(p, 0, 0);
} else {
/* allocate new shadow stack if needed. In case of CLONE_VM we have to */
ssp = shstk_alloc_thread_stack(p, args);
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 2/4] riscv/shstk: release shadow stack on failed fork
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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), stable
Same hole as arm64: copy_thread() maps a CLONE_VM shadow stack,
copy_process() fails later, exit_thread() ran but was never
implemented. Select HAVE_EXIT_THREAD and call shstk_release().
shstk_release() already documented this path; connect it. Clear
inherited tracking on the args->fn path for the same CLONE_VM
reason as arm64.
Fixes: fd44a4a85516 ("riscv/shstk: If needed allocate a new shadow stack on clone")
Cc: stable@vger.kernel.org
Cc: Deepak Gupta <debug@rivosinc.com>
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
arch/riscv/Kconfig | 1 +
arch/riscv/kernel/process.c | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 6c9d61d83d71..ba1ee563aa54 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -168,6 +168,7 @@ config RISCV
select HAVE_FUNCTION_GRAPH_FREGS
select HAVE_FUNCTION_TRACER if HAVE_DYNAMIC_FTRACE
select HAVE_EBPF_JIT if MMU
+ select HAVE_EXIT_THREAD
select HAVE_GENERIC_TIF_BITS
select HAVE_GUP_FAST if MMU
select HAVE_FUNCTION_ARG_ACCESS_API
diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
index 7cc5a6a5c020..5bb93377d67f 100644
--- a/arch/riscv/kernel/process.c
+++ b/arch/riscv/kernel/process.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
+#include <linux/sched/task.h>
#include <linux/sched/task_stack.h>
#include <linux/tick.h>
#include <linux/ptrace.h>
@@ -213,6 +214,12 @@ void arch_release_task_struct(struct task_struct *tsk)
riscv_v_thread_free(tsk);
}
+void exit_thread(struct task_struct *tsk)
+{
+ /* Failed fork after copy_thread(); deactivate_mm() does not run. */
+ shstk_release(tsk);
+}
+
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
{
fstate_save(src, task_pt_regs(src));
@@ -261,6 +268,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
p->thread.s[0] = (unsigned long)args->fn;
p->thread.s[1] = (unsigned long)args->fn_arg;
p->thread.ra = (unsigned long)ret_from_fork_kernel_asm;
+ /* No user shstk alloc here; drop inherited tracking for CLONE_VM. */
+ set_shstk_base(p, 0, 0);
} else {
/* allocate new shadow stack if needed. In case of CLONE_VM we have to */
ssp = shstk_alloc_thread_stack(p, args);
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
-1 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
KTAP coverage for clone3(CLONE_VM|CLONE_PIDFD) with a bad pidfd
pointer: sixteen failures must not grow the parent's GCS VMA count.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
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 <errno.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <asm/hwcap.h>
+
+#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 80a1317f6097..8877fd2d2eab 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
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
KTAP coverage for clone3(CLONE_VM|CLONE_PIDFD) with a bad pidfd
pointer: sixteen failures must not grow the parent's GCS VMA count.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
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 <errno.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <asm/hwcap.h>
+
+#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 80a1317f6097..8877fd2d2eab 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
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 4/4] selftests/riscv: shadow stack must not leak on a failed clone3
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
-1 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
Same clone3 bad-pidfd check as the arm64 GCS selftest.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
2 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile
index 418b4b5325a5..dbecb1fd3b82 100644
--- a/tools/testing/selftests/riscv/cfi/Makefile
+++ b/tools/testing/selftests/riscv/cfi/Makefile
@@ -13,10 +13,13 @@ endif
ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0)
-TEST_GEN_PROGS := cfitests
+TEST_GEN_PROGS := cfitests shstk-clone-pidfd
$(OUTPUT)/cfitests: cfitests.c shadowstack.c
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/shstk-clone-pidfd: shstk-clone-pidfd.c
+ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
else
$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2)
diff --git a/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
new file mode 100644
index 000000000000..1f16d9c513e6
--- /dev/null
+++ b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must
+ * not leak a shadow-stack 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 the shadow stack, do not return from main(): libc
+ * frames above that point have no shadow-stack entries.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest.h"
+#include "cfi_rv_test.h"
+
+#ifndef __NR_clone3
+#define __NR_clone3 435
+#endif
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#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;
+}
+
+/*
+ * Raw ecall: the child resumes on an empty shadow stack, so a libc
+ * return after clone3 would fault.
+ */
+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__("a7") = __NR_clone3;
+ register long ret __asm__("a0") = (long)&args;
+ register long sz __asm__("a1") = sizeof(args);
+ int st;
+
+ asm volatile("ecall"
+ : "+r"(ret)
+ : "r"(num), "r"(sz)
+ : "memory", "cc");
+ if (ret == 0) {
+ register long n __asm__("a7") = __NR_exit;
+ register long e __asm__("a0") = 0;
+
+ asm volatile("ecall" : : "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 ss_mode = 0;
+ long rc;
+ int pidfd = -1;
+ int vmas0, vmas1;
+ int i, n_efault = 0;
+
+ ksft_print_header();
+
+ 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_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_mode,
+ 0, 0, 0);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EOPNOTSUPP)
+ ksft_exit_skip("shadow stack not available: %ld\n", rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc);
+
+ if (!(ss_mode & PR_SHADOW_STACK_ENABLE)) {
+ rc = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+ PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+ if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY)
+ ksft_exit_skip("cannot enable shadow stack: %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 shadow-stack VMA leak (%d -> %d)\n",
+ vmas0, vmas1);
+
+ ksft_finished();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH v3 4/4] selftests/riscv: shadow stack must not leak on a failed clone3
@ 2026-10-28 15:43 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-28 15:43 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv
Cc: linux-kernel, linux-kselftest, 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)
Same clone3 bad-pidfd check as the arm64 GCS selftest.
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
tools/testing/selftests/riscv/cfi/Makefile | 5 +-
.../selftests/riscv/cfi/shstk-clone-pidfd.c | 173 ++++++++++++++++++
2 files changed, 177 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
diff --git a/tools/testing/selftests/riscv/cfi/Makefile b/tools/testing/selftests/riscv/cfi/Makefile
index 418b4b5325a5..dbecb1fd3b82 100644
--- a/tools/testing/selftests/riscv/cfi/Makefile
+++ b/tools/testing/selftests/riscv/cfi/Makefile
@@ -13,10 +13,13 @@ endif
ifeq ($(shell $(CC) $(CFLAGS) -nostdlib -xc /dev/null -o /dev/null > /dev/null 2>&1; echo $$?),0)
-TEST_GEN_PROGS := cfitests
+TEST_GEN_PROGS := cfitests shstk-clone-pidfd
$(OUTPUT)/cfitests: cfitests.c shadowstack.c
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
+
+$(OUTPUT)/shstk-clone-pidfd: shstk-clone-pidfd.c
+ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^
else
$(shell echo "Toolchain doesn't support CFI, skipping CFI kselftest." >&2)
diff --git a/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
new file mode 100644
index 000000000000..1f16d9c513e6
--- /dev/null
+++ b/tools/testing/selftests/riscv/cfi/shstk-clone-pidfd.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * clone3(CLONE_VM|CLONE_PIDFD) with an unwritable pidfd pointer must
+ * not leak a shadow-stack 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 the shadow stack, do not return from main(): libc
+ * frames above that point have no shadow-stack entries.
+ */
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "../../kselftest.h"
+#include "cfi_rv_test.h"
+
+#ifndef __NR_clone3
+#define __NR_clone3 435
+#endif
+
+#ifndef PR_GET_SHADOW_STACK_STATUS
+#define PR_GET_SHADOW_STACK_STATUS 74
+#endif
+#ifndef PR_SET_SHADOW_STACK_STATUS
+#define PR_SET_SHADOW_STACK_STATUS 75
+#endif
+#ifndef PR_SHADOW_STACK_ENABLE
+#define PR_SHADOW_STACK_ENABLE (1UL << 0)
+#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;
+}
+
+/*
+ * Raw ecall: the child resumes on an empty shadow stack, so a libc
+ * return after clone3 would fault.
+ */
+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__("a7") = __NR_clone3;
+ register long ret __asm__("a0") = (long)&args;
+ register long sz __asm__("a1") = sizeof(args);
+ int st;
+
+ asm volatile("ecall"
+ : "+r"(ret)
+ : "r"(num), "r"(sz)
+ : "memory", "cc");
+ if (ret == 0) {
+ register long n __asm__("a7") = __NR_exit;
+ register long e __asm__("a0") = 0;
+
+ asm volatile("ecall" : : "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 ss_mode = 0;
+ long rc;
+ int pidfd = -1;
+ int vmas0, vmas1;
+ int i, n_efault = 0;
+
+ ksft_print_header();
+
+ 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_syscall5(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &ss_mode,
+ 0, 0, 0);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EOPNOTSUPP)
+ ksft_exit_skip("shadow stack not available: %ld\n", rc);
+ if (rc)
+ ksft_exit_fail_msg("PR_GET_SHADOW_STACK_STATUS: %ld\n", rc);
+
+ if (!(ss_mode & PR_SHADOW_STACK_ENABLE)) {
+ rc = my_syscall5(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
+ PR_SHADOW_STACK_ENABLE, 0, 0, 0);
+ if (rc == -EOPNOTSUPP || rc == -EINVAL || rc == -EBUSY)
+ ksft_exit_skip("cannot enable shadow stack: %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 shadow-stack VMA leak (%d -> %d)\n",
+ vmas0, vmas1);
+
+ ksft_finished();
+}
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 18:31 ` Mark Brown
-1 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-28 18:31 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv, linux-kernel, linux-kselftest,
Mark Rutland, 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)
[-- Attachment #1: Type: text/plain, Size: 907 bytes --]
On Wed, Oct 28, 2026 at 12:43:14PM -0300, Iván Ezequiel Rodriguez wrote:
> KTAP coverage for clone3(CLONE_VM|CLONE_PIDFD) with a bad pidfd
> pointer: sixteen failures must not grow the parent's GCS VMA count.
Thanks, this is a lot cleaner and easier to understand than the original
version. It'd probably be nicer to do the refactoring of the existing
code in a separate commit but that's more nice to have than essential.
> +TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr \
> + gcs-clone-pidfd
We should update .gitignore as well (or merge the test into libc-gcs but
that's a bunch more work and definitely into bikeshed territory).
I verified that this fails with the current kernel and that the kernel
change causes it to pass so with the above:
Reviewed-by: Mark Brown <broonie@kernel.org>
Tested-by: Mark Brown <broonie@kernel.org>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3
@ 2026-10-28 18:31 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-28 18:31 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv, linux-kernel, linux-kselftest,
Mark Rutland, 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)
[-- Attachment #1.1: Type: text/plain, Size: 907 bytes --]
On Wed, Oct 28, 2026 at 12:43:14PM -0300, Iván Ezequiel Rodriguez wrote:
> KTAP coverage for clone3(CLONE_VM|CLONE_PIDFD) with a bad pidfd
> pointer: sixteen failures must not grow the parent's GCS VMA count.
Thanks, this is a lot cleaner and easier to understand than the original
version. It'd probably be nicer to do the refactoring of the existing
code in a separate commit but that's more nice to have than essential.
> +TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr \
> + gcs-clone-pidfd
We should update .gitignore as well (or merge the test into libc-gcs but
that's a bunch more work and definitely into bikeshed territory).
I verified that this fails with the current kernel and that the kernel
change causes it to pass so with the above:
Reviewed-by: Mark Brown <broonie@kernel.org>
Tested-by: Mark Brown <broonie@kernel.org>
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
[-- Attachment #2: Type: text/plain, Size: 161 bytes --]
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-28 18:41 ` Mark Brown
-1 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-28 18:41 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv, linux-kernel, linux-kselftest,
Mark Rutland, 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), stable
[-- Attachment #1: Type: text/plain, Size: 959 bytes --]
On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> copy_thread() can map a CLONE_VM GCS and still return 0. A later
> copy_process() failure calls exit_thread(), but arm64 did not
> implement it, so the VMA stayed in the parent's mm. Wire
> HAVE_EXIT_THREAD to gcs_free().
One extremely minor nit below, I verified that this fails with the
current kernel and that the kernel change causes it to pass so with or
without dealing with that:
Reviewed-by: Mark Brown <broonie@kernel.org>
Tested-by: Mark Brown <broonie@kernel.org>
> @@ -328,6 +335,7 @@ static int copy_thread_gcs(struct task_struct *p,
> {
> return 0;
> }
> +static void gcs_reset_thread(struct task_struct *task) { }
>
This looks like there's a missing blank line but it fits in with the
missing blank before the stub for copy_thread_gcs(). Possibly slightly
neater to put this before there next to the similar flush_gcs() stub but
meh.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
@ 2026-10-28 18:41 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-28 18:41 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Catalin Marinas, Will Deacon, Paul Walmsley, Palmer Dabbelt,
linux-arm-kernel, linux-riscv, linux-kernel, linux-kselftest,
Mark Rutland, 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), stable
[-- Attachment #1.1: Type: text/plain, Size: 959 bytes --]
On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> copy_thread() can map a CLONE_VM GCS and still return 0. A later
> copy_process() failure calls exit_thread(), but arm64 did not
> implement it, so the VMA stayed in the parent's mm. Wire
> HAVE_EXIT_THREAD to gcs_free().
One extremely minor nit below, I verified that this fails with the
current kernel and that the kernel change causes it to pass so with or
without dealing with that:
Reviewed-by: Mark Brown <broonie@kernel.org>
Tested-by: Mark Brown <broonie@kernel.org>
> @@ -328,6 +335,7 @@ static int copy_thread_gcs(struct task_struct *p,
> {
> return 0;
> }
> +static void gcs_reset_thread(struct task_struct *task) { }
>
This looks like there's a missing blank line but it fits in with the
missing blank before the stub for copy_thread_gcs(). Possibly slightly
neater to put this before there next to the similar flush_gcs() stub but
meh.
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
[-- Attachment #2: Type: text/plain, Size: 161 bytes --]
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-29 20:32 ` Catalin Marinas
-1 siblings, 0 replies; 22+ messages in thread
From: Catalin Marinas @ 2026-10-29 20:32 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Will Deacon, Paul Walmsley, Palmer Dabbelt, linux-arm-kernel,
linux-riscv, linux-kernel, linux-kselftest, 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), stable
On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> copy_thread() can map a CLONE_VM GCS and still return 0. A later
> copy_process() failure calls exit_thread(), but arm64 did not
> implement it, so the VMA stayed in the parent's mm. Wire
> HAVE_EXIT_THREAD to gcs_free().
>
> x86 already does this via exit_thread() -> shstk_free(). The separate
> x86 TLS leak is different: copy_thread() itself fails after the
> alloc, so copy_process() skips exit_thread().
Is the x86 TLS leak relevant to arm64?
[...]
> @@ -503,6 +517,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
>
> p->thread.cpu_context.x19 = (unsigned long)args->fn;
> p->thread.cpu_context.x20 = (unsigned long)args->fn_arg;
> + /* No user GCS alloc here; drop inherited tracking for CLONE_VM. */
> + gcs_reset_thread(p);
My LLM run noticed that this fixes another problem, unrelated to the
failed fork: io_uring and vhost workers are created with args->fn and
CLONE_VM and they inherit gcs_base/gcs_size from the creating task
through arch_dup_task_struct(). Without this reset, a worker exiting
normally unmaps the parent task's GCS.
I was wondering whether to have a separate one-line commit for the
worker thread case but I think I'll just add a comment in the commit
log, keep things simple.
--
Catalin
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
@ 2026-10-29 20:32 ` Catalin Marinas
0 siblings, 0 replies; 22+ messages in thread
From: Catalin Marinas @ 2026-10-29 20:32 UTC (permalink / raw)
To: Iván Ezequiel Rodriguez
Cc: Will Deacon, Paul Walmsley, Palmer Dabbelt, linux-arm-kernel,
linux-riscv, linux-kernel, linux-kselftest, 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), stable
On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> copy_thread() can map a CLONE_VM GCS and still return 0. A later
> copy_process() failure calls exit_thread(), but arm64 did not
> implement it, so the VMA stayed in the parent's mm. Wire
> HAVE_EXIT_THREAD to gcs_free().
>
> x86 already does this via exit_thread() -> shstk_free(). The separate
> x86 TLS leak is different: copy_thread() itself fails after the
> alloc, so copy_process() skips exit_thread().
Is the x86 TLS leak relevant to arm64?
[...]
> @@ -503,6 +517,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
>
> p->thread.cpu_context.x19 = (unsigned long)args->fn;
> p->thread.cpu_context.x20 = (unsigned long)args->fn_arg;
> + /* No user GCS alloc here; drop inherited tracking for CLONE_VM. */
> + gcs_reset_thread(p);
My LLM run noticed that this fixes another problem, unrelated to the
failed fork: io_uring and vhost workers are created with args->fn and
CLONE_VM and they inherit gcs_base/gcs_size from the creating task
through arch_dup_task_struct(). Without this reset, a worker exiting
normally unmaps the parent task's GCS.
I was wondering whether to have a separate one-line commit for the
worker thread case but I think I'll just add a comment in the commit
log, keep things simple.
--
Catalin
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: (subset) [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
@ 2026-10-29 20:57 ` Catalin Marinas
-1 siblings, 0 replies; 22+ messages in thread
From: Catalin Marinas @ 2026-10-29 20:57 UTC (permalink / raw)
To: Will Deacon, Paul Walmsley, Palmer Dabbelt, linux-arm-kernel,
linux-riscv, Iván Ezequiel Rodriguez
Cc: linux-kernel, linux-kselftest, 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)
On Wed, 28 Oct 2026 12:43:11 -0300, Iván Ezequiel Rodriguez wrote:
> clone3(CLONE_VM|CLONE_PIDFD) can fail in put_user() after copy_thread()
> has mapped a shadow stack into the shared mm. copy_process() then calls
> exit_thread(). arm64 and riscv did not implement it, so the VMA leaked.
>
> x86 already frees via exit_thread() -> shstk_free(). Its separate TLS
> leak is when copy_thread() fails after the alloc and skips exit_thread().
>
> [...]
Applied to arm64 (for-next/core), thanks!
Only the arm64 changes, riscv can go through the riscv tree. I changed
the commit log a bit on the first patch and addressed some nits Mark
raised. As a separate patch, it might be good to add a kselftest for the
io_uring worker thread and GCS.
[1/4] arm64/gcs: release GCS on failed fork
https://git.kernel.org/arm64/c/078e122f3bf0
[3/4] selftests/arm64: GCS must not leak on a failed clone3
https://git.kernel.org/arm64/c/f46be4da2cbb
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: (subset) [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork
@ 2026-10-29 20:57 ` Catalin Marinas
0 siblings, 0 replies; 22+ messages in thread
From: Catalin Marinas @ 2026-10-29 20:57 UTC (permalink / raw)
To: Will Deacon, Paul Walmsley, Palmer Dabbelt, linux-arm-kernel,
linux-riscv, Iván Ezequiel Rodriguez
Cc: linux-kernel, linux-kselftest, 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)
On Wed, 28 Oct 2026 12:43:11 -0300, Iván Ezequiel Rodriguez wrote:
> clone3(CLONE_VM|CLONE_PIDFD) can fail in put_user() after copy_thread()
> has mapped a shadow stack into the shared mm. copy_process() then calls
> exit_thread(). arm64 and riscv did not implement it, so the VMA leaked.
>
> x86 already frees via exit_thread() -> shstk_free(). Its separate TLS
> leak is when copy_thread() fails after the alloc and skips exit_thread().
>
> [...]
Applied to arm64 (for-next/core), thanks!
Only the arm64 changes, riscv can go through the riscv tree. I changed
the commit log a bit on the first patch and addressed some nits Mark
raised. As a separate patch, it might be good to add a kselftest for the
io_uring worker thread and GCS.
[1/4] arm64/gcs: release GCS on failed fork
https://git.kernel.org/arm64/c/078e122f3bf0
[3/4] selftests/arm64: GCS must not leak on a failed clone3
https://git.kernel.org/arm64/c/f46be4da2cbb
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
2026-10-29 20:32 ` Catalin Marinas
@ 2026-10-29 22:36 ` Mark Brown
-1 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-29 22:36 UTC (permalink / raw)
To: Catalin Marinas
Cc: Iván Ezequiel Rodriguez, Will Deacon, Paul Walmsley,
Palmer Dabbelt, linux-arm-kernel, linux-riscv, linux-kernel,
linux-kselftest, Mark Rutland, 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),
stable
[-- Attachment #1: Type: text/plain, Size: 472 bytes --]
On Thu, Oct 29, 2026 at 08:32:30PM +0000, Catalin Marinas wrote:
> On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> > x86 already does this via exit_thread() -> shstk_free(). The separate
> > x86 TLS leak is different: copy_thread() itself fails after the
> > alloc, so copy_process() skips exit_thread().
> Is the x86 TLS leak relevant to arm64?
That was in response to me querying if they were affected on prior
versions I think.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3 1/4] arm64/gcs: release GCS on failed fork
@ 2026-10-29 22:36 ` Mark Brown
0 siblings, 0 replies; 22+ messages in thread
From: Mark Brown @ 2026-10-29 22:36 UTC (permalink / raw)
To: Catalin Marinas
Cc: Iván Ezequiel Rodriguez, Will Deacon, Paul Walmsley,
Palmer Dabbelt, linux-arm-kernel, linux-riscv, linux-kernel,
linux-kselftest, Mark Rutland, 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),
stable
[-- Attachment #1.1: Type: text/plain, Size: 472 bytes --]
On Thu, Oct 29, 2026 at 08:32:30PM +0000, Catalin Marinas wrote:
> On Wed, Oct 28, 2026 at 12:43:12PM -0300, Iván Ezequiel Rodriguez wrote:
> > x86 already does this via exit_thread() -> shstk_free(). The separate
> > x86 TLS leak is different: copy_thread() itself fails after the
> > alloc, so copy_process() skips exit_thread().
> Is the x86 TLS leak relevant to arm64?
That was in response to me querying if they were affected on prior
versions I think.
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
[-- Attachment #2: Type: text/plain, Size: 161 bytes --]
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 0/1] selftests/arm64: GCS must survive io_uring SQPOLL worker exit
2026-10-29 20:57 ` Catalin Marinas
(?)
@ 2026-10-30 17:00 ` Iván Ezequiel Rodriguez
2026-10-30 17:00 ` [PATCH 1/1] " Iván Ezequiel Rodriguez
-1 siblings, 1 reply; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-30 17:00 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon
Cc: Mark Brown, Shuah Khan, Mark Rutland, linux-arm-kernel,
linux-kselftest, linux-kernel
As suggested, a separate kselftest for the io_uring/SQPOLL worker
path that gcs_reset_thread() also covers.
SQPOLL is create_io_thread() (args->fn + CLONE_VM). Without the reset,
worker exit unmaps the parent's GCS. The test waits for iou-sqp-* to
leave /proc/self/task after close (teardown is async), then checks the
parent ss VMA count.
QEMU virt/max: pass with the reset; SIGSEGV on an unpatched kernel.
Iván Ezequiel Rodriguez (1):
selftests/arm64: GCS must survive io_uring SQPOLL worker exit
tools/testing/selftests/arm64/gcs/Makefile | 2 +-
.../selftests/arm64/gcs/gcs-uring-worker.c | 254 ++++++++++++++++++
2 files changed, 255 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/arm64/gcs/gcs-uring-worker.c
--
2.43.0
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 1/1] selftests/arm64: GCS must survive io_uring SQPOLL worker exit
2026-10-30 17:00 ` [PATCH 0/1] selftests/arm64: GCS must survive io_uring SQPOLL worker exit Iván Ezequiel Rodriguez
@ 2026-10-30 17:00 ` Iván Ezequiel Rodriguez
0 siblings, 0 replies; 22+ messages in thread
From: Iván Ezequiel Rodriguez @ 2026-10-30 17:00 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon
Cc: Mark Brown, Shuah Khan, Mark Rutland, linux-arm-kernel,
linux-kselftest, linux-kernel
SQPOLL threads are create_io_thread() workers (args->fn + CLONE_VM).
They inherit GCS tracking via arch_dup_task_struct(); without
gcs_reset_thread() their exit runs gcs_free() on the parent's stack.
Create a SQPOLL ring, wait for iou-sqp-* to appear and leave
/proc/self/task (close only queues async teardown), then check the
parent ss VMA count is unchanged. QEMU: pass with the reset, SIGSEGV
without it.
Signed-off-by: Iván Ezequiel Rodriguez <ivanrwcm25@gmail.com>
---
tools/testing/selftests/arm64/gcs/Makefile | 2 +-
.../selftests/arm64/gcs/gcs-uring-worker.c | 254 ++++++++++++++++++
2 files changed, 255 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/arm64/gcs/gcs-uring-worker.c
diff --git a/tools/testing/selftests/arm64/gcs/Makefile b/tools/testing/selftests/arm64/gcs/Makefile
index f1365eea31c9..a427041f8cd1 100644
--- a/tools/testing/selftests/arm64/gcs/Makefile
+++ b/tools/testing/selftests/arm64/gcs/Makefile
@@ -7,7 +7,7 @@
#
TEST_GEN_PROGS := basic-gcs libc-gcs gcs-locking gcs-stress gcspushm gcsstr \
- gcs-clone-pidfd
+ gcs-clone-pidfd gcs-uring-worker
TEST_GEN_PROGS_EXTENDED := gcs-stress-thread
LDLIBS+=-lpthread
diff --git a/tools/testing/selftests/arm64/gcs/gcs-uring-worker.c b/tools/testing/selftests/arm64/gcs/gcs-uring-worker.c
new file mode 100644
index 000000000000..737e1a14177b
--- /dev/null
+++ b/tools/testing/selftests/arm64/gcs/gcs-uring-worker.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * io_uring SQPOLL uses create_io_thread() (args->fn + CLONE_VM) and
+ * inherits GCS tracking via arch_dup_task_struct(). Without
+ * gcs_reset_thread() on that path, worker exit runs deactivate_mm()
+ * -> gcs_free() and unmaps the parent's GCS.
+ *
+ * close(2) on the ring only queues teardown; wait for the iou-sqp-*
+ * thread to leave /proc/self/task so gcs_free() has finished before
+ * counting VMAs.
+ *
+ * After enabling GCS, do not return from main() through libc frames
+ * that were pushed before enable.
+ */
+#define _GNU_SOURCE
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/io_uring.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <asm/hwcap.h>
+
+#include "kselftest.h"
+#include "gcs-util.h"
+
+#ifndef HWCAP_GCS
+#define HWCAP_GCS (1UL << 32)
+#endif
+
+#ifndef __NR_io_uring_setup
+#define __NR_io_uring_setup 425
+#endif
+#ifndef __NR_io_uring_enter
+#define __NR_io_uring_enter 426
+#endif
+
+#define SQPOLL_WAIT_MS 5000
+
+struct uring {
+ int fd;
+ struct io_uring_params params;
+ void *sq;
+ size_t sq_len;
+ struct io_uring_sqe *sqes;
+ size_t sqes_len;
+};
+
+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;
+}
+
+static bool has_iou_sqp(void)
+{
+ DIR *d;
+ struct dirent *de;
+ char path[96], comm[32];
+ int fd, n;
+ bool found = false;
+
+ d = opendir("/proc/self/task");
+ if (!d)
+ return false;
+
+ while ((de = readdir(d))) {
+ if (de->d_name[0] < '1' || de->d_name[0] > '9')
+ continue;
+ if (snprintf(path, sizeof(path), "/proc/self/task/%s/comm",
+ de->d_name) >= (int)sizeof(path))
+ continue;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ continue;
+ n = read(fd, comm, sizeof(comm) - 1);
+ close(fd);
+ if (n <= 0)
+ continue;
+ comm[n] = '\0';
+ if (!strncmp(comm, "iou-sqp-", 8)) {
+ found = true;
+ break;
+ }
+ }
+ closedir(d);
+ return found;
+}
+
+static int wait_iou_sqp(bool want, int timeout_ms)
+{
+ int slept = 0;
+
+ while (has_iou_sqp() != want) {
+ if (slept >= timeout_ms)
+ return -ETIMEDOUT;
+ usleep(1000);
+ slept++;
+ }
+ return 0;
+}
+
+static void uring_close(struct uring *r)
+{
+ if (r->sqes && r->sqes != MAP_FAILED) {
+ munmap(r->sqes, r->sqes_len);
+ r->sqes = MAP_FAILED;
+ }
+ if (r->sq && r->sq != MAP_FAILED) {
+ munmap(r->sq, r->sq_len);
+ r->sq = MAP_FAILED;
+ }
+ if (r->fd >= 0) {
+ close(r->fd);
+ r->fd = -1;
+ }
+}
+
+static int uring_sqpoll_open(struct uring *r)
+{
+ size_t cq_len;
+ int ret;
+
+ memset(r, 0, sizeof(*r));
+ r->fd = -1;
+ r->sq = MAP_FAILED;
+ r->sqes = MAP_FAILED;
+
+ r->params.flags = IORING_SETUP_SQPOLL;
+ r->params.sq_thread_idle = 100;
+ r->fd = syscall(__NR_io_uring_setup, 8, &r->params);
+ if (r->fd < 0)
+ return -errno;
+
+ if (!(r->params.features & IORING_FEAT_SINGLE_MMAP)) {
+ ret = -EOPNOTSUPP;
+ goto err;
+ }
+
+ r->sq_len = r->params.sq_off.array +
+ r->params.sq_entries * sizeof(unsigned int);
+ cq_len = r->params.cq_off.cqes +
+ r->params.cq_entries * sizeof(struct io_uring_cqe);
+ if (cq_len > r->sq_len)
+ r->sq_len = cq_len;
+
+ r->sq = mmap(NULL, r->sq_len, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQ_RING);
+ if (r->sq == MAP_FAILED) {
+ ret = -errno;
+ goto err;
+ }
+
+ r->sqes_len = r->params.sq_entries * sizeof(struct io_uring_sqe);
+ r->sqes = mmap(NULL, r->sqes_len, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQES);
+ if (r->sqes == MAP_FAILED) {
+ ret = -errno;
+ goto err;
+ }
+
+ return 0;
+err:
+ uring_close(r);
+ return ret;
+}
+
+int main(void)
+{
+ struct uring ring;
+ unsigned long gcs_mode;
+ long rc;
+ int vmas0, vmas1;
+
+ ksft_print_header();
+
+ if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
+ ksft_exit_skip("GCS not supported\n");
+
+ 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 = uring_sqpoll_open(&ring);
+ if (rc == -ENOSYS || rc == -EINVAL || rc == -EPERM ||
+ rc == -EOPNOTSUPP || rc == -EACCES)
+ ksft_exit_skip("io_uring SQPOLL unavailable: %s\n",
+ strerror(-rc));
+ if (rc)
+ ksft_exit_fail_msg("io_uring_setup SQPOLL: %s\n",
+ strerror(-rc));
+
+ rc = syscall(__NR_io_uring_enter, ring.fd, 0, 0,
+ IORING_ENTER_SQ_WAKEUP, NULL, 0);
+ if (rc < 0)
+ ksft_exit_fail_msg("io_uring_enter SQ_WAKEUP: %s\n",
+ strerror(errno));
+
+ if (wait_iou_sqp(true, SQPOLL_WAIT_MS)) {
+ uring_close(&ring);
+ ksft_exit_fail_msg("SQPOLL thread did not appear\n");
+ }
+
+ vmas0 = count_ss_vmas();
+ if (vmas0 < 1) {
+ uring_close(&ring);
+ ksft_exit_fail_msg("expected a GCS VMA before worker exit\n");
+ }
+
+ ksft_set_plan(1);
+
+ uring_close(&ring);
+
+ if (wait_iou_sqp(false, SQPOLL_WAIT_MS))
+ ksft_exit_fail_msg("SQPOLL thread did not exit\n");
+
+ vmas1 = count_ss_vmas();
+ ksft_test_result(vmas1 == vmas0,
+ "parent GCS VMA survives SQPOLL exit (%d -> %d)\n",
+ vmas0, vmas1);
+
+ ksft_finished();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-10-30 17:01 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 15:43 [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 15:43 ` [PATCH v3 1/4] arm64/gcs: release GCS " Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 18:41 ` Mark Brown
2026-10-28 18:41 ` Mark Brown
2026-10-29 20:32 ` Catalin Marinas
2026-10-29 20:32 ` Catalin Marinas
2026-10-29 22:36 ` Mark Brown
2026-10-29 22:36 ` Mark Brown
2026-10-28 15:43 ` [PATCH v3 2/4] riscv/shstk: release shadow stack " Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 15:43 ` [PATCH v3 3/4] selftests/arm64: GCS must not leak on a failed clone3 Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-28 18:31 ` Mark Brown
2026-10-28 18:31 ` Mark Brown
2026-10-28 15:43 ` [PATCH v3 4/4] selftests/riscv: shadow stack " Iván Ezequiel Rodriguez
2026-10-28 15:43 ` Iván Ezequiel Rodriguez
2026-10-29 20:57 ` (subset) [PATCH v3 0/4] arm64, riscv: release shadow stacks on failed fork Catalin Marinas
2026-10-29 20:57 ` Catalin Marinas
2026-10-30 17:00 ` [PATCH 0/1] selftests/arm64: GCS must survive io_uring SQPOLL worker exit Iván Ezequiel Rodriguez
2026-10-30 17:00 ` [PATCH 1/1] " Iván Ezequiel Rodriguez
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.