drop_root_caps/lib.rs
1// SPDX-License-Identifier: 0BSD
2// Drop Root Capabilities
3// Copyright (C) 2025 by LoRd_MuldeR <mulder2@gmx.de>
4
5#![no_std]
6
7//! On Linux, the "root" user (UID 0) has some special capabilities that "regular" users do **not** normally have. This can result in weird behavior, e.g., if unit tests (or integration tests) are executed in the context of the "root" user, as Docker® containers do by default! For example, a file that **should not** be accessible (according to its access permissions) may suddenly become accessible – because the "root" user has the `CAP_DAC_OVERRIDE` capability, which allows them to access the file *regardless of the access permissions*. As a result, a test case that expects `File::open()` to return a "permission denied" error will suddenly start to fail 😨
8//!
9//! This crate uses the Linux syscall [`prctl()`](https://man7.org/linux/man-pages/man2/prctl.2.html) with argument [`PR_CAPBSET_DROP`](https://man7.org/linux/man-pages/man2/PR_CAPBSET_DROP.2const.html) to drop the "root"-specific capabilities at application startup and thus restores the expected behavior. It does *nothing* on other platforms.
10//!
11//! ## Usage
12//!
13//! Simply add the following code to the top of your test module(s):
14//!
15//! ```
16//! #[used]
17//! static DROP_ROOT_CAPS: () = drop_root_caps::set_up();
18//! ```
19//!
20//! ## Features
21//!
22//! The **`ctor`** feature, which is enabled *by default*, uses the [`ctor`](https://crates.io/crates/ctor) crate to drop the "root" user capabilities *before* the `main()` function or any of your `#[test]` functions run. This is the recommended way to use this crate 😎
23//!
24//! If you *disable* the `ctor` feature, then [`drop_root_caps()`] must be called explicitly, because it will **not** be called automatically. However, if the `ctor` feature is enabled (the default), then calling [`drop_root_caps()`] is **not** required or useful!
25//!
26//! <div class="warning">
27//!
28//! Note: For the `ctor` feature to work as expected, you **must** call the *static* [`set_up()`] function, as describe [above](#usage) 🚨
29//!
30//! </div>
31//!
32//! ## See also
33//!
34//! 🔗 <https://crates.io/crates/drop-root-caps>
35//! 🔗 <https://github.com/lordmulder/drop-root-caps>
36
37#[cfg(target_os = "linux")]
38mod linux {
39 use core::hint::black_box;
40 use libc::{c_long, prctl, PR_CAPBSET_DROP};
41
42 // Capability constants
43 // See linux/include/uapi/linux/capability.h for details!
44 const CAP_CHOWN: c_long = 0;
45 const CAP_DAC_OVERRIDE: c_long = 1;
46 const CAP_DAC_READ_SEARCH: c_long = 2;
47 const CAP_FOWNER: c_long = 3;
48 const CAP_FSETID: c_long = 4;
49 const CAP_LINUX_IMMUTABLE: c_long = 9;
50 const CAP_MKNOD: c_long = 27;
51 const CAP_MAC_OVERRIDE: c_long = 32;
52
53 pub unsafe fn drop_root_caps() {
54 for capability in [CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_LINUX_IMMUTABLE, CAP_MAC_OVERRIDE, CAP_MKNOD] {
55 black_box(prctl(PR_CAPBSET_DROP, capability, 0 as c_long, 0 as c_long, 0 as c_long));
56 }
57 }
58
59 /// The initialization function that will run before the "main" function (or any test function)
60 #[cfg(feature = "ctor")]
61 #[ctor::ctor]
62 unsafe fn global_initializer() {
63 drop_root_caps()
64 }
65}
66
67/// Drop the "root" user capabilities now.
68///
69/// <div class="warning">
70///
71/// Note: It is **not** required to explicitly call this function, if you use the `ctor` feature, which is enabled *by default* 💡
72///
73/// </div>
74pub fn drop_root_caps() {
75 #[cfg(target_os = "linux")]
76 unsafe {
77 linux::drop_root_caps()
78 };
79}
80
81/// Dummy set-up function to ensure that our crate will actually be linked
82#[cfg(feature = "ctor")]
83pub const fn set_up() {}