Skip to content

Commit 3c57ab6

Browse files
pam: Fork and switch UID before connecting to daemon
When gdm-session-worker (running as root) connects to the PAM socket, namespace isolation causes peer credentials to show UID 65534 instead of the actual UID, breaking authentication gnome-keyring solves this by forking a child process and using setuid()/setgid() to switch to the target user's credentials before connecting
1 parent 30ff878 commit 3c57ab6

3 files changed

Lines changed: 115 additions & 22 deletions

File tree

pam/src/lib.rs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ unsafe fn get_auth_token_internal(
8585
Ok(Zeroizing::new(password_bytes))
8686
}
8787

88-
/// Get the UID for a username using getpwnam (handles NSS/LDAP/etc)
89-
fn get_user_uid(username: &str) -> Option<u32> {
88+
/// Get the UID and GID for a username using getpwnam (handles NSS/LDAP/etc)
89+
fn get_user_credentials(username: &str) -> Option<(u32, u32)> {
9090
use std::ffi::CString;
9191

9292
let username_c = CString::new(username).ok()?;
@@ -95,7 +95,7 @@ fn get_user_uid(username: &str) -> Option<u32> {
9595
if pwd.is_null() {
9696
None
9797
} else {
98-
Some(unsafe { (*pwd).pw_uid })
98+
Some(unsafe { ((*pwd).pw_uid, (*pwd).pw_gid) })
9999
}
100100
}
101101

@@ -266,19 +266,19 @@ pub unsafe extern "C" fn pam_sm_open_session(
266266
}
267267
};
268268

269-
let user_uid = match get_user_uid(&username) {
270-
Some(uid) => uid,
269+
let (user_uid, user_gid) = match get_user_credentials(&username) {
270+
Some(creds) => creds,
271271
None => {
272-
tracing::error!("Failed to get UID for user: {}", username);
272+
tracing::error!("Failed to get credentials for user: {}", username);
273273
return PAM_SUCCESS;
274274
}
275275
};
276276

277277
let message = PamMessage::unlock(username.clone(), password.to_vec());
278278

279279
// Send the secret to the oo7 daemon
280-
std::thread::spawn(
281-
move || match send_secret_to_daemon(message, user_uid, auto_start) {
280+
std::thread::spawn(move || {
281+
match send_secret_to_daemon(message, user_uid, user_gid, auto_start) {
282282
Ok(_) => {
283283
tracing::info!(
284284
"Successfully sent secret to oo7 daemon for user: {}",
@@ -288,8 +288,8 @@ pub unsafe extern "C" fn pam_sm_open_session(
288288
Err(e) => {
289289
tracing::error!("Failed to send secret to oo7 daemon: {}", e);
290290
}
291-
},
292-
);
291+
}
292+
});
293293

294294
PAM_SUCCESS
295295
}
@@ -336,10 +336,10 @@ pub unsafe extern "C" fn pam_sm_chauthtok(
336336
}
337337
};
338338

339-
let user_uid = match get_user_uid(&username) {
340-
Some(uid) => uid,
339+
let (user_uid, user_gid) = match get_user_credentials(&username) {
340+
Some(creds) => creds,
341341
None => {
342-
tracing::error!("Failed to get UID for user: {}", username);
342+
tracing::error!("Failed to get credentials for user: {}", username);
343343
return PAM_SYSTEM_ERR;
344344
}
345345
};
@@ -384,8 +384,8 @@ pub unsafe extern "C" fn pam_sm_chauthtok(
384384
new_password.to_vec(),
385385
);
386386

387-
std::thread::spawn(
388-
move || match send_secret_to_daemon(message, user_uid, false) {
387+
std::thread::spawn(move || {
388+
match send_secret_to_daemon(message, user_uid, user_gid, false) {
389389
Ok(_) => {
390390
tracing::info!(
391391
"Successfully sent password change request to oo7 daemon for user: {}",
@@ -395,8 +395,8 @@ pub unsafe extern "C" fn pam_sm_chauthtok(
395395
Err(e) => {
396396
tracing::error!("Failed to send password change to oo7 daemon: {}", e);
397397
}
398-
},
399-
);
398+
}
399+
});
400400

401401
return PAM_SUCCESS;
402402
}

pam/src/socket.rs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,105 @@ impl std::error::Error for SocketError {
4141
pub fn send_secret_to_daemon(
4242
message: PamMessage,
4343
uid: u32,
44+
gid: u32,
4445
auto_start: bool,
4546
) -> Result<(), SocketError> {
46-
let runtime = tokio::runtime::Runtime::new().map_err(SocketError::Connect)?;
47+
// Check if we're already running as the target user
48+
let current_uid = unsafe { libc::getuid() };
49+
let current_gid = unsafe { libc::getgid() };
50+
let current_euid = unsafe { libc::geteuid() };
51+
let current_egid = unsafe { libc::getegid() };
52+
53+
if uid == current_uid && gid == current_gid && uid == current_euid && gid == current_egid {
54+
tracing::debug!("Already running as target user (UID={uid}, GID={gid})",);
55+
let runtime = tokio::runtime::Runtime::new().map_err(SocketError::Connect)?;
56+
return runtime
57+
.block_on(async { send_secret_to_daemon_async(message, uid, auto_start).await });
58+
}
59+
60+
// Need to fork and switch credentials
61+
tracing::debug!(
62+
"Running as different user (current UID={current_uid}, target UID={uid}), forking to switch credentials"
63+
);
64+
65+
match unsafe { libc::fork() } {
66+
-1 => {
67+
tracing::error!("Failed to fork process for credential switch");
68+
Err(SocketError::Connect(io::Error::last_os_error()))
69+
}
70+
0 => {
71+
unsafe {
72+
if libc::setgid(gid) < 0
73+
|| libc::setuid(uid) < 0
74+
|| libc::setegid(gid) < 0
75+
|| libc::seteuid(uid) < 0
76+
{
77+
tracing::error!(
78+
"Failed to switch to user credentials (UID={uid}, GID={gid}): {}",
79+
io::Error::last_os_error()
80+
);
81+
libc::_exit(1);
82+
}
83+
}
84+
85+
tracing::debug!("Child process switched to UID={uid}, GID={gid}");
86+
87+
let runtime = match tokio::runtime::Runtime::new() {
88+
Ok(rt) => rt,
89+
Err(e) => {
90+
tracing::error!("Failed to create tokio runtime in child: {e}",);
91+
unsafe { libc::_exit(1) };
92+
}
93+
};
4794

48-
runtime.block_on(async { send_secret_to_daemon_async(message, uid, auto_start).await })
95+
let result = runtime
96+
.block_on(async { send_secret_to_daemon_async(message, uid, auto_start).await });
97+
98+
match result {
99+
Ok(_) => unsafe { libc::_exit(0) },
100+
Err(e) => {
101+
tracing::error!("Failed to send message in child process: {e}",);
102+
unsafe { libc::_exit(1) }
103+
}
104+
}
105+
}
106+
child_pid => {
107+
// Parent process - wait for child to complete
108+
tracing::debug!("Forked child process with PID {child_pid}",);
109+
let mut status: libc::c_int = 0;
110+
111+
loop {
112+
let wait_result = unsafe { libc::waitpid(child_pid, &mut status, 0) };
113+
if wait_result == child_pid {
114+
break;
115+
} else if wait_result == -1 {
116+
let err = io::Error::last_os_error();
117+
if err.kind() != io::ErrorKind::Interrupted {
118+
tracing::error!("Failed to wait for child process: {err}",);
119+
return Err(SocketError::Connect(err));
120+
}
121+
}
122+
}
123+
124+
if libc::WIFEXITED(status) {
125+
let exit_code = libc::WEXITSTATUS(status);
126+
if exit_code == 0 {
127+
tracing::debug!("Child process completed successfully");
128+
Ok(())
129+
} else {
130+
tracing::error!("Child process exited with code {exit_code}");
131+
Err(SocketError::Connect(io::Error::other(format!(
132+
"Child process failed with exit code {exit_code}"
133+
))))
134+
}
135+
} else {
136+
tracing::error!("Child process terminated abnormally");
137+
Err(SocketError::Connect(io::Error::other(
138+
"Child process terminated abnormally",
139+
)))
140+
}
141+
}
142+
}
49143
}
50144

51145
/// Start the oo7-daemon for the current user

server/src/pam_listener.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ impl PamListener {
103103

104104
/// Handle a single PAM connection
105105
async fn handle_connection(&self, mut stream: UnixStream) -> Result<(), Error> {
106-
tracing::debug!("Accepted PAM connection");
107-
108106
// Accept connections from:
109107
// 1. Root (UID 0) as PAM modules run as root during authentication
110108
// 2. Same UID as us
@@ -114,8 +112,9 @@ impl PamListener {
114112

115113
if peer_uid != 0 && peer_uid != our_uid {
116114
tracing::warn!(
117-
"Rejected PAM connection from UID {} (expected 0 or {})",
115+
"Rejected PAM connection from UID {} PID {} (expected UID 0 or {})",
118116
peer_uid,
117+
peer_cred.pid().unwrap_or(0),
119118
our_uid
120119
);
121120
return Err(Error::IO(std::io::Error::new(

0 commit comments

Comments
 (0)