From 0f906993b8b17088af06e4b25ee74c2c158bc24c Mon Sep 17 00:00:00 2001 From: "yang.zhang" Date: Sun, 3 May 2026 08:58:55 +0800 Subject: [PATCH] lwp: fix pid leak on exec failure before task startup When msh tries to execute a non-ELF path, lwp_execve() may allocate a PID before lwp_load() fails. The old error path only dropped the LWP reference, leaving the PID tree entry pointing to a freed LWP. In an init-less boot flow, this can poison pid 1 after a failed command from msh. A later LWP launch may then treat the stale pid 1 entry as a valid parent LWP, resulting in invalid pgrp/session state and a job-control assertion during process exit. Add lwp_pid_rollback() for exec/spawn failures before the process becomes runnable. Unlike lwp_pid_put(), it always releases the PID lock and does not enter the "no more pid allocation" state when the PID tree becomes empty. Use the rollback helper in lwp_execve() failure paths after PID allocation. Signed-off-by: zhangyang --- components/lwp/lwp.c | 8 ++++---- components/lwp/lwp_pid.c | 29 +++++++++++++++++++++++++++++ components/lwp/lwp_pid.h | 1 + 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/components/lwp/lwp.c b/components/lwp/lwp.c index 4e589788993..f3841bb4c10 100644 --- a/components/lwp/lwp.c +++ b/components/lwp/lwp.c @@ -514,14 +514,14 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) if ((tid = lwp_tid_get()) == 0) { - lwp_ref_dec(lwp); + lwp_pid_rollback(lwp); return -ENOMEM; } #ifdef ARCH_MM_MMU if (lwp_user_space_init(lwp, 0) != 0) { lwp_tid_put(tid); - lwp_ref_dec(lwp); + lwp_pid_rollback(lwp); return -ENOMEM; } #endif @@ -529,7 +529,7 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) if ((aux = argscopy(lwp, argc, argv, envp)) == RT_NULL) { lwp_tid_put(tid); - lwp_ref_dec(lwp); + lwp_pid_rollback(lwp); return -ENOMEM; } @@ -629,7 +629,7 @@ pid_t lwp_execve(char *filename, int debug, int argc, char **argv, char **envp) } lwp_tid_put(tid); - lwp_ref_dec(lwp); + lwp_pid_rollback(lwp); return -RT_ERROR; } diff --git a/components/lwp/lwp_pid.c b/components/lwp/lwp_pid.c index c992f1da8c3..82f205a694e 100644 --- a/components/lwp/lwp_pid.c +++ b/components/lwp/lwp_pid.c @@ -335,6 +335,35 @@ void lwp_pid_put(struct rt_lwp *lwp) lwp_ref_dec(lwp); } +/** + * @brief Roll back a PID allocated for a process that never became runnable. + * + * @param[in,out] lwp The lightweight process whose PID allocation should be + * undone. The LWP must have been allocated a PID, but must + * not have been made visible as a runnable user task. + * + * @note This helper is intended for exec/spawn failure paths after PID + * allocation and before task startup. It removes the PID table entry, + * clears lwp->pid, and drops the initial LWP reference. + * + * Do not use lwp_pid_put() for this case. lwp_pid_put() has process-exit + * semantics: when the PID tree becomes empty it wakes waiters and keeps + * the PID lock held to prevent new PID allocation. A failed exec rollback + * must release the PID lock unconditionally so later LWP launches can + * still allocate PIDs, especially in init-less boot flows. + */ +void lwp_pid_rollback(struct rt_lwp *lwp) +{ + _free_proc_dentry(lwp); + + lwp_pid_lock_take(); + lwp_pid_put_locked(lwp->pid); + lwp_pid_lock_release(); + + lwp->pid = 0; + lwp_ref_dec(lwp); +} + /** * @brief Set the LWP for a given PID while holding the PID lock * diff --git a/components/lwp/lwp_pid.h b/components/lwp/lwp_pid.h index 4901fd4392d..5b85b321977 100644 --- a/components/lwp/lwp_pid.h +++ b/components/lwp/lwp_pid.h @@ -29,6 +29,7 @@ int lwp_pid_init(void); int lwp_pid_wait_for_empty(int wait_flags, rt_tick_t to); int lwp_pid_for_each(int (*cb)(pid_t pid, void *data), void *data); void lwp_pid_put(struct rt_lwp *lwp); +void lwp_pid_rollback(struct rt_lwp *lwp); void lwp_pid_lock_take(void); void lwp_pid_lock_release(void);