Skip to content

Commit a166e84

Browse files
committed
Added support for prctl in Linux
1 parent b1e1a60 commit a166e84

File tree

5 files changed

+345
-0
lines changed

5 files changed

+345
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
3030
(#[1752](https://github.com/nix-rust/nix/pull/1752))
3131
- Added `signal::SigSet::from_sigset_t_unchecked()`.
3232
(#[1741](https://github.com/nix-rust/nix/pull/1741))
33+
- Added support for prctl in Linux.
34+
(#[1550](https://github.com/nix-rust/nix/pull/1550))
3335

3436
### Changed
3537

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ name = "test-mount"
111111
path = "test/test_mount.rs"
112112
harness = false
113113

114+
[[test]]
115+
name = "test-prctl"
116+
path = "test/sys/test_prctl.rs"
117+
114118
[[test]]
115119
name = "test-ptymaster-drop"
116120
path = "test/test_ptymaster_drop.rs"

src/sys/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ feature! {
6868
pub mod personality;
6969
}
7070

71+
#[cfg(target_os = "linux")]
72+
feature! {
73+
#![feature = "process"]
74+
pub mod prctl;
75+
}
76+
7177
feature! {
7278
#![feature = "pthread"]
7379
pub mod pthread;

src/sys/prctl.rs

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
//! prctl is a Linux-only API for performing operations on a process or thread.
2+
//!
3+
//! Note that careless use of some prctl() operations can confuse the user-space run-time
4+
//! environment, so these operations should be used with care.
5+
//!
6+
//! For more documentation, please read [prctl(2)](https://man7.org/linux/man-pages/man2/prctl.2.html).
7+
8+
use crate::errno::Errno;
9+
use crate::sys::signal::Signal;
10+
use crate::Result;
11+
12+
use libc::{c_int, c_ulong};
13+
use std::convert::TryFrom;
14+
use std::ffi::{CStr, CString};
15+
16+
libc_enum! {
17+
/// The type of hardware memory corruption kill policy for the thread.
18+
19+
#[repr(i32)]
20+
#[non_exhaustive]
21+
#[allow(non_camel_case_types)]
22+
pub enum PrctlMCEKillPolicy {
23+
/// The thread will receive SIGBUS as soon as a memory corruption is detected.
24+
PR_MCE_KILL_EARLY,
25+
/// The process is killed only when it accesses a corrupted page.
26+
PR_MCE_KILL_LATE,
27+
/// Uses the system-wide default.
28+
PR_MCE_KILL_DEFAULT,
29+
}
30+
impl TryFrom<i32>
31+
}
32+
33+
fn prctl_set_bool(option: c_int, status: bool) -> Result<()> {
34+
let res = unsafe { libc::prctl(option, status as c_ulong, 0, 0, 0) };
35+
Errno::result(res).map(drop)
36+
}
37+
38+
fn prctl_get_bool(option: c_int) -> Result<bool> {
39+
let res = unsafe { libc::prctl(option, 0, 0, 0, 0) };
40+
Errno::result(res).map(|res| res != 0)
41+
}
42+
43+
/// Set the "child subreaper" attribute for this process
44+
pub fn set_child_subreaper(attribute: bool) -> Result<()> {
45+
prctl_set_bool(libc::PR_SET_CHILD_SUBREAPER, attribute)
46+
}
47+
48+
/// Get the "child subreaper" attribute for this process
49+
pub fn get_child_subreaper() -> Result<bool> {
50+
// prctl writes into this var
51+
let mut subreaper: c_int = 0;
52+
53+
let res = unsafe { libc::prctl(libc::PR_GET_CHILD_SUBREAPER, &mut subreaper, 0, 0, 0) };
54+
55+
Errno::result(res).map(|_| subreaper != 0)
56+
}
57+
58+
/// Set the dumpable attribute which determines if core dumps are created for this process.
59+
pub fn set_dumpable(attribute: bool) -> Result<()> {
60+
prctl_set_bool(libc::PR_SET_DUMPABLE, attribute)
61+
}
62+
63+
/// Get the dumpable attribute for this process.
64+
pub fn get_dumpable() -> Result<bool> {
65+
prctl_get_bool(libc::PR_GET_DUMPABLE)
66+
}
67+
68+
/// Set the "keep capabilities" attribute for this process. This causes the thread to retain
69+
/// capabilities even if it switches its UID to a nonzero value.
70+
pub fn set_keepcaps(attribute: bool) -> Result<()> {
71+
prctl_set_bool(libc::PR_SET_KEEPCAPS, attribute)
72+
}
73+
74+
/// Get the "keep capabilities" attribute for this process
75+
pub fn get_keepcaps() -> Result<bool> {
76+
prctl_get_bool(libc::PR_GET_KEEPCAPS)
77+
}
78+
79+
/// Clear the thread memory corruption kill policy and use the system-wide default
80+
pub fn clear_mce_kill() -> Result<()> {
81+
let res = unsafe { libc::prctl(libc::PR_MCE_KILL, libc::PR_MCE_KILL_CLEAR, 0, 0, 0) };
82+
83+
Errno::result(res).map(drop)
84+
}
85+
86+
/// Set the thread memory corruption kill policy
87+
pub fn set_mce_kill(policy: PrctlMCEKillPolicy) -> Result<()> {
88+
let res = unsafe {
89+
libc::prctl(
90+
libc::PR_MCE_KILL,
91+
libc::PR_MCE_KILL_SET,
92+
policy as c_ulong,
93+
0,
94+
0,
95+
)
96+
};
97+
98+
Errno::result(res).map(drop)
99+
}
100+
101+
/// Get the thread memory corruption kill policy
102+
pub fn get_mce_kill() -> Result<PrctlMCEKillPolicy> {
103+
let res = unsafe { libc::prctl(libc::PR_MCE_KILL_GET, 0, 0, 0, 0) };
104+
105+
match Errno::result(res) {
106+
Ok(val) => Ok(PrctlMCEKillPolicy::try_from(val)?),
107+
Err(e) => Err(e),
108+
}
109+
}
110+
111+
/// Set the parent-death signal of the calling process. This is the signal that the calling process
112+
/// will get when its parent dies.
113+
pub fn set_pdeathsig<T: Into<Option<Signal>>>(signal: T) -> Result<()> {
114+
let sig = match signal.into() {
115+
Some(s) => s as c_int,
116+
None => 0,
117+
};
118+
119+
let res = unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, sig, 0, 0, 0) };
120+
121+
Errno::result(res).map(drop)
122+
}
123+
124+
/// Returns the current parent-death signal
125+
pub fn get_pdeathsig() -> Result<Option<Signal>> {
126+
// prctl writes into this var
127+
let mut sig: c_int = 0;
128+
129+
let res = unsafe { libc::prctl(libc::PR_GET_PDEATHSIG, &mut sig, 0, 0, 0) };
130+
131+
match Errno::result(res) {
132+
Ok(_) => Ok(match sig {
133+
0 => None,
134+
_ => Some(Signal::try_from(sig)?),
135+
}),
136+
Err(e) => Err(e),
137+
}
138+
}
139+
140+
/// Set the name of the calling thread. Strings longer than 15 bytes will be truncated.
141+
pub fn set_name(name: &str) -> Result<()> {
142+
let name = CString::new(name.as_bytes()).unwrap();
143+
144+
let res = unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr(), 0, 0, 0) };
145+
146+
Errno::result(res).map(drop)
147+
}
148+
149+
/// Return the name of the calling thread
150+
pub fn get_name() -> Result<String> {
151+
// Size of buffer determined by linux/sched.h TASK_COMM_LEN
152+
let buf = [0u8; 16];
153+
154+
let res = unsafe { libc::prctl(libc::PR_GET_NAME, &buf, 0, 0, 0) };
155+
156+
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
157+
let cstr = CStr::from_bytes_with_nul(&buf[..=len]).map_err(|_| Errno::EINVAL)?;
158+
let name = cstr.to_str().map_err(|_| Errno::EINVAL)?;
159+
160+
Errno::result(res).map(|_| name.to_owned())
161+
}
162+
163+
/// Sets the timer slack value for the calling thread. Timer slack is used by the kernel to group
164+
/// timer expirations and make them the supplied amount of nanoseconds late.
165+
pub fn set_timerslack(ns: u64) -> Result<()> {
166+
let res = unsafe { libc::prctl(libc::PR_SET_TIMERSLACK, ns, 0, 0, 0) };
167+
168+
Errno::result(res).map(drop)
169+
}
170+
171+
/// Get the timerslack for the calling thread.
172+
pub fn get_timerslack() -> Result<i32> {
173+
let res = unsafe { libc::prctl(libc::PR_GET_TIMERSLACK, 0, 0, 0, 0) };
174+
175+
Errno::result(res)
176+
}
177+
178+
/// Disable all performance counters attached to the calling process.
179+
pub fn task_perf_events_disable() -> Result<()> {
180+
let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_DISABLE, 0, 0, 0, 0) };
181+
182+
Errno::result(res).map(drop)
183+
}
184+
185+
/// Enable all performance counters attached to the calling process.
186+
pub fn task_perf_events_enable() -> Result<()> {
187+
let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_ENABLE, 0, 0, 0, 0) };
188+
189+
Errno::result(res).map(drop)
190+
}
191+
192+
/// Set the calling threads "no new privs" attribute. Once set this option can not be unset.
193+
pub fn set_no_new_privs() -> Result<()> {
194+
prctl_set_bool(libc::PR_SET_NO_NEW_PRIVS, true) // Cannot be unset
195+
}
196+
197+
/// Get the "no new privs" attribute for the calling thread.
198+
pub fn get_no_new_privs() -> Result<bool> {
199+
prctl_get_bool(libc::PR_GET_NO_NEW_PRIVS)
200+
}
201+
202+
/// Set the state of the "THP disable" flag for the calling thread. Setting this disables
203+
/// transparent huge pages.
204+
pub fn set_thp_disable(flag: bool) -> Result<()> {
205+
prctl_set_bool(libc::PR_SET_THP_DISABLE, flag)
206+
}
207+
208+
/// Get the "THP disable" flag for the calling thread.
209+
pub fn get_thp_disable() -> Result<bool> {
210+
prctl_get_bool(libc::PR_GET_THP_DISABLE)
211+
}

