Skip to content

Commit 3a4037c

Browse files
authored
Added support for prctl in Linux (#1550)
1 parent d59d235 commit 3a4037c

File tree

5 files changed

+346
-0
lines changed

5 files changed

+346
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
3636
([#2014](https://github.com/nix-rust/nix/pull/2014))
3737
- Added `SO_TS_CLOCK` for FreeBSD to `nix::sys::socket::sockopt`.
3838
([#2093](https://github.com/nix-rust/nix/pull/2093))
39+
- Added support for prctl in Linux.
40+
(#[1550](https://github.com/nix-rust/nix/pull/1550))
41+
3942

4043
### Changed
4144

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,7 @@ path = "test/test_clearenv.rs"
9898
name = "test-mount"
9999
path = "test/test_mount.rs"
100100
harness = false
101+
102+
[[test]]
103+
name = "test-prctl"
104+
path = "test/sys/test_prctl.rs"

src/sys/mod.rs

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

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

src/sys/prctl.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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: &CStr) -> Result<()> {
142+
let res = unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr(), 0, 0, 0) };
143+
144+
Errno::result(res).map(drop)
145+
}
146+
147+
/// Return the name of the calling thread
148+
pub fn get_name() -> Result<CString> {
149+
// Size of buffer determined by linux/sched.h TASK_COMM_LEN
150+
let buf = [0u8; 16];
151+
152+
let res = unsafe { libc::prctl(libc::PR_GET_NAME, &buf, 0, 0, 0) };
153+
154+
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
155+
let name = CStr::from_bytes_with_nul(&buf[..=len]).map_err(|_| Errno::EINVAL)?;
156+
157+
Errno::result(res).map(|_| name.to_owned())
158+
}
159+
160+
/// Sets the timer slack value for the calling thread. Timer slack is used by the kernel to group
161+
/// timer expirations and make them the supplied amount of nanoseconds late.
162+
pub fn set_timerslack(ns: u64) -> Result<()> {
163+
let res = unsafe { libc::prctl(libc::PR_SET_TIMERSLACK, ns, 0, 0, 0) };
164+
165+
Errno::result(res).map(drop)
166+
}
167+
168+
/// Get the timerslack for the calling thread.
169+
pub fn get_timerslack() -> Result<i32> {
170+
let res = unsafe { libc::prctl(libc::PR_GET_TIMERSLACK, 0, 0, 0, 0) };
171+
172+
Errno::result(res)
173+
}
174+
175+
/// Disable all performance counters attached to the calling process.
176+
pub fn task_perf_events_disable() -> Result<()> {
177+
let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_DISABLE, 0, 0, 0, 0) };
178+
179+
Errno::result(res).map(drop)
180+
}
181+
182+
/// Enable all performance counters attached to the calling process.
183+
pub fn task_perf_events_enable() -> Result<()> {
184+
let res = unsafe { libc::prctl(libc::PR_TASK_PERF_EVENTS_ENABLE, 0, 0, 0, 0) };
185+
186+
Errno::result(res).map(drop)
187+
}
188+
189+
/// Set the calling threads "no new privs" attribute. Once set this option can not be unset.
190+
pub fn set_no_new_privs() -> Result<()> {
191+
prctl_set_bool(libc::PR_SET_NO_NEW_PRIVS, true) // Cannot be unset
192+
}
193+
194+
/// Get the "no new privs" attribute for the calling thread.
195+
pub fn get_no_new_privs() -> Result<bool> {
196+
prctl_get_bool(libc::PR_GET_NO_NEW_PRIVS)
197+
}
198+
199+
/// Set the state of the "THP disable" flag for the calling thread. Setting this disables
200+
/// transparent huge pages.
201+
pub fn set_thp_disable(flag: bool) -> Result<()> {
202+
prctl_set_bool(libc::PR_SET_THP_DISABLE, flag)
203+
}
204+
205+
/// Get the "THP disable" flag for the calling thread.
206+
pub fn get_thp_disable() -> Result<bool> {
207+
prctl_get_bool(libc::PR_GET_THP_DISABLE)
208+
}

test/sys/test_prctl.rs

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

0 commit comments

Comments
 (0)