From 026cfde023e3fae85d12e414b78b9059437e303e Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 10 Mar 2026 09:57:18 -0600 Subject: [PATCH] Fix Linux tmux segfault in user shell lookup (#13900) Replace the Unix shell lookup path in `codex-rs/core/src/shell.rs` to use `libc::getpwuid_r()` instead of `libc::getpwuid()` when resolving the current user's shell. Why: - `getpwuid()` can return pointers into libc-managed shared storage - on the musl static Linux build, concurrent callers can race on that storage - this matches the crash pattern reported in tmux/Linux sessions with parallel shell activity Refs: - Fixes #13842 --- codex-rs/core/src/shell.rs | 62 +++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/src/shell.rs b/codex-rs/core/src/shell.rs index cec5d8a93..4cd728992 100644 --- a/codex-rs/core/src/shell.rs +++ b/codex-rs/core/src/shell.rs @@ -90,22 +90,62 @@ impl Eq for Shell {} #[cfg(unix)] fn get_user_shell_path() -> Option { - use libc::getpwuid; - use libc::getuid; + let uid = unsafe { libc::getuid() }; use std::ffi::CStr; + use std::mem::MaybeUninit; + use std::ptr; - unsafe { - let uid = getuid(); - let pw = getpwuid(uid); + let mut passwd = MaybeUninit::::uninit(); - if !pw.is_null() { - let shell_path = CStr::from_ptr((*pw).pw_shell) + // We cannot use getpwuid here: it returns pointers into libc-managed + // storage, which is not safe to read concurrently on all targets (the musl + // static build used by the CLI can segfault when parallel callers race on + // that buffer). getpwuid_r keeps the passwd data in caller-owned memory. + let suggested_buffer_len = unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) }; + let buffer_len = usize::try_from(suggested_buffer_len) + .ok() + .filter(|len| *len > 0) + .unwrap_or(1024); + let mut buffer = vec![0; buffer_len]; + + loop { + let mut result = ptr::null_mut(); + let status = unsafe { + libc::getpwuid_r( + uid, + passwd.as_mut_ptr(), + buffer.as_mut_ptr().cast(), + buffer.len(), + &mut result, + ) + }; + + if status == 0 { + if result.is_null() { + return None; + } + + let passwd = unsafe { passwd.assume_init_ref() }; + if passwd.pw_shell.is_null() { + return None; + } + + let shell_path = unsafe { CStr::from_ptr(passwd.pw_shell) } .to_string_lossy() .into_owned(); - Some(PathBuf::from(shell_path)) - } else { - None + return Some(PathBuf::from(shell_path)); } + + if status != libc::ERANGE { + return None; + } + + // Retry with a larger buffer until libc can materialize the passwd entry. + let new_len = buffer.len().checked_mul(2)?; + if new_len > 1024 * 1024 { + return None; + } + buffer.resize(new_len, 0); } } @@ -500,7 +540,7 @@ mod tests { } #[test] - fn finds_poweshell() { + fn finds_powershell() { if !cfg!(windows) { return; }