Skip to content

Commit 6175761

Browse files
authored
Rework setting of user and group ID. (#11)
This PR updates setting of user and group ID along the lines of what Unbound does. This requires a bit of trickery to be portable. I hope the commentary in the code is sufficient to explain what’s going on.
1 parent 737bd3e commit 6175761

File tree

1 file changed

+147
-27
lines changed

1 file changed

+147
-27
lines changed

src/process.rs

Lines changed: 147 additions & 27 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,131 @@ 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 cascade: 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+
#[allow(unused_imports)]
189+
use nix::libc::*;
190+
191+
if unsafe { setregid(rgid.as_raw(), egid.as_raw()) } != 0 {
192+
return Err(nix::errno::Errno::last());
193+
}
194+
}
195+
196+
Ok(())
197+
}
198+
199+
/// Fallback function for `nix::unistd::setresuid`.
200+
#[allow(dead_code)]
201+
fn setresuid(
202+
ruid: Uid, euid: Uid, _suid: Uid
203+
) -> Result<(), nix::errno::Errno> {
204+
use nix::libc::{c_int, uid_t};
205+
206+
#[allow(dead_code)]
207+
unsafe fn setreuid(ruid: uid_t, _euid: uid_t) -> c_int {
208+
unsafe { nix::libc::setuid(ruid) }
209+
}
210+
211+
{
212+
#[allow(unused_imports)]
213+
use nix::libc::*;
214+
215+
if unsafe { setreuid(ruid.as_raw(), euid.as_raw()) } != 0 {
216+
return Err(nix::errno::Errno::last());
217+
}
218+
}
219+
220+
Ok(())
221+
}
222+
223+
let Some(user) = self.config.user.as_ref() else {
224+
return Ok(())
225+
};
226+
227+
// If we don’t have an explicit group, we use the user’s group.
228+
let gid = self.config.group.as_ref().map(|g| {
229+
g.gid
230+
}).unwrap_or_else(|| {
231+
user.gid
232+
});
233+
234+
// Let the system load the supplemental groups for the user.
235+
{
236+
#[allow(unused_imports)]
237+
use nix::unistd::*;
238+
239+
initgroups(&user.c_name, gid).map_err(|err| {
148240
error!(
149-
"Fatal: failed to set user '{}': {}",
150-
user.name, err
241+
"failed to initialize the group access list: {err}",
151242
);
152-
return Err(Failed)
153-
}
243+
Failed
244+
})?;
154245
}
155246

156-
if let Some(group) = self.config.group.as_ref() {
157-
if let Err(err) = setgid(group.gid) {
247+
// Set the group ID.
248+
{
249+
#[allow(unused_imports)]
250+
use nix::unistd::*;
251+
252+
setresgid(gid, gid, gid).map_err(|err| {
158253
error!(
159-
"Fatal: failed to set group '{}': {}",
160-
group.name, err
254+
"failed to set group ID: {err}"
161255
);
162-
return Err(Failed)
163-
}
256+
Failed
257+
})?;
164258
}
165259

166-
self.write_pid_file()?;
260+
// Set the user ID.
261+
{
262+
#[allow(unused_imports)]
263+
use nix::unistd::*;
264+
265+
setresuid(user.uid, user.uid, user.uid).map_err(|err| {
266+
error!(
267+
"failed to set user ID: {err}"
268+
);
269+
Failed
270+
})?;
271+
}
167272

168273
Ok(())
169274
}
@@ -404,22 +509,39 @@ mod unix {
404509
#[derive(Clone, Debug, Deserialize, Serialize)]
405510
#[serde(try_from = "String", into = "String", expecting = "a user name")]
406511
struct UserId {
407-
/// The numerical user ID.
408-
uid: Uid,
409-
410512
/// The user name.
411513
///
412-
/// We keep this information so we can produce the actual config.
514+
/// This is used for error reporting.
413515
name: String,
516+
517+
/// The user name as a C string.
518+
///
519+
/// This is used internally. We keep both the string and C string
520+
/// versions because conversion can cause errors, so it best happens
521+
/// already when creating an object.
522+
c_name: CString,
523+
524+
/// The numerical user ID.
525+
uid: Uid,
526+
527+
/// The numerical group ID of the user.
528+
gid: Gid,
529+
414530
}
415531

416532
impl TryFrom<String> for UserId {
417533
type Error = String;
418534

419535
fn try_from(name: String) -> Result<Self, Self::Error> {
536+
let Ok(c_name) = CString::new(name.clone()) else {
537+
return Err(format!("invalid user name '{name}'"))
538+
};
420539
match User::from_name(&name) {
421540
Ok(Some(user)) => {
422-
Ok(UserId { uid: user.uid, name })
541+
Ok(UserId {
542+
name, c_name,
543+
gid: user.gid, uid: user.uid
544+
})
423545
}
424546
Ok(None) => {
425547
Err(format!("unknown user '{name}'"))
@@ -452,13 +574,11 @@ mod unix {
452574
#[derive(Clone, Debug, Deserialize, Serialize)]
453575
#[serde(try_from = "String", into = "String", expecting = "a user name")]
454576
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.
577+
/// The group name.
461578
name: String,
579+
580+
/// The numerical group ID.
581+
gid: Gid,
462582
}
463583

464584
impl TryFrom<String> for GroupId {
@@ -470,10 +590,10 @@ mod unix {
470590
Ok(GroupId { gid: group.gid, name })
471591
}
472592
Ok(None) => {
473-
Err(format!("unknown user '{name}'"))
593+
Err(format!("unknown group '{name}'"))
474594
}
475595
Err(err) => {
476-
Err(format!("failed to resolve user '{name}': {err}"))
596+
Err(format!("failed to resolve group '{name}': {err}"))
477597
}
478598
}
479599
}

0 commit comments

Comments
 (0)