test/sys/test_prctl.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#[cfg(target_os = "linux")]
2+
#[cfg(feature = "process")]
3+
mod test_prctl {
4+
use nix::sys::prctl;
5+
6+
#[cfg_attr(qemu, ignore)]
7+
#[test]
8+
fn test_get_set_subreaper() {
9+
let original = prctl::get_child_subreaper().unwrap();
10+
11+
prctl::set_child_subreaper(true).unwrap();
12+
let subreaper = prctl::get_child_subreaper().unwrap();
13+
assert!(subreaper);
14+
15+
prctl::set_child_subreaper(original).unwrap();
16+
}
17+
18+
#[test]
19+
fn test_get_set_dumpable() {
20+
let original = prctl::get_dumpable().unwrap();
21+
22+
prctl::set_dumpable(false).unwrap();
23+
let dumpable = prctl::get_dumpable().unwrap();
24+
assert!(!dumpable);
25+
26+
prctl::set_dumpable(original).unwrap();
27+
}
28+
29+
#[test]
30+
fn test_get_set_keepcaps() {
31+
let original = prctl::get_keepcaps().unwrap();
32+
33+
prctl::set_keepcaps(true).unwrap();
34+
let keepcaps = prctl::get_keepcaps().unwrap();
35+
assert!(keepcaps);
36+
37+
prctl::set_keepcaps(original).unwrap();
38+
}
39+
40+
#[test]
41+
fn test_get_set_clear_mce_kill() {
42+
use prctl::PrctlMCEKillPolicy::*;
43+
44+
prctl::set_mce_kill(PR_MCE_KILL_LATE).unwrap();
45+
let mce = prctl::get_mce_kill().unwrap();
46+
assert_eq!(mce, PR_MCE_KILL_LATE);
47+
48+
prctl::clear_mce_kill().unwrap();
49+
let mce = prctl::get_mce_kill().unwrap();
50+
assert_eq!(mce, PR_MCE_KILL_DEFAULT);
51+
}
52+
53+
#[cfg_attr(qemu, ignore)]
54+
#[test]
55+
fn test_get_set_pdeathsig() {
56+
use nix::sys::signal::Signal;
57+
58+
let original = prctl::get_pdeathsig().unwrap();
59+
60+
prctl::set_pdeathsig(Signal::SIGUSR1).unwrap();
61+
let sig = prctl::get_pdeathsig().unwrap();
62+
assert_eq!(sig, Some(Signal::SIGUSR1));
63+
64+
prctl::set_pdeathsig(original).unwrap();
65+
}
66+
67+
#[test]
68+
fn test_get_set_name() {
69+
let original = prctl::get_name().unwrap();
70+
71+
let long_name = String::from("0123456789abcdefghijklmn");
72+
prctl::set_name(&long_name).unwrap();
73+
let res = prctl::get_name().unwrap();
74+
75+
// name truncated by kernel to TASK_COMM_LEN
76+
assert_eq!(&long_name[..15], res);
77+
78+
let short_name = String::from("01234567");
79+
prctl::set_name(&short_name).unwrap();
80+
let res = prctl::get_name().unwrap();
81+
assert_eq!(short_name, res);
82+
83+
prctl::set_name(&original).unwrap();
84+
}
85+
86+
#[cfg_attr(qemu, ignore)]
87+
#[test]
88+
fn test_get_set_timerslack() {
89+
let original = prctl::get_timerslack().unwrap();
90+
91+
let slack = 60_000;
92+
prctl::set_timerslack(slack).unwrap();
93+
let res = prctl::get_timerslack().unwrap();
94+
assert_eq!(slack, res as u64);
95+
96+
prctl::set_timerslack(original as u64).unwrap();
97+
}
98+
99+
#[test]
100+
fn test_disable_enable_perf_events() {
101+
prctl::task_perf_events_disable().unwrap();
102+
prctl::task_perf_events_enable().unwrap();
103+
}
104+
105+
#[test]
106+
fn test_get_set_no_new_privs() {
107+
prctl::set_no_new_privs().unwrap();
108+
let no_new_privs = prctl::get_no_new_privs().unwrap();
109+
assert!(no_new_privs);
110+
}
111+
112+
#[test]
113+
fn test_get_set_thp_disable() {
114+
let original = prctl::get_thp_disable().unwrap();
115+
116+
prctl::set_thp_disable(true).unwrap();
117+
let thp_disable = prctl::get_thp_disable().unwrap();
118+
assert!(thp_disable);
119+
120+
prctl::set_thp_disable(original).unwrap();
121+
}
122+
}

0 commit comments

Comments
 (0)