Skip to main content

illumos_priv/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// Copyright 2020 Joyent, Inc.
6
7#![deny(warnings)]
8#![deny(missing_docs)]
9
10//! illumos implements a set of privileges that provide fine-grained control over the actions of
11//! processes. The possession of a certain privilege allows a process to perform a specific set of
12//! restricted operations.
13//!
14//! This crate provides a safe wrapper around this interface and lets you add/remove/replace a
15//! privilege set for a process or its off-spring.
16//!
17//! # Example:
18//! ```
19//! use illumos_priv::{PrivOp, PrivPtype, PrivSet, Privilege};
20//!
21//! // Get a new basic PrivSet.
22//! let mut set = PrivSet::new_basic().unwrap();
23//!
24//! // Remove the ability to fork(2) from the set.
25//! let _ = set
26//!     .delset(Privilege::ProcFork)
27//!     .expect("failed to delete from set");
28//!
29//! // Replace the effective privilege set with the new one
30//! illumos_priv::setppriv(PrivOp::Set, PrivPtype::Effective, &set).unwrap();
31//!
32//! ```
33
34use std::ffi::CStr;
35use std::io;
36use std::os::raw::c_char;
37
38mod ffi;
39mod privileges;
40
41// Have to use "crate::" here due to a bug in rust 1.31 which is used by jenkins
42pub use crate::privileges::Privilege;
43
44/// See GETPPRIV(2) for more in depth documentation.
45pub enum PrivPtype {
46    /// Set of privileges currently in effect.
47    Effective,
48    /// Set of privileges that comes into effect on exec.
49    Inheritable,
50    /// Set of privileges that can be put into the effective set without restriction.
51    Permitted,
52    /// Set of privileges that determines the absolute upper bound of privileges this process and
53    /// its off-spring can obtain.
54    Limit,
55}
56
57impl PrivPtype {
58    fn as_str(&self) -> &str {
59        match self {
60            PrivPtype::Effective => "Effective\0",
61            PrivPtype::Inheritable => "Inheritable\0",
62            PrivPtype::Permitted => "Permitted\0",
63            PrivPtype::Limit => "Limit\0",
64        }
65    }
66
67    fn as_ptr(&self) -> *const c_char {
68        CStr::from_bytes_with_nul(self.as_str().as_bytes())
69            .expect("all variants should be nul terminated")
70            .as_ptr()
71    }
72}
73
74/// See GETPPRIV(2) for more in depth documentation.
75#[repr(C)]
76pub enum PrivOp {
77    /// Turns on the privileges in a `PrivSet`
78    On = 0,
79    /// Turns off the privileges in a `PrivSet`
80    Off,
81    /// Replaces the privileges in a `PrivSet`
82    Set,
83}
84
85/// An illumos privilege set that allows one to add/remove or complete replace a set of privileges
86/// for a process. When `PrivSet` is dropped, its backing memory is freed.
87pub struct PrivSet {
88    inner: *mut ffi::OpaquePrivSet,
89}
90
91impl PrivSet {
92    /// Allocates a new empty `PrivSet`
93    pub fn new_empty() -> io::Result<Self> {
94        unsafe {
95            let inner = ptr_or_err(ffi::priv_allocset())?;
96            ffi::priv_emptyset(inner);
97            Ok(PrivSet { inner })
98        }
99    }
100
101    /// Allocates a new `PrivSet` with "basic" privileges.
102    /// "basic" privileges are "privileges" unprivileged processes are accustomed to having.
103    pub fn new_basic() -> io::Result<Self> {
104        unsafe {
105            let inner = ptr_or_err(ffi::priv_allocset())?;
106            ffi::priv_basicset(inner);
107            Ok(PrivSet { inner })
108        }
109    }
110
111    /// Adds the "basic" set to the `PrivSet`.
112    pub fn basic(&mut self) {
113        unsafe {
114            ffi::priv_basicset(self.inner);
115        }
116    }
117
118    /// Empties the `PrivSet` so that it contains no "privileges"
119    pub fn empty(&mut self) {
120        unsafe {
121            ffi::priv_emptyset(self.inner);
122        }
123    }
124
125    /// Adds the named privilege to the `PrivSet`
126    pub fn addset(&mut self, p: Privilege) -> io::Result<()> {
127        unsafe { ret_or_err(ffi::priv_addset(self.inner, p.as_ptr())) }
128    }
129
130    /// Removes the named privilege from the `PrivSet`
131    pub fn delset(&mut self, p: Privilege) -> io::Result<()> {
132        unsafe { ret_or_err(ffi::priv_delset(self.inner, p.as_ptr())) }
133    }
134
135    /// Determines whether the `PrivSet` is empty.
136    pub fn is_empty(&self) -> bool {
137        unsafe { true_or_false(ffi::priv_isemptyset(self.inner)) }
138    }
139
140    /// Determines whether the named privilege is a member of the `PrivSet`.
141    pub fn is_member(&self, p: Privilege) -> bool {
142        unsafe { true_or_false(ffi::priv_ismember(self.inner, p.as_ptr())) }
143    }
144
145    /// Determines whether the `PrivSet` is equal to the `dst` `PrivSet`.
146    pub fn is_equal(&self, dst: &PrivSet) -> bool {
147        unsafe { true_or_false(ffi::priv_isequalset(self.inner, dst.inner)) }
148    }
149}
150
151impl PartialEq for PrivSet {
152    fn eq(&self, other: &Self) -> bool {
153        self.is_equal(other)
154    }
155}
156
157/// Sets or changes the processes privilege set.
158pub fn setppriv(op: PrivOp, ptype: PrivPtype, sp: &PrivSet) -> io::Result<()> {
159    unsafe { ret_or_err(ffi::setppriv(op as i32, ptype.as_ptr(), sp.inner)) }
160}
161
162/// Gets the process privilege set for the given `PrivPtype`.
163pub fn getppriv(ptype: PrivPtype) -> io::Result<PrivSet> {
164    unsafe {
165        let inner = ptr_or_err(ffi::priv_allocset())?;
166        // Make sure we create an instance of `PrivSet` first so in the event the call to getppriv
167        // below fails, the inner value will be freed via `Drop`
168        let sp = PrivSet { inner };
169        ret_or_err(ffi::getppriv(ptype.as_ptr(), sp.inner))?;
170        Ok(sp)
171    }
172}
173
174impl Drop for PrivSet {
175    fn drop(&mut self) {
176        if !self.inner.is_null() {
177            unsafe {
178                ffi::priv_freeset(self.inner);
179            }
180        }
181    }
182}
183
184// ============ Helpers ============
185
186fn ptr_or_err<T>(ptr: *mut T) -> io::Result<*mut T> {
187    if ptr.is_null() {
188        Err(io::Error::last_os_error())
189    } else {
190        Ok(ptr)
191    }
192}
193
194fn ret_or_err(ret: i32) -> io::Result<()> {
195    match ret {
196        -1 => Err(io::Error::last_os_error()),
197        _ => Ok(()),
198    }
199}
200
201fn true_or_false(ret: i32) -> bool {
202    matches!(ret, 1)
203}
204
205// ============ Tests ============
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::process::Command;
211
212    #[test]
213    fn empty_set_test() {
214        let set = PrivSet::new_empty().unwrap();
215        assert_eq!(true, set.is_empty(), "set is empty");
216    }
217
218    #[test]
219    fn empty_test() {
220        let mut set = PrivSet::new_basic().unwrap();
221        assert_eq!(false, set.is_empty(), "set is not empty");
222        set.empty();
223        assert_eq!(true, set.is_empty(), "set is empty");
224    }
225
226    #[test]
227    fn is_equal_test() {
228        let src = PrivSet::new_basic().unwrap();
229        let mut dst = PrivSet::new_empty().unwrap();
230        assert_eq!(false, src.is_equal(&dst), "PrivSets are not equal");
231        dst.basic();
232        assert_eq!(true, src.is_equal(&dst), "PrivSets are equal");
233        // Also verify that PartialEq returns true
234        assert!(src == dst, "PrivSets are equal");
235    }
236
237    #[test]
238    fn is_member_test() {
239        let set = PrivSet::new_basic().unwrap();
240        assert_eq!(
241            true,
242            set.is_member(Privilege::ProcFork),
243            "PRIV_PROC_FORK is in the set"
244        );
245    }
246
247    #[test]
248    fn add_priv_test() {
249        let mut set = PrivSet::new_empty().unwrap();
250        assert_eq!(
251            false,
252            set.is_member(Privilege::ProcFork),
253            "PRIV_PROC_FORK is not in the set"
254        );
255        let _ = set
256            .addset(Privilege::ProcFork)
257            .expect("failed to add to the set");
258        assert_eq!(
259            true,
260            set.is_member(Privilege::ProcFork),
261            "PRIV_PROC_FORK is in the set"
262        );
263    }
264
265    #[test]
266    fn del_priv_test() {
267        let mut set = PrivSet::new_basic().unwrap();
268        assert_eq!(
269            true,
270            set.is_member(Privilege::ProcFork),
271            "PRIV_PROC_FORK is in the set"
272        );
273        let _ = set
274            .delset(Privilege::ProcFork)
275            .expect("failed to delete from set");
276        assert_eq!(
277            false,
278            set.is_member(Privilege::ProcFork),
279            "PRIV_PROC_FORK is not in the set"
280        );
281    }
282
283    #[test]
284    fn getppriv_test() {
285        let orig = getppriv(PrivPtype::Effective).unwrap();
286        let mut src = PrivSet::new_basic().unwrap();
287        let _ = src
288            .delset(Privilege::ProcFork)
289            .expect("failed to delete from set");
290        setppriv(PrivOp::Set, PrivPtype::Effective, &src).unwrap();
291
292        let dst = getppriv(PrivPtype::Effective).unwrap();
293        assert!(src == dst, "getpprive PrivSet matches the one we just set");
294        // Reset the original privilege set so other tests don't fail
295        setppriv(PrivOp::Set, PrivPtype::Effective, &orig).unwrap();
296    }
297
298    #[test]
299    fn drop_fork_test() {
300        let orig = getppriv(PrivPtype::Effective).unwrap();
301        let mut set = PrivSet::new_basic().unwrap();
302        let _ = set
303            .delset(Privilege::ProcFork)
304            .expect("failed to delete from set");
305
306        let res = Command::new("ls").output();
307        assert!(res.is_ok(), "successfully ran ls");
308
309        // Drop proc_fork and make sure the command fails below
310        setppriv(PrivOp::Set, PrivPtype::Effective, &set).unwrap();
311
312        let res = Command::new("ls").output();
313        assert!(res.is_err(), "can no longer run ls");
314        let err = res.unwrap_err();
315        assert_eq!(
316            std::io::ErrorKind::PermissionDenied,
317            err.kind(),
318            "got permission denied when attempting to run ls"
319        );
320
321        // Reset the original privilege set so other tests don't fail
322        setppriv(PrivOp::Set, PrivPtype::Effective, &orig).unwrap();
323    }
324}