copy_thread() maps a shadow stack for a CLONE_VM child and can still
fail in set_new_tls(). That error returns through bad_fork_cleanup_io,
which does not call exit_thread(). bad_fork_cleanup_thread does, but
only after copy_thread() has returned 0.

commit 748c90c69336 ("x86/shstk: Remove useless clone error handling")
removed the shstk_free() on this path, assuming exit_thread() would
run. It does not. Each rejected clone leaves one shadow-stack VMA in
the parent's mm until the mm dies: one map_count slot, pages not
populated. The size follows the clone stack, or RLIMIT_STACK when
that size is zero.

Free only after set_new_tls() fails. The allocation error returns
earlier, while the child's base still aliases the parent.
shstk_free() ignores a cleared base and a different mm, so
CLONE_VFORK and !CLONE_VM stay no-ops. The exit_thread() path is
left alone, so the double unmap that commit avoided does not come
back. The io bitmap is still shared only when TLS setup succeeds.

Tested with user shadow stack enabled, on one config with and without
this change. Sixteen clone3() calls with CLONE_VM|CLONE_SETTLS and an
out-of-range TLS leaked 16 shadow-stack VMAs without the fix. With it,
the VMA count was unchanged and call/return on the parent still used
the original shadow stack.

Fixes: 748c90c69336 ("x86/shstk: Remove useless clone error handling")
Cc: [email protected]
Assisted-by: LLM
Signed-off-by: Iván Ezequiel Rodriguez <[email protected]>
---
 arch/x86/kernel/process.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c
index 346c438ac880..be840652d055 100644
--- a/arch/x86/kernel/process.c
+++ b/arch/x86/kernel/process.c
@@ -265,11 +265,19 @@ int copy_thread(struct task_struct *p, const struct 
kernel_clone_args *args)
        /* Set a new TLS for the child thread? */
        if (clone_flags & CLONE_SETTLS)
                ret = set_new_tls(p, tls);
+       if (ret) {
+               /*
+                * copy_process() skips exit_thread() when copy_thread()
+                * fails. Free the shadow stack mapped before this error.
+                */
+               shstk_free(p);
+               return ret;
+       }
 
-       if (!ret && unlikely(test_tsk_thread_flag(current, TIF_IO_BITMAP)))
+       if (unlikely(test_tsk_thread_flag(current, TIF_IO_BITMAP)))
                io_bitmap_share(p);
 
-       return ret;
+       return 0;
 }
 
 static void pkru_flush_thread(void)
-- 
2.43.0


Reply via email to