priv_sep

Privilege separation library.
git clone https://git.philomathiclife.com/repos/priv_sep
Log | Files | Refs | README

commit f743e91856c60e488f06fdc7415c75e2dc374d23
Author: Zack Newman <zack@philomathiclife.com>
Date:   Tue, 25 Jul 2023 00:06:34 -0600

init

Diffstat:
A.gitignore | 2++
ACargo.toml | 27+++++++++++++++++++++++++++
ALICENSE-APACHE | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE-MIT | 20++++++++++++++++++++
AREADME.md | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib.rs | 419+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 705 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/** diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = ["Zack Newman <zack@philomathiclife.com>"] +categories = ["development-tools::ffi", "external-ffi-bindings", "os"] +description = "FFI for pledge(2) and unveil(2) on OpenBSD." +documentation = "https://crates.io/crates/priv_sep" +edition = "2021" +keywords = ["ffi", "openbsd", "privsep", "security"] +license = "MIT OR Apache-2.0" +name = "priv_sep" +readme = "README.md" +repository = "https://git.philomathiclife.com/repos/priv_sep/" +version = "0.1.0" + +[lib] +name = "priv_sep" +path = "src/lib.rs" + +[target.'cfg(openbsd)'.dependencies] +libc = { version = "0.2.147", default-features = false, features = ["std"] } + +[badges] +maintenance = { status = "actively-developed" } + +[profile.release] +lto = true +panic = 'abort' +strip = 'symbols' diff --git a/LICENSE-APACHE b/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright © 2023 Zack Newman + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +“Software”), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md @@ -0,0 +1,60 @@ +# `priv_sep` + +[`priv_sep`](https://docs.rs/priv_sep/latest/priv_sep) is a library for privilege separation. +It is currently designed around [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) and +[`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD-stable—that is correct, `stable` not `current`— +but in the future may contain functionality for Linux's +[`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html). + +## Pledge + +It is very rare to use the `execpromises` parameter, so [`pledge`] only relies on [`Promise`]s. +Additionally this function cannot be used to invoke `pledge(2)` with a `promises` value of `""`. + +## Unveil + +Unlike `pledge(2)` which allows a large quantity of duplicate `promises` to be provided, `unveil(2)` allows a maximum +of four `permissions` to be passed. For this reason, there are dedicated functions for each quantity of +[`Permission`]s. + +## Errors + +Any error returned from the underlying system call is propagated via +[`c_int`](https://doc.rust-lang.org/core/ffi/type.c_int.html). Note for both `pledge(2)` and `unveil(2)` duplicates +are ignored, so it is not an error to pass in duplicate values for their corresponding functions in this crate. + +### Status + +This package will be actively maintained to stay in-sync with the latest version of OpenBSD-stable; as a result, +the crate is only tested on the `x86_64-unknown-openbsd` target. While OpenBSD supports both the most recent +`release/stable` release as well as the previous version, only the most recent version will be supported by this +library. For that reason any removal of `promises` in subsequent releases of `pledge(2)` will lead to breaking +changes in this library as the corresponding `Promise` variant will be removed. + +### Building and testing + +```bash +laptop$ git clone https://git.philomathiclife.com/repos/priv_sep +Cloning into 'priv_sep'... +laptop$ cd priv_sep/ +laptop$ cargo build --release + Updating crates.io index + Compiling priv_sep v0.1.0 (/home/zack/projects/priv_sep) + Finished release [optimized] target(s) in 1.35s +laptop$ cargo t + Compiling priv_sep v0.1.0 (/home/zack/projects/priv_sep) + Finished test [unoptimized + debuginfo] target(s) in 0.33s + Running unittests src/lib.rs (target/debug/deps/priv_sep-2b69b951a87c3632) + +running 1 test +test tests::test ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + Doc-tests priv_sep + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + +``` diff --git a/src/lib.rs b/src/lib.rs @@ -0,0 +1,419 @@ +//! # `priv_sep` +//! +//! `priv_sep` is a library for privilege separation. +//! It is currently designed around [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) and +//! [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) for OpenBSD-stable—that is correct, `stable` not `current`— +//! but in the future may contain functionality for Linux's +//! [`seccomp(2)`](https://man7.org/linux/man-pages/man2/seccomp.2.html). +//! +//! ## Pledge +//! +//! It is very rare to use the `execpromises` parameter, so [`pledge`] only relies on [`Promise`]s. +//! Additionally this function cannot be used to invoke `pledge(2)` with a `promises` value of `""`. +//! +//! ## Unveil +//! +//! Unlike `pledge(2)` which allows a large quantity of duplicate `promises` to be provided, `unveil(2)` allows a maximum +//! of four `permissions` to be passed. For this reason, there are dedicated functions for each quantity of +//! [`Permission`]s. +//! +//! ## Errors +//! +//! Any error returned from the underlying system call is propagated via +//! [`c_int`](https://doc.rust-lang.org/core/ffi/type.c_int.html). Note for both `pledge(2)` and `unveil(2)` duplicates +//! are ignored, so it is not an error to pass in duplicate values for their corresponding functions in this crate. +#![deny( + unsafe_code, + unused, + warnings, + clippy::all, + clippy::cargo, + clippy::complexity, + clippy::correctness, + clippy::nursery, + clippy::pedantic, + clippy::perf, + clippy::restriction, + clippy::style, + clippy::suspicious +)] +#![allow( + clippy::arithmetic_side_effects, + clippy::blanket_clippy_restriction_lints, + clippy::implicit_return, + clippy::missing_trait_methods, + clippy::unseparated_literal_suffix +)] +#![cfg(target_os = "openbsd")] +extern crate alloc; +use alloc::ffi::{CString, NulError}; +use core::ffi::{c_char, c_int}; +use core::fmt::{self, Display, Formatter}; +use core::ptr; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; +use Promise::{ + Audio, Bpf, Chown, Cpath, Disklabel, Dns, Dpath, Drm, Error, Exec, Fattr, Flock, Getpw, Id, + Inet, Mcast, Pf, Proc, ProtExec, Ps, Recvfd, Route, Rpath, Sendfd, Settime, Stdio, Tape, + Tmppath, Tty, Unix, Unveil, Video, Vminfo, Vmm, Wpath, Wroute, +}; +#[non_exhaustive] +/// A `promise` to [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2). +pub enum Promise { + /// Consult `pledge(2)`. + Audio, + /// Consult `pledge(2)`. + Bpf, + /// Consult `pledge(2)`. + Chown, + /// Consult `pledge(2)`. + Cpath, + /// Consult `pledge(2)`. + Disklabel, + /// Consult `pledge(2)`. + Dns, + /// Consult `pledge(2)`. + Dpath, + /// Consult `pledge(2)`. + Drm, + /// Consult `pledge(2)`. + Error, + /// Consult `pledge(2)`. + Exec, + /// Consult `pledge(2)`. + Fattr, + /// Consult `pledge(2)`. + Flock, + /// Consult `pledge(2)`. + Getpw, + /// Consult `pledge(2)`. + Id, + /// Consult `pledge(2)`. + Inet, + /// Consult `pledge(2)`. + Mcast, + /// Consult `pledge(2)`. + Pf, + /// Consult `pledge(2)`. + Proc, + /// Consult `pledge(2)`. + ProtExec, + /// Consult `pledge(2)`. + Ps, + /// Consult `pledge(2)`. + Recvfd, + /// Consult `pledge(2)`. + Route, + /// Consult `pledge(2)`. + Rpath, + /// Consult `pledge(2)`. + Sendfd, + /// Consult `pledge(2)`. + Settime, + /// Consult `pledge(2)`. + Stdio, + /// Consult `pledge(2)`. + Tape, + /// Consult `pledge(2)`. + Tmppath, + /// Consult `pledge(2)`. + Tty, + /// Consult `pledge(2)`. + Unix, + /// Consult `pledge(2)`. + Unveil, + /// Consult `pledge(2)`. + Video, + /// Consult `pledge(2)`. + Vminfo, + /// Consult `pledge(2)`. + Vmm, + /// Consult `pledge(2)`. + Wpath, + /// Consult `pledge(2)`. + Wroute, +} +/// Invokes [`pledge(2)`](https://man.openbsd.org/amd64/pledge.2) always passing in +/// `NULL` for `execpromises`. When no [`Promise`]s are passed, then `NULL` is passed +/// for `promises`. There is no way to pass `""` for `promises`. Like the system call +/// it wraps, duplicates are ignored. +/// +/// # Errors +/// +/// Will return [`c_int`](https://doc.rust-lang.org/stable/core/ffi/type.c_int.html) iff +/// `pledge(2)` does. +#[inline] +#[allow(unsafe_code, clippy::indexing_slicing, clippy::into_iter_on_ref)] +pub fn pledge<const N: usize>(promises: &[Promise; N]) -> Result<(), c_int> { + extern "C" { + fn pledge(promises: *const c_char, execpromises: *const c_char) -> c_int; + } + let arg: CString; + let ptr = if promises.is_empty() { + ptr::null() + } else { + let mut p = Vec::new(); + promises.into_iter().fold((), |_, promise| { + p.extend_from_slice(match *promise { + Audio => b"audio ", + Bpf => b"bpf ", + Chown => b"chown ", + Cpath => b"cpath ", + Disklabel => b"disklabel ", + Dns => b"dns ", + Dpath => b"dpath ", + Drm => b"drm ", + Error => b"error ", + Exec => b"exec ", + Fattr => b"fattr ", + Flock => b"flock ", + Getpw => b"getpw ", + Id => b"id ", + Inet => b"inet ", + Mcast => b"mcast ", + Pf => b"pf ", + Proc => b"proc ", + ProtExec => b"prot_exec ", + Ps => b"ps ", + Recvfd => b"recvfd ", + Route => b"route ", + Rpath => b"rpath ", + Sendfd => b"sendfd ", + Settime => b"settime ", + Stdio => b"stdio ", + Tape => b"tape ", + Tmppath => b"tmppath ", + Tty => b"tty ", + Unix => b"unix ", + Unveil => b"unveil ", + Video => b"video ", + Vminfo => b"vminfo ", + Vmm => b"vmm ", + Wpath => b"wpath ", + Wroute => b"wroute ", + }); + }); + // All promises have a space after them which means + // we must replace the last promise's space with + // 0/nul byte. + let idx = p.len() - 1; + p[idx] = 0; + // SAFETY: + // p was populated above with correct ASCII-encoding of the literal + // values all of which do not have 0 bytes. + // p ends with a 0/nul byte. + arg = unsafe { CString::from_vec_with_nul_unchecked(p) }; + arg.as_ptr() + }; + // SAFETY: + // pledge is an FFI binding; thus requires unsafe code. + // ptr meets the requirements of the pledge(2) call. + match unsafe { pledge(ptr, ptr::null()) } { + 0i32 => Ok(()), + c => Err(c), + } +} +/// A `permission` to [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2). +#[allow(clippy::exhaustive_enums)] +pub enum Permission { + /// Consult `unveil(2)`. + Create, + /// Consult `unveil(2)`. + Execute, + /// Consult `unveil(2)`. + Read, + /// Consult `unveil(2)`. + Write, +} +/// Error returned by the `unveil` functions. +#[allow(clippy::exhaustive_enums)] +#[derive(Debug)] +pub enum UnveilErr { + /// Error propagated from [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2). + CInt(c_int), + /// Error when a path cannot be converted into a + /// [`CString`](https://doc.rust-lang.org/alloc/ffi/struct.CString.html). + NulError(NulError), +} +impl Display for UnveilErr { + #[allow(clippy::ref_patterns)] + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match *self { + Self::CInt(c) => c.fmt(f), + Self::NulError(ref e) => e.fmt(f), + } + } +} +impl std::error::Error for UnveilErr {} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2). +/// +/// # Errors +/// +/// Will return [`c_int`](https://doc.rust-lang.org/stable/core/ffi/type.c_int.html) iff +/// `unveil(2)` does. Returns [`NulError`](https://doc.rust-lang.org/alloc/ffi/struct.NulError.html) +/// iff [`CString::new`](https://doc.rust-lang.org/alloc/ffi/struct.CString.html#method.new) does. +#[inline] +#[allow(unsafe_code, clippy::indexing_slicing, clippy::into_iter_on_ref)] +fn unveil<P: AsRef<Path>, const N: usize>( + path: Option<P>, + permissions: &[Permission; N], +) -> Result<(), UnveilErr> { + extern "C" { + fn unveil(path: *const c_char, permissions: *const c_char) -> c_int; + } + let path_c: CString; + let perm_c: CString; + let (fst, snd) = match path { + Some(path_val) => { + let mut v = Vec::new(); + permissions.into_iter().fold((), |_, p| { + v.push(match *p { + Permission::Create => b'c', + Permission::Execute => b'x', + Permission::Read => b'r', + Permission::Write => b'w', + }); + }); + v.push(0); + match CString::new(path_val.as_ref().as_os_str().as_bytes()) { + Ok(s) => { + path_c = s; + // SAFETY: + // v was populated above with correct ASCII-encoding of the literal + // values all of which do not have 0 bytes. + // v ends with a 0/nul byte. + perm_c = unsafe { CString::from_vec_with_nul_unchecked(v) }; + (path_c.as_ptr(), perm_c.as_ptr()) + } + Err(e) => return Err(UnveilErr::NulError(e)), + } + } + None => (ptr::null(), ptr::null()), + }; + // SAFETY: + // unveil is an FFI binding; thus requires unsafe code. + // fst and snd meet the requirements of the unveil(2) call. + match unsafe { unveil(fst, snd) } { + 0i32 => Ok(()), + c => Err(UnveilErr::CInt(c)), + } +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `NULL` for both `path` and `permissions`. +/// +/// # Errors +/// +/// Will return [`c_int`](https://doc.rust-lang.org/stable/core/ffi/type.c_int.html) iff +/// `unveil(2)` does. +#[allow(clippy::unreachable)] +#[inline] +pub fn unveil_no_more() -> Result<(), c_int> { + unveil::<PathBuf, 0>(None, &[]).map_err(|e| match e { + UnveilErr::CInt(c) => c, + UnveilErr::NulError(_) => unreachable!("There is a bug in unveil."), + }) +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `""` for `permissions`. +/// +/// # Errors +/// +/// Returns [`UnveilErr`] when a problem occurs. +#[inline] +pub fn unveil_no_perms<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> { + unveil(Some(path), &[]) +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perm` for `permissions`. +/// +/// # Errors +/// +/// Returns [`UnveilErr`] when a problem occurs. +#[inline] +pub fn unveil_one_perm<P: AsRef<Path>>(path: P, perm: Permission) -> Result<(), UnveilErr> { + unveil(Some(path), &[perm]) +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perms` for `permissions`. +/// +/// # Errors +/// +/// Returns [`UnveilErr`] when a problem occurs. +#[inline] +pub fn unveil_two_perms<P: AsRef<Path>>(path: P, perms: &[Permission; 2]) -> Result<(), UnveilErr> { + unveil(Some(path), perms) +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `perms` for `permissions`. +/// +/// # Errors +/// +/// Returns [`UnveilErr`] when a problem occurs. +#[inline] +pub fn unveil_three_perms<P: AsRef<Path>>( + path: P, + perms: &[Permission; 3], +) -> Result<(), UnveilErr> { + unveil(Some(path), perms) +} +/// Invokes [`unveil(2)`](https://man.openbsd.org/amd64/unveil.2) by passing `path` for `path` and `"cxrw"` for `permissions`. +/// +/// # Errors +/// +/// Returns [`UnveilErr`] when a problem occurs. +#[inline] +pub fn unveil_all_perms<P: AsRef<Path>>(path: P) -> Result<(), UnveilErr> { + unveil( + Some(path), + &[ + Permission::Create, + Permission::Execute, + Permission::Read, + Permission::Write, + ], + ) +} +#[cfg(test)] +mod tests { + use super::{Permission, Promise}; + // We only have one test since we must force the order of pledge/unveil calls. + // The file /home/zack/foo.txt must exist before calling. + // The file /home/zack/aadkjfasj3s23.2lk3h and the directory /home/zack/aadkjfasj3s23/2lk3h + // must not exist before calling. + #[test] + fn test() { + // This tests that a NULL pledge does nothing. + assert!(super::pledge(&[]).is_ok()); + print!(""); + // This tests that duplicate pledges are ignored. + assert!(super::pledge(&[ + Promise::Stdio, + Promise::Stdio, + Promise::Unveil, + Promise::Rpath + ]) + .is_ok()); + print!(""); + // This tests unveil with no permissions. + assert!(super::unveil_no_perms("/home/zack/foo.txt").is_ok()); + assert!(std::fs::metadata("/home/zack/foo.txt").is_err()); + // This tests unveil with read permissions, duplicates are ignored, + // and one can unveil more permissions (unlike pledge which can only remove promises). + assert!(super::unveil_two_perms( + "/home/zack/foo.txt", + &[Permission::Read, Permission::Read] + ) + .is_ok()); + assert!(std::fs::metadata("/home/zack/foo.txt").is_ok()); + // This tests that calls to unveil on missing files don't error. + assert!(super::unveil_no_perms("/home/zack/aadkjfasj3s23.2lk3h").is_ok()); + // This tests that calls to unveil on missing directories error. + assert!(super::unveil_no_perms("/home/zack/aadkjfasj3s23/2lk3h").is_err()); + // This tests that unveil can no longer be called. + assert!(super::unveil_no_more().is_ok()); + assert!(super::unveil_no_perms("/home/zack/foo.txt").is_err()); + assert!(std::fs::metadata("/home/zack/foo.txt").is_ok()); + // The below tests that Promises can only be removed and not added. + assert!(super::pledge(&[Promise::Stdio]).is_ok()); + assert!(super::pledge(&[Promise::Rpath]).is_err()); + // If the below is uncommented, the program should crash since the above + // call to pledge no longer allows access to the file system. + //_ = std::fs::metadata("/home/zack/foo.txt"); + } +}