Rust tip: (Unix) drop the current user privileges

Brief

Rust is a modern programing language which is claimed to be blazingly fast and memory-efficient. It syntactically similar to C++, but is designed to provide better memory safety while maintaining high performance and productivity:

  • Zero cost abstraction: allow a perfect balance between performance and productivity
  • Memory efficient with no runtime or garbage collector
  • Memory safe: Rust does not permit null pointers, dangling pointers, or data races in safe code.
  • Memory management using an ownership model guarantee memory-safety and thread-safety .
  • Great documentation, easy to use compiler and integrated packages/libraries management
  • Easy to interface with other language.
  • A bit of learning curve for the variable ownership and variable lifetime features.

Drop user privileges in Rust

Enough theory, now come the problem: I currently develop a daemon network application (server) in Rust for an embedded project, and for security purpose, at some point in the program, after reading all configurations from a file, the application need to drop user privileges of the current process to another configured user. How can one do such a thing in Rust ?

Basically, in Unix-like system, user privileges dropping can be performed via some libc functions:

// get group struct from group name
struct group *getgrnam(const char *name);
// Drop the group privileges of the current process
int setgid(    gid_t gid);
// get user struct from user name
struct passwd *getpwnam(const char *name);
// Drop user privileges
int setuid(uid_t uid);

These function need to be called from Rust to perform the task. This requires performing some FFI calls to external library (libc in this case). Luckily, we can use the libc crate, a libc wrapper for Rust, for this purpose.

Using external library in Rust involves the use of unsafe feature in Rust. Basically, Rust is compile time memory safe, but when accessing external C library which is not managed by the Rust's compiler, the memory safety of these calls are unknown at compile time. In this case, you must use the unsafe feature to tell the compiler that it can count you, and that you do it with your own risk. The downside: if you use unsafe code incorrectly, problems due to memory unsafety, such as null pointers, dangling pointers, or data races, can occur.

The following snippet shows how to use unsafe code to perform user privileges dropping via FFI calls to underlying libc library, unsafe calls are denoted by the unsafe keyword:

extern crate libc;
use std::ffi::{CStr, CString};
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::ptr;

/// Drop user privileges
///
/// This function drop the privileges of the current user
/// to another inferior privileges user.
pub fn privdrop(user: &str, group: &str) -> Result<(), Error> {
    // the group id need to be set first, otherwise,
    // when the user privileges drop, it is unnable to
    // drop the group privileges
    // get the gid from username
    if let Ok(cstr) = CString::new(group.as_bytes()) {
        let p = unsafe { libc::getgrnam(cstr.as_ptr()) };
        if p.is_null() {
            eprintln!("privdrop: Unable to getgrnam of group: {}", group);
            return Err(Error::last_os_error());
        }
        if unsafe { libc::setgid((*p).gr_gid) } != 0 {
            eprintln!("privdrop: Unable to setgid of group: {}", group);
            return Err(Error::last_os_error());
        }
    } else {
        return Err(Error::new(
            ErrorKind::Other,
            "Cannot create CString from String (group)!",
        ));
    }
    // drop the user privileges
    // get the uid from username
    if let Ok(cstr) = CString::new(user.as_bytes()) {
        let p = unsafe { libc::getpwnam(cstr.as_ptr()) };
        if p.is_null() {
            eprintln!("privdrop: Unable to getpwnam of user: {}", user);
            return Err(Error::last_os_error());
        }
        if unsafe { libc::setuid((*p).pw_uid) } != 0 {
            eprintln!("privdrop: Unable to setuid of user: {}", user);
            return Err(Error::last_os_error());
        }
    } else {
        return Err(Error::new(
            ErrorKind::Other,
            "Cannot create CString from String (user)!",
        ));
    }
    Ok(())
}

Related posts

Comments

The comment editor supports Markdown syntax. Your email is necessary to notify you of further updates on the discussion. It will be hidden from the public.
Powered by antd server, (c) 2017 - 2024 Dany LE.This site does not use cookie, but some third-party contents (e.g. Youtube, Twitter) may do.