Skip to content

Commit 4e7ce2f

Browse files
committed
Rework setting of user and group ID.
1 parent 737bd3e commit 4e7ce2f

File tree

1 file changed

+142
-26
lines changed

1 file changed

+142
-26
lines changed

src/process.rs

Lines changed: 142 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub use self::noop::{Args, Config, Process};
1515
mod unix {
1616
use std::io;
1717
use std::env::set_current_dir;
18+
use std::ffi::{CStr, CString};
1819
use std::fs::{File, OpenOptions};
1920
use std::io::Write;
2021
use std::os::fd::{AsFd, AsRawFd};
@@ -27,7 +28,7 @@ mod unix {
2728
use nix::sys::stat::umask;
2829
use nix::unistd::{Gid, Group, Uid, User};
2930
use nix::unistd::{
30-
close, chroot, dup2, fork, getpid, setgid, setsid, setuid,
31+
close, chroot, dup2, fork, getpid, setsid,
3132
};
3233
use serde::{Deserialize, Serialize};
3334
use crate::config::{ConfigFile, ConfigPath};
@@ -143,27 +144,127 @@ mod unix {
143144
}
144145
}
145146

146-
if let Some(user) = self.config.user.as_ref() {
147-
if let Err(err) = setuid(user.uid) {
147+
self.set_user_and_group()?;
148+
149+
self.write_pid_file()?;
150+
151+
Ok(())
152+
}
153+
154+
/// Changes the user and group IDs.
155+
fn set_user_and_group(&self) -> Result<(), Failed> {
156+
// Unfortunately, this isn’t quite as portable as we want it to
157+
// be as most of the function we use are not available on some
158+
// platforms. Instead of copying the cfg attributes from the nix
159+
// crate, we define fallback functions and overwrite their symbol
160+
// if possible using a glob import.
161+
//
162+
// For setting uid and gid, we need to cascase: Use `setresuid`
163+
// if available, otherwise use `setreuid` if available, otherwise
164+
// use `setuid`; analogous for gid. We achieve this by having
165+
// the fallback call the next step which may itself be a fallback.
166+
167+
/// Dummy fallback function for `nix::unistd::initgroups`.
168+
#[allow(dead_code)]
169+
fn initgroups(
170+
_user: &CStr, _group: Gid
171+
) -> Result<(), nix::errno::Errno> {
172+
Ok(())
173+
}
174+
175+
/// Fallback function for `nix::unistd::setresgid`.
176+
#[allow(dead_code)]
177+
fn setresgid(
178+
rgid: Gid, egid: Gid, _sgid: Gid
179+
) -> Result<(), nix::errno::Errno> {
180+
use nix::libc::{c_int, gid_t};
181+
182+
#[allow(dead_code)]
183+
unsafe fn setregid(rgid: gid_t, _egid: gid_t) -> c_int {
184+
unsafe { nix::libc::setgid(rgid) }
185+
}
186+
187+
{
188+
use nix::libc::*;
189+
190+
if unsafe { setregid(rgid.as_raw(), egid.as_raw()) } != 0 {
191+
return Err(nix::errno::Errno::last());
192+
}
193+
}
194+
195+
Ok(())
196+
}
197+
198+
/// Fallback function for `nix::unistd::setresuid`.
199+
#[allow(dead_code)]
200+
fn setresuid(
201+
ruid: Uid, euid: Uid, _suid: Uid
202+
) -> Result<(), nix::errno::Errno> {
203+
use nix::libc::{c_int, uid_t};
204+
205+
#[allow(dead_code)]
206+
unsafe fn setreuid(ruid: uid_t, _euid: uid_t) -> c_int {
207+
unsafe { nix::libc::setuid(ruid) }
208+
}
209+
210+
{
211+
use nix::libc::*;
212+
213+
if unsafe { setreuid(ruid.as_raw(), euid.as_raw()) } != 0 {
214+
return Err(nix::errno::Errno::last());
215+
}
216+
}
217+
218+
Ok(())
219+
}
220+
221+
let Some(user) = self.config.user.as_ref() else {
222+
return Ok(())
223+
};
224+
225+
// If we don’t have an explicit group, we use the user’s group.
226+
let gid = self.config.group.as_ref().map(|g| {
227+
g.gid
228+
}).unwrap_or_else(|| {
229+
user.gid
230+
});
231+
232+
// Let the system load the supplemental groups for the user.
233+
{
234+
use nix::unistd::*;
235+
236+
initgroups(&user.c_name, gid).map_err(|err| {
148237
error!(
149-
"Fatal: failed to set user '{}': {}",
238+
"failed to initgroups {}: {}",
150239
user.name, err
151240
);
152-
return Err(Failed)
153-
}
241+
Failed
242+
})?;
154243
}
155244

156-
if let Some(group) = self.config.group.as_ref() {
157-
if let Err(err) = setgid(group.gid) {
245+
// Set the group ID.
246+
{
247+
use nix::unistd::*;
248+
249+
setresgid(gid, gid, gid).map_err(|err| {
158250
error!(
159-
"Fatal: failed to set group '{}': {}",
160-
group.name, err
251+
"failed to set group ID: {err}"
161252
);
162-
return Err(Failed)
163-
}
253+
Failed
254+
})?;
164255
}
165256

166-
self.write_pid_file()?;
257+
// Set the user ID.
258+
{
259+
use nix::unistd::*;
260+
261+
setresuid(user.uid, user.uid, user.uid).map_err(|err| {
262+
error!(
263+
"failed to set user ID: {err}"
264+
);
265+
Failed
266+
})?;
267+
}
167268

168269
Ok(())
169270
}
@@ -404,22 +505,39 @@ mod unix {
404505
#[derive(Clone, Debug, Deserialize, Serialize)]
405506
#[serde(try_from = "String", into = "String", expecting = "a user name")]
406507
struct UserId {
407-
/// The numerical user ID.
408-
uid: Uid,
409-
410508
/// The user name.
411509
///
412-
/// We keep this information so we can produce the actual config.
510+
/// This is used for error reporting.
413511
name: String,
512+
513+
/// The user name as a C string.
514+
///
515+
/// This is used internally. We keep both the string and C string
516+
/// versions because conversion can cause errors and we want to catch
517+
/// those early.
518+
c_name: CString,
519+
520+
/// The numerical user ID.
521+
uid: Uid,
522+
523+
/// The numerical group ID of the user.
524+
gid: Gid,
525+
414526
}
415527

416528
impl TryFrom<String> for UserId {
417529
type Error = String;
418530

419531
fn try_from(name: String) -> Result<Self, Self::Error> {
532+
let Ok(c_name) = CString::new(name.clone()) else {
533+
return Err(format!("invalid user name '{name}'"))
534+
};
420535
match User::from_name(&name) {
421536
Ok(Some(user)) => {
422-
Ok(UserId { uid: user.uid, name })
537+
Ok(UserId {
538+
name, c_name,
539+
gid: user.gid, uid: user.uid
540+
})
423541
}
424542
Ok(None) => {
425543
Err(format!("unknown user '{name}'"))
@@ -452,13 +570,11 @@ mod unix {
452570
#[derive(Clone, Debug, Deserialize, Serialize)]
453571
#[serde(try_from = "String", into = "String", expecting = "a user name")]
454572
struct GroupId {
455-
/// The numerical user ID.
456-
gid: Gid,
457-
458-
/// The user name.
459-
///
460-
/// We keep this information so we can produce the actual config.
573+
/// The group name.
461574
name: String,
575+
576+
/// The numerical group ID.
577+
gid: Gid,
462578
}
463579

464580
impl TryFrom<String> for GroupId {
@@ -470,10 +586,10 @@ mod unix {
470586
Ok(GroupId { gid: group.gid, name })
471587
}
472588
Ok(None) => {
473-
Err(format!("unknown user '{name}'"))
589+
Err(format!("unknown group '{name}'"))
474590
}
475591
Err(err) => {
476-
Err(format!("failed to resolve user '{name}': {err}"))
592+
Err(format!("failed to resolve group '{name}': {err}"))
477593
}
478594
}
479595
}

0 commit comments

Comments
 (0)