priv_sep

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

commit c63d5b114163f56899e75153ee25b475bb623beb
parent 9a8e88d6229ff3848547d754326e06482bb5546b
Author: Zack Newman <zack@philomathiclife.com>
Date:   Tue, 30 Sep 2025 17:43:27 -0600

make no_std. only support certain platforms. add supp groups support

Diffstat:
MCargo.toml | 34++++++++++++++++++++++++++++------
MREADME.md | 13+++++++++++++
Msrc/c.rs | 94+++++++++++++++++++++++++++++++++++++------------------------------------------
Asrc/err.rs | 2264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 750+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/openbsd.rs | 728+++++++++++++++++++++++++++++++++++++++++--------------------------------------
6 files changed, 3134 insertions(+), 749 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Zack Newman <zack@philomathiclife.com>"] -categories = ["external-ffi-bindings", "os::unix-apis"] -description = "FFI for setresuid(2), setresgid(2), chroot(2), pledge(2), and unveil(2)." +categories = ["external-ffi-bindings", "no-std::no-alloc", "os::unix-apis"] +description = "FFI for setgroups(2), setresuid(2), setresgid(2), chroot(2), pledge(2), and unveil(2)." documentation = "https://docs.rs/priv_sep/latest/priv_sep/" edition = "2024" keywords = ["ffi", "openbsd", "privsep", "security", "unix"] @@ -10,7 +10,7 @@ name = "priv_sep" readme = "README.md" repository = "https://git.philomathiclife.com/repos/priv_sep/" rust-version = "1.86.0" -version = "3.0.0-alpha.1.3" +version = "3.0.0-alpha.2.0" [lints.rust] ambiguous_negative_literals = { level = "deny", priority = -1 } @@ -23,7 +23,7 @@ future_incompatible = { level = "deny", priority = -1 } impl_trait_redundant_captures = { level = "deny", priority = -1 } keyword_idents = { level = "deny", priority = -1 } let_underscore = { level = "deny", priority = -1 } -#linker_messages = { level = "deny", priority = -1 } +linker_messages = { level = "deny", priority = -1 } #lossy_provenance_casts = { level = "deny", priority = -1 } macro_use_extern_crate = { level = "deny", priority = -1 } meta_variable_misuse = { level = "deny", priority = -1 } @@ -44,9 +44,11 @@ rust_2021_compatibility = { level = "deny", priority = -1 } rust_2024_compatibility = { level = "deny", priority = -1 } single_use_lifetimes = { level = "deny", priority = -1 } #supertrait_item_shadowing_definition = { level = "deny", priority = -1 } +#supertrait_item_shadowing_usage = { level = "deny", priority = -1 } trivial_casts = { level = "deny", priority = -1 } trivial_numeric_casts = { level = "deny", priority = -1 } unit_bindings = { level = "deny", priority = -1 } +unknown-or-malformed-diagnostic-attributes = { level = "deny", priority = -1 } unnameable_types = { level = "deny", priority = -1 } #unqualified_local_imports = { level = "deny", priority = -1 } unreachable_pub = { level = "deny", priority = -1 } @@ -62,7 +64,6 @@ variant_size_differences = { level = "deny", priority = -1 } warnings = { level = "deny", priority = -1 } [lints.clippy] -all = { level = "deny", priority = -1 } cargo = { level = "deny", priority = -1 } complexity = { level = "deny", priority = -1 } correctness = { level = "deny", priority = -1 } @@ -81,14 +82,35 @@ implicit_return = "allow" min_ident_chars = "allow" missing_trait_methods = "allow" pub_with_shorthand = "allow" +question_mark_used = "allow" redundant_pub_crate = "allow" ref_patterns = "allow" return_and_then = "allow" single_char_lifetime_names = "allow" +semicolon_inside_block = "allow" [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +cargo-args = ["-Zbuild-std=std"] +default-target = "x86_64-unknown-openbsd" +targets = [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "i686-unknown-linux-gnu", + "x86_64-unknown-dragonfly", + "x86_64-unknown-freebsd", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-netbsd" +] [dev-dependencies] tokio = { version = "1.47.1", default-features = false, features = ["macros", "net", "rt"] } + + +### FEATURES ################################################################# + +[features] +default = ["std"] + +# Provide std support. +std = [] diff --git a/README.md b/README.md @@ -6,6 +6,17 @@ `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction. +Note the only platforms that are currently supported are platforms that correspond to the following +`target_os` values: + +* `dragonfly` +* `freebsd` +* `linux` +* `netbsd` +* `openbsd` + +or the `apple` `target_vendor`. + ## `priv_sep` in action for OpenBSD ```rust @@ -25,6 +36,7 @@ async fn main() -> Result<Infallible, PrivDropErr<Error>> { // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`. // Bind to TCP `[::1]:443` as root. + // `setgroups(2)` to drop all supplementary groups. // `setresgid(2)` to the group ID associated with nobody. // `setresuid(2)` to the user ID associated with nobody. // Remove `id` from our `pledge(2)`d promises. @@ -71,6 +83,7 @@ async fn main() -> Result<Infallible, PrivDropErr<Error>> { // Get the user ID and group ID for nobody from `passwd(5)`. // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. // Bind to TCP `[::1]:443` as root. + // `setgroups(2)` to drop all supplementary groups. // `setresgid(2)` to the group ID associated with nobody. // `setresuid(2)` to the user ID associated with nobody. let listener = UserInfo::chroot_then_priv_drop_async("nobody", "/path/chroot/", false, async || { diff --git a/src/c.rs b/src/c.rs @@ -1,31 +1,15 @@ use super::{Gid, Uid, UserInfo}; -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))] -use core::ffi::c_ushort; use core::ffi::{c_char, c_int}; /// Error code when a libc call is successful. pub(crate) const SUCCESS: c_int = 0; -/// `uid_t` and `gid_t`. -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))] -pub type IdT = c_ushort; -/// `uid_t` and `gid_t`. -#[cfg(target_os = "nto")] -pub type IdT = i32; -/// `uid_t` and `gid_t`. -#[cfg(not(any( - target_os = "espidf", - target_os = "horizon", - target_os = "nto", - target_os = "vita" -)))] -pub(crate) type IdT = u32; /// `time_t`. #[cfg(all( any( target_os = "dragonfly", target_os = "freebsd", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ), target_arch = "aarch64", target_pointer_width = "32" @@ -36,9 +20,9 @@ type TimeT = i32; any( target_os = "dragonfly", target_os = "freebsd", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ), not(all(target_arch = "aarch64", target_pointer_width = "32")) ))] @@ -51,25 +35,25 @@ pub(crate) struct Passwd { /// User password. pass: *mut c_char, /// User ID. - uid: IdT, + uid: u32, /// Group ID. - gid: IdT, + gid: u32, /// Password change time. #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ))] change: TimeT, /// User access class. #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ))] class: *mut c_char, /// User information. @@ -82,13 +66,13 @@ pub(crate) struct Passwd { #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "macos", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", + target_vendor = "apple" ))] expire: TimeT, /// Internal fields. - #[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "macos"))] + #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] fields: c_int, } impl Passwd { @@ -103,13 +87,13 @@ impl Passwd { #[expect(unsafe_code, reason = "FFI requires unsafe")] unsafe extern "C" { /// [`getuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getuid.html). - pub(crate) safe fn getuid() -> IdT; + pub(crate) safe fn getuid() -> u32; /// [`geteuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/geteuid.html). - pub(crate) safe fn geteuid() -> IdT; + pub(crate) safe fn geteuid() -> u32; /// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html). - pub(crate) safe fn getgid() -> IdT; + pub(crate) safe fn getgid() -> u32; /// [`getegid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getegid.html). - pub(crate) safe fn getegid() -> IdT; + pub(crate) safe fn getegid() -> u32; /// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html). #[cfg(any( target_os = "dragonfly", @@ -117,15 +101,10 @@ unsafe extern "C" { target_os = "linux", target_os = "openbsd" ))] - pub(crate) safe fn setresuid(ruid: IdT, euid: IdT, suid: IdT) -> c_int; + pub(crate) safe fn setresuid(ruid: u32, euid: u32, suid: u32) -> c_int; /// [`setuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setuid.html#). - #[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - )))] - pub(crate) safe fn setuid(uid: IdT) -> c_int; + #[cfg(any(target_os = "netbsd", target_vendor = "apple"))] + pub(crate) safe fn setuid(uid: u32) -> c_int; /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html). #[cfg(any( target_os = "dragonfly", @@ -133,15 +112,10 @@ unsafe extern "C" { target_os = "linux", target_os = "openbsd" ))] - pub(crate) safe fn setresgid(rgid: IdT, egid: IdT, sgid: IdT) -> c_int; + pub(crate) safe fn setresgid(rgid: u32, egid: u32, sgid: u32) -> c_int; /// [`setgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setgid.html#). - #[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - )))] - pub(crate) safe fn setgid(gid: IdT) -> c_int; + #[cfg(any(target_os = "netbsd", target_vendor = "apple"))] + pub(crate) safe fn setgid(gid: u32) -> c_int; /// [`chroot(2)`](https://manned.org/chroot.2). pub(crate) fn chroot(path: *const c_char) -> c_int; /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html). @@ -156,10 +130,30 @@ unsafe extern "C" { ) -> c_int; /// [`getpwuid_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwuid_r.html). pub(crate) fn getpwuid_r( - uid: IdT, + uid: u32, pwd: *mut Passwd, buf: *mut c_char, size: usize, result: *mut *mut Passwd, ) -> c_int; + /// [`setgroups(2)`](https://man.openbsd.org/setgroups.2). + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + ))] + pub(crate) fn setgroups(size: c_int, gids: *const u32) -> c_int; + /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html). + #[cfg(target_os = "linux")] + pub(crate) fn setgroups(size: usize, gids: *const u32) -> c_int; + /// [`__errno`](https://github.com/openbsd/src/blob/master/include/errno.h#L54). + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + pub(crate) fn __errno() -> *mut c_int; + /// [`__error`](https://github.com/freebsd/freebsd-src/blob/main/sys/sys/errno.h#L43). + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + pub(crate) fn __error() -> *mut c_int; + /// [`__errno_location`](https://sourceware.org/git/?p=glibc.git;a=blob;f=include/errno.h;h=f0ccaa74dd8bebed83e4adfbf301339efeae3cdb;hb=HEAD#l37). + #[cfg(any(target_os = "dragonfly", target_os = "linux"))] + pub(crate) fn __errno_location() -> *mut c_int; } diff --git a/src/err.rs b/src/err.rs @@ -0,0 +1,2264 @@ +use super::c; +use core::{ + error::Error, + ffi::c_int, + fmt::{self, Display, Formatter}, +}; +#[cfg(feature = "std")] +use std::io::Error as StdErr; +/// `newtype` around a [`c_int`] that is used to contain unknown error numbers in the [`Errno::Other`] +/// variant. +/// +/// Note this cannot be constructed directly since the purpose is to only contain unknown error codes +/// ensuring that any non-[`Errno::Other`] is not equal to an `Errno::Other`. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct OtherErrno(c_int); +impl OtherErrno { + /// Returns the contained error number. + #[inline] + #[must_use] + pub const fn into_raw(self) -> c_int { + self.0 + } +} +impl From<OtherErrno> for c_int { + #[inline] + fn from(value: OtherErrno) -> Self { + value.0 + } +} +/// Error returned from calls to the system's libc. +/// +/// Note this contains variants associated with all supported targets; thus for a given target, +/// it's likely a non-empty subset of the variants will never be returned. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Errno { + /// Operation not permitted. + EPERM, + /// No such file or directory. + ENOENT, + /// No such process. + ESRCH, + /// Interrupted system call. + EINTR, + /// Input/output error. + EIO, + /// Device not configured. + ENXIO, + /// Argument list too long. + E2BIG, + /// Exec format error. + ENOEXEC, + /// Bad file descriptor. + EBADF, + /// No child processes. + ECHILD, + /// Resource deadlock avoided. + EDEADLK, + /// Cannot allocate memory. + ENOMEM, + /// Permission denied. + EACCES, + /// Bad address. + EFAULT, + /// Block device required. + ENOTBLK, + /// Device busy. + EBUSY, + /// File exists. + EEXIST, + /// Cross-device link. + EXDEV, + /// Operation not supported by device. + ENODEV, + /// Not a directory. + ENOTDIR, + /// Is a directory. + EISDIR, + /// Invalid argument. + EINVAL, + /// Too many open files in system. + ENFILE, + /// Too many open files. + EMFILE, + /// Inappropriate ioctl for device. + ENOTTY, + /// Text file busy. + ETXTBSY, + /// File too large. + EFBIG, + /// No space left on device. + ENOSPC, + /// Illegal seek. + ESPIPE, + /// Read-only file system. + EROFS, + /// Too many links. + EMLINK, + /// Broken pipe. + EPIPE, + /// Numerical argument out of domain. + EDOM, + /// Result too large. + ERANGE, + /// Operation would block. + EWOULDBLOCK, + /// Operation now in progress. + EINPROGRESS, + /// Operation already in progress. + EALREADY, + /// Socket operation on non-socket. + ENOTSOCK, + /// Destination address required. + EDESTADDRREQ, + /// Message too long. + EMSGSIZE, + /// Protocol wrong type for socket. + EPROTOTYPE, + /// Protocol not available. + ENOPROTOOPT, + /// Protocol not supported. + EPROTONOSUPPORT, + /// Socket type not supported. + ESOCKTNOSUPPORT, + /// Operation not supported. + EOPNOTSUPP, + /// Protocol family not supported. + EPFNOSUPPORT, + /// Address family not supported by protocol family. + EAFNOSUPPORT, + /// Address already in use. + EADDRINUSE, + /// Can't assign requested address. + EADDRNOTAVAIL, + /// Network is down. + ENETDOWN, + /// Network is unreachable. + ENETUNREACH, + /// Network dropped connection on reset. + ENETRESET, + /// Software caused connection abort. + ECONNABORTED, + /// Connection reset by peer. + ECONNRESET, + /// No buffer space available. + ENOBUFS, + /// Socket is already connected. + EISCONN, + /// Socket is not connected. + ENOTCONN, + /// Can't send after socket shutdown. + ESHUTDOWN, + /// Too many references: can't splice. + ETOOMANYREFS, + /// Operation timed out. + ETIMEDOUT, + /// Connection refused. + ECONNREFUSED, + /// Too many levels of symbolic links. + ELOOP, + /// File name too long. + ENAMETOOLONG, + /// Host is down. + EHOSTDOWN, + /// No route to host. + EHOSTUNREACH, + /// Directory not empty. + ENOTEMPTY, + /// Too many processes. + EPROCLIM, + /// Too many users. + EUSERS, + /// Disk quota exceeded. + EDQUOT, + /// Stale NFS file handle. + ESTALE, + /// Too many levels of remote in path. + EREMOTE, + /// RPC struct is bad. + EBADRPC, + /// RPC version wrong. + ERPCMISMATCH, + /// RPC program not available. + EPROGUNAVAIL, + /// Program version wrong. + EPROGMISMATCH, + /// Bad procedure for program. + EPROCUNAVAIL, + /// No locks available. + ENOLCK, + /// Function not implemented. + ENOSYS, + /// Inappropriate file type or format. + EFTYPE, + /// Authentication error. + EAUTH, + /// Need authenticator. + ENEEDAUTH, + /// IPsec processing failure. + #[expect(clippy::doc_markdown, reason = "false positive")] + EIPSEC, + /// Attribute not found. + ENOATTR, + /// Illegal byte sequence. + EILSEQ, + /// No medium found. + ENOMEDIUM, + /// Wrong medium type. + EMEDIUMTYPE, + /// Value too large to be stored in data type. + EOVERFLOW, + /// Operation canceled. + ECANCELED, + /// Identifier removed. + EIDRM, + /// No message of desired type. + ENOMSG, + /// Not supported. + ENOTSUP, + /// Bad message. + EBADMSG, + /// State not recoverable. + ENOTRECOVERABLE, + /// Previous owner died. + EOWNERDEAD, + /// Protocol error. + EPROTO, + /// Programming error. + EDOOFUS, + /// Multihop attempted. + EMULTIHOP, + /// Link has been severed. + ENOLINK, + /// Capabilities insufficient. + ENOTCAPABLE, + /// Not permitted in capability mode. + ECAPMODE, + /// Integrity check failed. + EINTEGRITY, + /// Channel number out of range. + ECHRNG, + /// Level 2 not synchronized. + EL2NSYNC, + /// Level 3 halted. + EL3HLT, + /// Level 3 reset. + EL3RST, + /// Link number out of range. + ELNRNG, + /// Protocol driver not attached. + EUNATCH, + /// No CSI structure available. + ENOCSI, + /// Level 2 halted. + EL2HLT, + /// Invalid exchange. + EBADE, + /// Invalid request descriptor. + EBADR, + /// Exchange full. + EXFULL, + /// No anode. + ENOANO, + /// Invalid request code. + EBADRQC, + /// Invalid slot. + EBADSLT, + /// Bad font file format. + EBFONT, + /// Device not a stream. + ENOSTR, + /// No data available. + ENODATA, + /// Timer expired. + ETIME, + /// Out of streams resources. + ENOSR, + /// Machine is not on the network. + ENONET, + /// Package not installed. + ENOPKG, + /// Advertise error. + EADV, + /// Srmount error. + ESRMNT, + /// Communication error on send. + ECOMM, + /// RFS specific error. + EDOTDOT, + /// Name not unique on network. + ENOTUNIQ, + /// File descriptor in bad state. + EBADFD, + /// Remote address changed. + EREMCHG, + /// Can not access a needed shared library. + ELIBACC, + /// Accessing a corrupted shared library. + ELIBBAD, + /// .lib section in a.out corrupted. + ELIBSCN, + /// Attempting to link in too many shared libraries. + ELIBMAX, + /// Cannot exec a shared library directly. + ELIBEXEC, + /// Interrupted system call should be restarted. + ERESTART, + /// Streams pipe error. + ESTRPIPE, + /// Structure needs cleaning. + EUCLEAN, + /// Not a XENIX named type file. + ENOTNAM, + /// No XENIX semaphores available. + ENAVAIL, + /// Is a named type file. + EISNAM, + /// Remote I/O error. + EREMOTEIO, + /// Required key not available. + ENOKEY, + /// Key has expired. + EKEYEXPIRED, + /// Key has been revoked. + EKEYREVOKED, + /// Key was rejected by service. + EKEYREJECTED, + /// Operation not possible due to RF-kill. + ERFKILL, + /// Memory page has hardware error. + EHWPOISON, + /// Device power is off. + EPWROFF, + /// Device error, e.g. paper out. + EDEVERR, + /// Bad executable. + EBADEXEC, + /// Bad CPU type in executable. + EBADARCH, + /// Shared library version mismatch. + ESHLIBVERS, + /// Malformed Macho file. + EBADMACHO, + /// No such policy registered. + ENOPOLICY, + /// Interface output queue is full. + EQFULL, + /// All other error codes. + /// + /// Note this is guaranteed to contain an error code that is not the same as any of the above + /// variants. + Other(OtherErrno), +} +impl Errno { + /// Returns a `Self` equivalent to `code`. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::Errno; + /// assert_eq!(Errno::from_raw(1), Errno::EPERM); + /// ``` + #[expect(clippy::too_many_lines, reason = "large match expression")] + #[inline] + #[must_use] + pub const fn from_raw(code: c_int) -> Self { + #[cfg(target_os = "dragonfly")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EDEADLK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EWOULDBLOCK, + 36 => Self::EINPROGRESS, + 37 => Self::EALREADY, + 38 => Self::ENOTSOCK, + 39 => Self::EDESTADDRREQ, + 40 => Self::EMSGSIZE, + 41 => Self::EPROTOTYPE, + 42 => Self::ENOPROTOOPT, + 43 => Self::EPROTONOSUPPORT, + 44 => Self::ESOCKTNOSUPPORT, + 45 => Self::EOPNOTSUPP, + 46 => Self::EPFNOSUPPORT, + 47 => Self::EAFNOSUPPORT, + 48 => Self::EADDRINUSE, + 49 => Self::EADDRNOTAVAIL, + 50 => Self::ENETDOWN, + 51 => Self::ENETUNREACH, + 52 => Self::ENETRESET, + 53 => Self::ECONNABORTED, + 54 => Self::ECONNRESET, + 55 => Self::ENOBUFS, + 56 => Self::EISCONN, + 57 => Self::ENOTCONN, + 58 => Self::ESHUTDOWN, + 59 => Self::ETOOMANYREFS, + 60 => Self::ETIMEDOUT, + 61 => Self::ECONNREFUSED, + 62 => Self::ELOOP, + 63 => Self::ENAMETOOLONG, + 64 => Self::EHOSTDOWN, + 65 => Self::EHOSTUNREACH, + 66 => Self::ENOTEMPTY, + 67 => Self::EPROCLIM, + 68 => Self::EUSERS, + 69 => Self::EDQUOT, + 70 => Self::ESTALE, + 71 => Self::EREMOTE, + 72 => Self::EBADRPC, + 73 => Self::ERPCMISMATCH, + 74 => Self::EPROGUNAVAIL, + 75 => Self::EPROGMISMATCH, + 76 => Self::EPROCUNAVAIL, + 77 => Self::ENOLCK, + 78 => Self::ENOSYS, + 79 => Self::EFTYPE, + 80 => Self::EAUTH, + 81 => Self::ENEEDAUTH, + 82 => Self::EIDRM, + 83 => Self::ENOMSG, + 84 => Self::EOVERFLOW, + 85 => Self::ECANCELED, + 86 => Self::EILSEQ, + 87 => Self::ENOATTR, + 88 => Self::EDOOFUS, + 89 => Self::EBADMSG, + 90 => Self::EMULTIHOP, + 91 => Self::ENOLINK, + 92 => Self::EPROTO, + 93 => Self::ENOMEDIUM, + 94 => Self::ENOTRECOVERABLE, + 95 => Self::EOWNERDEAD, + _ => Self::Other(OtherErrno(code)), + } + #[cfg(target_os = "freebsd")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EDEADLK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EWOULDBLOCK, + 36 => Self::EINPROGRESS, + 37 => Self::EALREADY, + 38 => Self::ENOTSOCK, + 39 => Self::EDESTADDRREQ, + 40 => Self::EMSGSIZE, + 41 => Self::EPROTOTYPE, + 42 => Self::ENOPROTOOPT, + 43 => Self::EPROTONOSUPPORT, + 44 => Self::ESOCKTNOSUPPORT, + 45 => Self::EOPNOTSUPP, + 46 => Self::EPFNOSUPPORT, + 47 => Self::EAFNOSUPPORT, + 48 => Self::EADDRINUSE, + 49 => Self::EADDRNOTAVAIL, + 50 => Self::ENETDOWN, + 51 => Self::ENETUNREACH, + 52 => Self::ENETRESET, + 53 => Self::ECONNABORTED, + 54 => Self::ECONNRESET, + 55 => Self::ENOBUFS, + 56 => Self::EISCONN, + 57 => Self::ENOTCONN, + 58 => Self::ESHUTDOWN, + 59 => Self::ETOOMANYREFS, + 60 => Self::ETIMEDOUT, + 61 => Self::ECONNREFUSED, + 62 => Self::ELOOP, + 63 => Self::ENAMETOOLONG, + 64 => Self::EHOSTDOWN, + 65 => Self::EHOSTUNREACH, + 66 => Self::ENOTEMPTY, + 67 => Self::EPROCLIM, + 68 => Self::EUSERS, + 69 => Self::EDQUOT, + 70 => Self::ESTALE, + 71 => Self::EREMOTE, + 72 => Self::EBADRPC, + 73 => Self::ERPCMISMATCH, + 74 => Self::EPROGUNAVAIL, + 75 => Self::EPROGMISMATCH, + 76 => Self::EPROCUNAVAIL, + 77 => Self::ENOLCK, + 78 => Self::ENOSYS, + 79 => Self::EFTYPE, + 80 => Self::EAUTH, + 81 => Self::ENEEDAUTH, + 82 => Self::EIDRM, + 83 => Self::ENOMSG, + 84 => Self::EOVERFLOW, + 85 => Self::ECANCELED, + 86 => Self::EILSEQ, + 87 => Self::ENOATTR, + 88 => Self::EDOOFUS, + 89 => Self::EBADMSG, + 90 => Self::EMULTIHOP, + 91 => Self::ENOLINK, + 92 => Self::EPROTO, + 93 => Self::ENOTCAPABLE, + 94 => Self::ECAPMODE, + 95 => Self::ENOTRECOVERABLE, + 96 => Self::EOWNERDEAD, + 97 => Self::EINTEGRITY, + _ => Self::Other(OtherErrno(code)), + } + #[cfg(target_vendor = "apple")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EDEADLK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EWOULDBLOCK, + 36 => Self::EINPROGRESS, + 37 => Self::EALREADY, + 38 => Self::ENOTSOCK, + 39 => Self::EDESTADDRREQ, + 40 => Self::EMSGSIZE, + 41 => Self::EPROTOTYPE, + 42 => Self::ENOPROTOOPT, + 43 => Self::EPROTONOSUPPORT, + 44 => Self::ESOCKTNOSUPPORT, + 45 => Self::ENOTSUPP, + 46 => Self::EPFNOSUPPORT, + 47 => Self::EAFNOSUPPORT, + 48 => Self::EADDRINUSE, + 49 => Self::EADDRNOTAVAIL, + 50 => Self::ENETDOWN, + 51 => Self::ENETUNREACH, + 52 => Self::ENETRESET, + 53 => Self::ECONNABORTED, + 54 => Self::ECONNRESET, + 55 => Self::ENOBUFS, + 56 => Self::EISCONN, + 57 => Self::ENOTCONN, + 58 => Self::ESHUTDOWN, + 59 => Self::ETOOMANYREFS, + 60 => Self::ETIMEDOUT, + 61 => Self::ECONNREFUSED, + 62 => Self::ELOOP, + 63 => Self::ENAMETOOLONG, + 64 => Self::EHOSTDOWN, + 65 => Self::EHOSTUNREACH, + 66 => Self::ENOTEMPTY, + 67 => Self::EPROCLIM, + 68 => Self::EUSERS, + 69 => Self::EDQUOT, + 70 => Self::ESTALE, + 71 => Self::EREMOTE, + 72 => Self::EBADRPC, + 73 => Self::ERPCMISMATCH, + 74 => Self::EPROGUNAVAIL, + 75 => Self::EPROGMISMATCH, + 76 => Self::EPROCUNAVAIL, + 77 => Self::ENOLCK, + 78 => Self::ENOSYS, + 79 => Self::EFTYPE, + 80 => Self::EAUTH, + 81 => Self::ENEEDAUTH, + 82 => Self::EPWROFF, + 83 => Self::EDEVERR, + 84 => Self::EOVERFLOW, + 85 => Self::EBADEXEC, + 86 => Self::EBADARCH, + 87 => Self::ESHLIBVERS, + 88 => Self::EBADMACHO, + 89 => Self::ECANCELED, + 90 => Self::EIDRM, + 91 => Self::ENOMSG, + 92 => Self::EILSEQ, + 93 => Self::ENOATTR, + 94 => Self::EBADMSG, + 95 => Self::EMULTIHOP, + 96 => Self::ENODATA, + 97 => Self::ENOLINK, + 98 => Self::ENOSR, + 99 => Self::ENOSTR, + 100 => Self::EPROTO, + 101 => Self::ETIME, + 102 => Self::EOPNOTSUPP, + 103 => Self::ENOPOLICY, + 104 => Self::ENOTRECOVERABLE, + 105 => Self::EOWNERDEAD, + 106 => Self::EQFULL, + _ => Self::Other(OtherErrno(code)), + } + #[cfg(target_os = "netbsd")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EDEADLK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EWOULDBLOCK, + 36 => Self::EINPROGRESS, + 37 => Self::EALREADY, + 38 => Self::ENOTSOCK, + 39 => Self::EDESTADDRREQ, + 40 => Self::EMSGSIZE, + 41 => Self::EPROTOTYPE, + 42 => Self::ENOPROTOOPT, + 43 => Self::EPROTONOSUPPORT, + 44 => Self::ESOCKTNOSUPPORT, + 45 => Self::EOPNOTSUPP, + 46 => Self::EPFNOSUPPORT, + 47 => Self::EAFNOSUPPORT, + 48 => Self::EADDRINUSE, + 49 => Self::EADDRNOTAVAIL, + 50 => Self::ENETDOWN, + 51 => Self::ENETUNREACH, + 52 => Self::ENETRESET, + 53 => Self::ECONNABORTED, + 54 => Self::ECONNRESET, + 55 => Self::ENOBUFS, + 56 => Self::EISCONN, + 57 => Self::ENOTCONN, + 58 => Self::ESHUTDOWN, + 59 => Self::ETOOMANYREFS, + 60 => Self::ETIMEDOUT, + 61 => Self::ECONNREFUSED, + 62 => Self::ELOOP, + 63 => Self::ENAMETOOLONG, + 64 => Self::EHOSTDOWN, + 65 => Self::EHOSTUNREACH, + 66 => Self::ENOTEMPTY, + 67 => Self::EPROCLIM, + 68 => Self::EUSERS, + 69 => Self::EDQUOT, + 70 => Self::ESTALE, + 71 => Self::EREMOTE, + 72 => Self::EBADRPC, + 73 => Self::ERPCMISMATCH, + 74 => Self::EPROGUNAVAIL, + 75 => Self::EPROGMISMATCH, + 76 => Self::EPROCUNAVAIL, + 77 => Self::ENOLCK, + 78 => Self::ENOSYS, + 79 => Self::EFTYPE, + 80 => Self::EAUTH, + 81 => Self::ENEEDAUTH, + 82 => Self::EIDRM, + 83 => Self::ENOMSG, + 84 => Self::EOVERFLOW, + 85 => Self::EILSEQ, + 86 => Self::ENOTSUP, + 87 => Self::ECANCELED, + 88 => Self::EBADMSG, + 89 => Self::ENODATA, + 90 => Self::ENOSR, + 91 => Self::ENOSTR, + 92 => Self::ETIME, + 93 => Self::ENOATTR, + 94 => Self::EMULTIHOP, + 95 => Self::ENOLINK, + 96 => Self::EPROTO, + 97 => Self::EOWNERDEAD, + 98 => Self::ENOTRECOVERABLE, + _ => Self::Other(OtherErrno(code)), + } + #[cfg(target_os = "openbsd")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EDEADLK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EWOULDBLOCK, + 36 => Self::EINPROGRESS, + 37 => Self::EALREADY, + 38 => Self::ENOTSOCK, + 39 => Self::EDESTADDRREQ, + 40 => Self::EMSGSIZE, + 41 => Self::EPROTOTYPE, + 42 => Self::ENOPROTOOPT, + 43 => Self::EPROTONOSUPPORT, + 44 => Self::ESOCKTNOSUPPORT, + 45 => Self::EOPNOTSUPP, + 46 => Self::EPFNOSUPPORT, + 47 => Self::EAFNOSUPPORT, + 48 => Self::EADDRINUSE, + 49 => Self::EADDRNOTAVAIL, + 50 => Self::ENETDOWN, + 51 => Self::ENETUNREACH, + 52 => Self::ENETRESET, + 53 => Self::ECONNABORTED, + 54 => Self::ECONNRESET, + 55 => Self::ENOBUFS, + 56 => Self::EISCONN, + 57 => Self::ENOTCONN, + 58 => Self::ESHUTDOWN, + 59 => Self::ETOOMANYREFS, + 60 => Self::ETIMEDOUT, + 61 => Self::ECONNREFUSED, + 62 => Self::ELOOP, + 63 => Self::ENAMETOOLONG, + 64 => Self::EHOSTDOWN, + 65 => Self::EHOSTUNREACH, + 66 => Self::ENOTEMPTY, + 67 => Self::EPROCLIM, + 68 => Self::EUSERS, + 69 => Self::EDQUOT, + 70 => Self::ESTALE, + 71 => Self::EREMOTE, + 72 => Self::EBADRPC, + 73 => Self::ERPCMISMATCH, + 74 => Self::EPROGUNAVAIL, + 75 => Self::EPROGMISMATCH, + 76 => Self::EPROCUNAVAIL, + 77 => Self::ENOLCK, + 78 => Self::ENOSYS, + 79 => Self::EFTYPE, + 80 => Self::EAUTH, + 81 => Self::ENEEDAUTH, + 82 => Self::EIPSEC, + 83 => Self::ENOATTR, + 84 => Self::EILSEQ, + 85 => Self::ENOMEDIUM, + 86 => Self::EMEDIUMTYPE, + 87 => Self::EOVERFLOW, + 88 => Self::ECANCELED, + 89 => Self::EIDRM, + 90 => Self::ENOMSG, + 91 => Self::ENOTSUP, + 92 => Self::EBADMSG, + 93 => Self::ENOTRECOVERABLE, + 94 => Self::EOWNERDEAD, + 95 => Self::EPROTO, + _ => Self::Other(OtherErrno(code)), + } + #[cfg(target_os = "linux")] + match code { + 1 => Self::EPERM, + 2 => Self::ENOENT, + 3 => Self::ESRCH, + 4 => Self::EINTR, + 5 => Self::EIO, + 6 => Self::ENXIO, + 7 => Self::E2BIG, + 8 => Self::ENOEXEC, + 9 => Self::EBADF, + 10 => Self::ECHILD, + 11 => Self::EWOULDBLOCK, + 12 => Self::ENOMEM, + 13 => Self::EACCES, + 14 => Self::EFAULT, + 15 => Self::ENOTBLK, + 16 => Self::EBUSY, + 17 => Self::EEXIST, + 18 => Self::EXDEV, + 19 => Self::ENODEV, + 20 => Self::ENOTDIR, + 21 => Self::EISDIR, + 22 => Self::EINVAL, + 23 => Self::ENFILE, + 24 => Self::EMFILE, + 25 => Self::ENOTTY, + 26 => Self::ETXTBSY, + 27 => Self::EFBIG, + 28 => Self::ENOSPC, + 29 => Self::ESPIPE, + 30 => Self::EROFS, + 31 => Self::EMLINK, + 32 => Self::EPIPE, + 33 => Self::EDOM, + 34 => Self::ERANGE, + 35 => Self::EDEADLK, + 36 => Self::ENAMETOOLONG, + 37 => Self::ENOLCK, + 38 => Self::ENOSYS, + 39 => Self::ENOTEMPTY, + 40 => Self::ELOOP, + 42 => Self::ENOMSG, + 43 => Self::EIDRM, + 44 => Self::ECHRNG, + 45 => Self::EL2NSYNC, + 46 => Self::EL3HLT, + 47 => Self::EL3RST, + 48 => Self::ELNRNG, + 49 => Self::EUNATCH, + 50 => Self::ENOCSI, + 51 => Self::EL2HLT, + 52 => Self::EBADE, + 53 => Self::EBADR, + 54 => Self::EXFULL, + 55 => Self::ENOANO, + 56 => Self::EBADRQC, + 57 => Self::EBADSLT, + 59 => Self::EBFONT, + 60 => Self::ENOSTR, + 61 => Self::ENODATA, + 62 => Self::ETIME, + 63 => Self::ENOSR, + 64 => Self::ENONET, + 65 => Self::ENOPKG, + 66 => Self::EREMOTE, + 67 => Self::ENOLINK, + 68 => Self::EADV, + 69 => Self::ESRMNT, + 70 => Self::ECOMM, + 71 => Self::EPROTO, + 72 => Self::EMULTIHOP, + 73 => Self::EDOTDOT, + 74 => Self::EBADMSG, + 75 => Self::EOVERFLOW, + 76 => Self::ENOTUNIQ, + 77 => Self::EBADFD, + 78 => Self::EREMCHG, + 79 => Self::ELIBACC, + 80 => Self::ELIBBAD, + 81 => Self::ELIBSCN, + 82 => Self::ELIBMAX, + 83 => Self::ELIBEXEC, + 84 => Self::EILSEQ, + 85 => Self::ERESTART, + 86 => Self::ESTRPIPE, + 87 => Self::EUSERS, + 88 => Self::ENOTSOCK, + 89 => Self::EDESTADDRREQ, + 90 => Self::EMSGSIZE, + 91 => Self::EPROTOTYPE, + 92 => Self::ENOPROTOOPT, + 93 => Self::EPROTONOSUPPORT, + 94 => Self::ESOCKTNOSUPPORT, + 95 => Self::EOPNOTSUPP, + 96 => Self::EPFNOSUPPORT, + 97 => Self::EAFNOSUPPORT, + 98 => Self::EADDRINUSE, + 99 => Self::EADDRNOTAVAIL, + 100 => Self::ENETDOWN, + 101 => Self::ENETUNREACH, + 102 => Self::ENETRESET, + 103 => Self::ECONNABORTED, + 104 => Self::ECONNRESET, + 105 => Self::ENOBUFS, + 106 => Self::EISCONN, + 107 => Self::ENOTCONN, + 108 => Self::ESHUTDOWN, + 109 => Self::ETOOMANYREFS, + 110 => Self::ETIMEDOUT, + 111 => Self::ECONNREFUSED, + 112 => Self::EHOSTDOWN, + 113 => Self::EHOSTUNREACH, + 114 => Self::EALREADY, + 115 => Self::EINPROGRESS, + 116 => Self::ESTALE, + 117 => Self::EUCLEAN, + 118 => Self::ENOTNAM, + 119 => Self::ENAVAIL, + 120 => Self::EISNAM, + 121 => Self::EREMOTEIO, + 122 => Self::EDQUOT, + 123 => Self::ENOMEDIUM, + 124 => Self::EMEDIUMTYPE, + 125 => Self::ECANCELED, + 126 => Self::ENOKEY, + 127 => Self::EKEYEXPIRED, + 128 => Self::EKEYREVOKED, + 129 => Self::EKEYREJECTED, + 130 => Self::EOWNERDEAD, + 131 => Self::ENOTRECOVERABLE, + 132 => Self::ERFKILL, + 133 => Self::EHWPOISON, + _ => Self::Other(OtherErrno(code)), + } + } + /// Returns the raw error code `self` is equivalent to. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::Errno; + /// assert_eq!(Errno::EPERM.into_raw(), 1); + /// ``` + #[expect(clippy::too_many_lines, reason = "large match expression")] + #[inline] + #[must_use] + pub const fn into_raw(self) -> c_int { + /// Essentially `unreachable` but `const`. + macro_rules! impossible { + () => { + panic!("there is a bug in priv_sep::Errno. Impossible variant for the target.") + }; + } + #[cfg(target_os = "dragonfly")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EDEADLK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EWOULDBLOCK => 35, + Self::EINPROGRESS => 36, + Self::EALREADY => 37, + Self::ENOTSOCK => 38, + Self::EDESTADDRREQ => 39, + Self::EMSGSIZE => 40, + Self::EPROTOTYPE => 41, + Self::ENOPROTOOPT => 42, + Self::EPROTONOSUPPORT => 43, + Self::ESOCKTNOSUPPORT => 44, + Self::EOPNOTSUPP => 45, + Self::EPFNOSUPPORT => 46, + Self::EAFNOSUPPORT => 47, + Self::EADDRINUSE => 48, + Self::EADDRNOTAVAIL => 49, + Self::ENETDOWN => 50, + Self::ENETUNREACH => 51, + Self::ENETRESET => 52, + Self::ECONNABORTED => 53, + Self::ECONNRESET => 54, + Self::ENOBUFS => 55, + Self::EISCONN => 56, + Self::ENOTCONN => 57, + Self::ESHUTDOWN => 58, + Self::ETOOMANYREFS => 59, + Self::ETIMEDOUT => 60, + Self::ECONNREFUSED => 61, + Self::ELOOP => 62, + Self::ENAMETOOLONG => 63, + Self::EHOSTDOWN => 64, + Self::EHOSTUNREACH => 65, + Self::ENOTEMPTY => 66, + Self::EPROCLIM => 67, + Self::EUSERS => 68, + Self::EDQUOT => 69, + Self::ESTALE => 70, + Self::EREMOTE => 71, + Self::EBADRPC => 72, + Self::ERPCMISMATCH => 73, + Self::EPROGUNAVAIL => 74, + Self::EPROGMISMATCH => 75, + Self::EPROCUNAVAIL => 76, + Self::ENOLCK => 77, + Self::ENOSYS => 78, + Self::EFTYPE => 79, + Self::EAUTH => 80, + Self::ENEEDAUTH => 81, + Self::EIDRM => 82, + Self::ENOMSG => 83, + Self::EOVERFLOW => 84, + Self::ECANCELED => 85, + Self::EILSEQ => 86, + Self::ENOATTR => 87, + Self::EDOOFUS => 88, + Self::EBADMSG => 89, + Self::EMULTIHOP => 90, + Self::ENOLINK => 91, + Self::EPROTO => 92, + Self::ENOMEDIUM => 93, + Self::ENOTRECOVERABLE => 94, + Self::EOWNERDEAD => 95, + Self::Other(OtherErrno(code)) => code, + Self::EIPSEC + | Self::EMEDIUMTYPE + | Self::ENOTSUP + | Self::ENOTCAPABLE + | Self::ECAPMODE + | Self::EINTEGRITY + | Self::ECHRNG + | Self::EL2NSYNC + | Self::EL3HLT + | Self::EL3RST + | Self::ELNRNG + | Self::EUNATCH + | Self::ENOCSI + | Self::EL2HLT + | Self::EBADE + | Self::EBADR + | Self::EXFULL + | Self::ENOANO + | Self::EBADRQC + | Self::EBADSLT + | Self::EBFONT + | Self::ENOSTR + | Self::ENODATA + | Self::ETIME + | Self::ENOSR + | Self::ENONET + | Self::ENOPKG + | Self::EADV + | Self::ESRMNT + | Self::ECOMM + | Self::EDOTDOT + | Self::ENOTUNIQ + | Self::EBADFD + | Self::EREMCHG + | Self::ELIBACC + | Self::ELIBBAD + | Self::ELIBSCN + | Self::ELIBMAX + | Self::ELIBEXEC + | Self::ERESTART + | Self::ESTRPIPE + | Self::EUCLEAN + | Self::ENOTNAM + | Self::ENAVAIL + | Self::EISNAM + | Self::EREMOTEIO + | Self::ENOKEY + | Self::EKEYEXPIRED + | Self::EKEYREVOKED + | Self::EKEYREJECTED + | Self::ERFKILL + | Self::EHWPOISON + | Self::EPWROFF + | Self::EDEVERR + | Self::EBADEXEC + | Self::EBADARCH + | Self::ESHLIBVERS + | Self::EBADMACHO + | Self::ENOPOLICY + | Self::EQFULL => impossible!(), + } + #[cfg(target_os = "freebsd")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EDEADLK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EWOULDBLOCK => 35, + Self::EINPROGRESS => 36, + Self::EALREADY => 37, + Self::ENOTSOCK => 38, + Self::EDESTADDRREQ => 39, + Self::EMSGSIZE => 40, + Self::EPROTOTYPE => 41, + Self::ENOPROTOOPT => 42, + Self::EPROTONOSUPPORT => 43, + Self::ESOCKTNOSUPPORT => 44, + Self::EOPNOTSUPP => 45, + Self::EPFNOSUPPORT => 46, + Self::EAFNOSUPPORT => 47, + Self::EADDRINUSE => 48, + Self::EADDRNOTAVAIL => 49, + Self::ENETDOWN => 50, + Self::ENETUNREACH => 51, + Self::ENETRESET => 52, + Self::ECONNABORTED => 53, + Self::ECONNRESET => 54, + Self::ENOBUFS => 55, + Self::EISCONN => 56, + Self::ENOTCONN => 57, + Self::ESHUTDOWN => 58, + Self::ETOOMANYREFS => 59, + Self::ETIMEDOUT => 60, + Self::ECONNREFUSED => 61, + Self::ELOOP => 62, + Self::ENAMETOOLONG => 63, + Self::EHOSTDOWN => 64, + Self::EHOSTUNREACH => 65, + Self::ENOTEMPTY => 66, + Self::EPROCLIM => 67, + Self::EUSERS => 68, + Self::EDQUOT => 69, + Self::ESTALE => 70, + Self::EREMOTE => 71, + Self::EBADRPC => 72, + Self::ERPCMISMATCH => 73, + Self::EPROGUNAVAIL => 74, + Self::EPROGMISMATCH => 75, + Self::EPROCUNAVAIL => 76, + Self::ENOLCK => 77, + Self::ENOSYS => 78, + Self::EFTYPE => 79, + Self::EAUTH => 80, + Self::ENEEDAUTH => 81, + Self::EIDRM => 82, + Self::ENOMSG => 83, + Self::EOVERFLOW => 84, + Self::ECANCELED => 85, + Self::EILSEQ => 86, + Self::ENOATTR => 87, + Self::EDOOFUS => 88, + Self::EBADMSG => 89, + Self::EMULTIHOP => 90, + Self::ENOLINK => 91, + Self::EPROTO => 92, + Self::ENOTCAPABLE => 93, + Self::ECAPMODE => 94, + Self::ENOTRECOVERABLE => 95, + Self::EOWNERDEAD => 96, + Self::EINTEGRITY => 97, + Self::Other(OtherErrno(code)) => code, + Self::EIPSEC + | Self::ENOMEDIUM + | Self::EMEDIUMTYPE + | Self::ENOTSUP + | Self::ECHRNG + | Self::EL2NSYNC + | Self::EL3HLT + | Self::EL3RST + | Self::ELNRNG + | Self::EUNATCH + | Self::ENOCSI + | Self::EL2HLT + | Self::EBADE + | Self::EBADR + | Self::EXFULL + | Self::ENOANO + | Self::EBADRQC + | Self::EBADSLT + | Self::EBFONT + | Self::ENOSTR + | Self::ENODATA + | Self::ETIME + | Self::ENOSR + | Self::ENONET + | Self::ENOPKG + | Self::EADV + | Self::ESRMNT + | Self::ECOMM + | Self::EDOTDOT + | Self::ENOTUNIQ + | Self::EBADFD + | Self::EREMCHG + | Self::ELIBACC + | Self::ELIBBAD + | Self::ELIBSCN + | Self::ELIBMAX + | Self::ELIBEXEC + | Self::ERESTART + | Self::ESTRPIPE + | Self::EUCLEAN + | Self::ENOTNAM + | Self::ENAVAIL + | Self::EISNAM + | Self::EREMOTEIO + | Self::ENOKEY + | Self::EKEYEXPIRED + | Self::EKEYREVOKED + | Self::EKEYREJECTED + | Self::ERFKILL + | Self::EHWPOISON + | Self::EPWROFF + | Self::EDEVERR + | Self::EBADEXEC + | Self::EBADARCH + | Self::ESHLIBVERS + | Self::EBADMACHO + | Self::ENOPOLICY + | Self::EQFULL => impossible!(), + } + #[cfg(target_vendor = "apple")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EDEADLK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EWOULDBLOCK => 35, + Self::EINPROGRESS => 36, + Self::EALREADY => 37, + Self::ENOTSOCK => 38, + Self::EDESTADDRREQ => 39, + Self::EMSGSIZE => 40, + Self::EPROTOTYPE => 41, + Self::ENOPROTOOPT => 42, + Self::EPROTONOSUPPORT => 43, + Self::ESOCKTNOSUPPORT => 44, + Self::ENOTSUPP => 45, + Self::EPFNOSUPPORT => 46, + Self::EAFNOSUPPORT => 47, + Self::EADDRINUSE => 48, + Self::EADDRNOTAVAIL => 49, + Self::ENETDOWN => 50, + Self::ENETUNREACH => 51, + Self::ENETRESET => 52, + Self::ECONNABORTED => 53, + Self::ECONNRESET => 54, + Self::ENOBUFS => 55, + Self::EISCONN => 56, + Self::ENOTCONN => 57, + Self::ESHUTDOWN => 58, + Self::ETOOMANYREFS => 59, + Self::ETIMEDOUT => 60, + Self::ECONNREFUSED => 61, + Self::ELOOP => 62, + Self::ENAMETOOLONG => 63, + Self::EHOSTDOWN => 64, + Self::EHOSTUNREACH => 65, + Self::ENOTEMPTY => 66, + Self::EPROCLIM => 67, + Self::EUSERS => 68, + Self::EDQUOT => 69, + Self::ESTALE => 70, + Self::EREMOTE => 71, + Self::EBADRPC => 72, + Self::ERPCMISMATCH => 73, + Self::EPROGUNAVAIL => 74, + Self::EPROGMISMATCH => 75, + Self::EPROCUNAVAIL => 76, + Self::ENOLCK => 77, + Self::ENOSYS => 78, + Self::EFTYPE => 79, + Self::EAUTH => 80, + Self::ENEEDAUTH => 81, + Self::EPWROFF => 82, + Self::EDEVERR => 83, + Self::EOVERFLOW => 84, + Self::EBADEXEC => 85, + Self::EBADARCH => 86, + Self::ESHLIBVERS => 87, + Self::EBADMACHO => 88, + Self::ECANCELED => 89, + Self::EIDRM => 90, + Self::ENOMSG => 91, + Self::EILSEQ => 92, + Self::ENOATTR => 93, + Self::EBADMSG => 94, + Self::EMULTIHOP => 95, + Self::ENODATA => 96, + Self::ENOLINK => 97, + Self::ENOSR => 98, + Self::ENOSTR => 99, + Self::EPROTO => 100, + Self::ETIME => 101, + Self::EOPNOTSUPP => 102, + Self::ENOPOLICY => 103, + Self::ENOTRECOVERABLE => 104, + Self::EOWNERDEAD => 105, + Self::EQFULL => 106, + Self::Other(OtherErrno(code)) => code, + Self::EIPSEC + | Self::ENOMEDIUM + | Self::EMEDIUMTYPE + | Self::EDOOFUS + | Self::ENOTCAPABLE + | Self::ECAPMODE + | Self::EINTEGRITY + | Self::ECHRNG + | Self::EL2NSYNC + | Self::EL3HLT + | Self::EL3RST + | Self::ELNRNG + | Self::EUNATCH + | Self::ENOCSI + | Self::EL2HLT + | Self::EBADE + | Self::EBADR + | Self::EXFULL + | Self::ENOANO + | Self::EBADRQC + | Self::EBADSLT + | Self::EBFONT + | Self::ENONET + | Self::ENOPKG + | Self::EADV + | Self::ESRMNT + | Self::ECOMM + | Self::EDOTDOT + | Self::ENOTUNIQ + | Self::EBADFD + | Self::EREMCHG + | Self::ELIBACC + | Self::ELIBBAD + | Self::ELIBSCN + | Self::ELIBMAX + | Self::ELIBEXEC + | Self::ERESTART + | Self::ESTRPIPE + | Self::EUCLEAN + | Self::ENOTNAM + | Self::ENAVAIL + | Self::EISNAM + | Self::EREMOTEIO + | Self::ENOKEY + | Self::EKEYEXPIRED + | Self::EKEYREVOKED + | Self::EKEYREJECTED + | Self::ERFKILL + | Self::EHWPOISON => impossible!(), + } + #[cfg(target_os = "netbsd")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EDEADLK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EWOULDBLOCK => 35, + Self::EINPROGRESS => 36, + Self::EALREADY => 37, + Self::ENOTSOCK => 38, + Self::EDESTADDRREQ => 39, + Self::EMSGSIZE => 40, + Self::EPROTOTYPE => 41, + Self::ENOPROTOOPT => 42, + Self::EPROTONOSUPPORT => 43, + Self::ESOCKTNOSUPPORT => 44, + Self::EOPNOTSUPP => 45, + Self::EPFNOSUPPORT => 46, + Self::EAFNOSUPPORT => 47, + Self::EADDRINUSE => 48, + Self::EADDRNOTAVAIL => 49, + Self::ENETDOWN => 50, + Self::ENETUNREACH => 51, + Self::ENETRESET => 52, + Self::ECONNABORTED => 53, + Self::ECONNRESET => 54, + Self::ENOBUFS => 55, + Self::EISCONN => 56, + Self::ENOTCONN => 57, + Self::ESHUTDOWN => 58, + Self::ETOOMANYREFS => 59, + Self::ETIMEDOUT => 60, + Self::ECONNREFUSED => 61, + Self::ELOOP => 62, + Self::ENAMETOOLONG => 63, + Self::EHOSTDOWN => 64, + Self::EHOSTUNREACH => 65, + Self::ENOTEMPTY => 66, + Self::EPROCLIM => 67, + Self::EUSERS => 68, + Self::EDQUOT => 69, + Self::ESTALE => 70, + Self::EREMOTE => 71, + Self::EBADRPC => 72, + Self::ERPCMISMATCH => 73, + Self::EPROGUNAVAIL => 74, + Self::EPROGMISMATCH => 75, + Self::EPROCUNAVAIL => 76, + Self::ENOLCK => 77, + Self::ENOSYS => 78, + Self::EFTYPE => 79, + Self::EAUTH => 80, + Self::ENEEDAUTH => 81, + Self::EIDRM => 82, + Self::ENOMSG => 83, + Self::EOVERFLOW => 84, + Self::EILSEQ => 85, + Self::ENOTSUP => 86, + Self::ECANCELED => 87, + Self::EBADMSG => 88, + Self::ENODATA => 89, + Self::ENOSR => 90, + Self::ENOSTR => 91, + Self::ETIME => 92, + Self::ENOATTR => 93, + Self::EMULTIHOP => 94, + Self::ENOLINK => 95, + Self::EPROTO => 96, + Self::EOWNERDEAD => 97, + Self::ENOTRECOVERABLE => 98, + Self::Other(OtherErrno(code)) => code, + Self::EIPSEC + | Self::ENOMEDIUM + | Self::EMEDIUMTYPE + | Self::EDOOFUS + | Self::ENOTCAPABLE + | Self::ECAPMODE + | Self::EINTEGRITY + | Self::ECHRNG + | Self::EL2NSYNC + | Self::EL3HLT + | Self::EL3RST + | Self::ELNRNG + | Self::EUNATCH + | Self::ENOCSI + | Self::EL2HLT + | Self::EBADE + | Self::EBADR + | Self::EXFULL + | Self::ENOANO + | Self::EBADRQC + | Self::EBADSLT + | Self::EBFONT + | Self::ENONET + | Self::ENOPKG + | Self::EADV + | Self::ESRMNT + | Self::ECOMM + | Self::EDOTDOT + | Self::ENOTUNIQ + | Self::EBADFD + | Self::EREMCHG + | Self::ELIBACC + | Self::ELIBBAD + | Self::ELIBSCN + | Self::ELIBMAX + | Self::ELIBEXEC + | Self::ERESTART + | Self::ESTRPIPE + | Self::EUCLEAN + | Self::ENOTNAM + | Self::ENAVAIL + | Self::EISNAM + | Self::EREMOTEIO + | Self::ENOKEY + | Self::EKEYEXPIRED + | Self::EKEYREVOKED + | Self::EKEYREJECTED + | Self::ERFKILL + | Self::EHWPOISON + | Self::EPWROFF + | Self::EDEVERR + | Self::EBADEXEC + | Self::EBADARCH + | Self::ESHLIBVERS + | Self::EBADMACHO + | Self::ENOPOLICY + | Self::EQFULL => impossible!(), + } + #[cfg(target_os = "openbsd")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EDEADLK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EWOULDBLOCK => 35, + Self::EINPROGRESS => 36, + Self::EALREADY => 37, + Self::ENOTSOCK => 38, + Self::EDESTADDRREQ => 39, + Self::EMSGSIZE => 40, + Self::EPROTOTYPE => 41, + Self::ENOPROTOOPT => 42, + Self::EPROTONOSUPPORT => 43, + Self::ESOCKTNOSUPPORT => 44, + Self::EOPNOTSUPP => 45, + Self::EPFNOSUPPORT => 46, + Self::EAFNOSUPPORT => 47, + Self::EADDRINUSE => 48, + Self::EADDRNOTAVAIL => 49, + Self::ENETDOWN => 50, + Self::ENETUNREACH => 51, + Self::ENETRESET => 52, + Self::ECONNABORTED => 53, + Self::ECONNRESET => 54, + Self::ENOBUFS => 55, + Self::EISCONN => 56, + Self::ENOTCONN => 57, + Self::ESHUTDOWN => 58, + Self::ETOOMANYREFS => 59, + Self::ETIMEDOUT => 60, + Self::ECONNREFUSED => 61, + Self::ELOOP => 62, + Self::ENAMETOOLONG => 63, + Self::EHOSTDOWN => 64, + Self::EHOSTUNREACH => 65, + Self::ENOTEMPTY => 66, + Self::EPROCLIM => 67, + Self::EUSERS => 68, + Self::EDQUOT => 69, + Self::ESTALE => 70, + Self::EREMOTE => 71, + Self::EBADRPC => 72, + Self::ERPCMISMATCH => 73, + Self::EPROGUNAVAIL => 74, + Self::EPROGMISMATCH => 75, + Self::EPROCUNAVAIL => 76, + Self::ENOLCK => 77, + Self::ENOSYS => 78, + Self::EFTYPE => 79, + Self::EAUTH => 80, + Self::ENEEDAUTH => 81, + Self::EIPSEC => 82, + Self::ENOATTR => 83, + Self::EILSEQ => 84, + Self::ENOMEDIUM => 85, + Self::EMEDIUMTYPE => 86, + Self::EOVERFLOW => 87, + Self::ECANCELED => 88, + Self::EIDRM => 89, + Self::ENOMSG => 90, + Self::ENOTSUP => 91, + Self::EBADMSG => 92, + Self::ENOTRECOVERABLE => 93, + Self::EOWNERDEAD => 94, + Self::EPROTO => 95, + Self::Other(OtherErrno(code)) => code, + Self::EDOOFUS + | Self::EMULTIHOP + | Self::ENOLINK + | Self::ENOTCAPABLE + | Self::ECAPMODE + | Self::EINTEGRITY + | Self::ECHRNG + | Self::EL2NSYNC + | Self::EL3HLT + | Self::EL3RST + | Self::ELNRNG + | Self::EUNATCH + | Self::ENOCSI + | Self::EL2HLT + | Self::EBADE + | Self::EBADR + | Self::EXFULL + | Self::ENOANO + | Self::EBADRQC + | Self::EBADSLT + | Self::EBFONT + | Self::ENOSTR + | Self::ENODATA + | Self::ETIME + | Self::ENOSR + | Self::ENONET + | Self::ENOPKG + | Self::EADV + | Self::ESRMNT + | Self::ECOMM + | Self::EDOTDOT + | Self::ENOTUNIQ + | Self::EBADFD + | Self::EREMCHG + | Self::ELIBACC + | Self::ELIBBAD + | Self::ELIBSCN + | Self::ELIBMAX + | Self::ELIBEXEC + | Self::ERESTART + | Self::ESTRPIPE + | Self::EUCLEAN + | Self::ENOTNAM + | Self::ENAVAIL + | Self::EISNAM + | Self::EREMOTEIO + | Self::ENOKEY + | Self::EKEYEXPIRED + | Self::EKEYREVOKED + | Self::EKEYREJECTED + | Self::ERFKILL + | Self::EHWPOISON + | Self::EPWROFF + | Self::EDEVERR + | Self::EBADEXEC + | Self::EBADARCH + | Self::ESHLIBVERS + | Self::EBADMACHO + | Self::ENOPOLICY + | Self::EQFULL => impossible!(), + } + #[cfg(target_os = "linux")] + match self { + Self::EPERM => 1, + Self::ENOENT => 2, + Self::ESRCH => 3, + Self::EINTR => 4, + Self::EIO => 5, + Self::ENXIO => 6, + Self::E2BIG => 7, + Self::ENOEXEC => 8, + Self::EBADF => 9, + Self::ECHILD => 10, + Self::EWOULDBLOCK => 11, + Self::ENOMEM => 12, + Self::EACCES => 13, + Self::EFAULT => 14, + Self::ENOTBLK => 15, + Self::EBUSY => 16, + Self::EEXIST => 17, + Self::EXDEV => 18, + Self::ENODEV => 19, + Self::ENOTDIR => 20, + Self::EISDIR => 21, + Self::EINVAL => 22, + Self::ENFILE => 23, + Self::EMFILE => 24, + Self::ENOTTY => 25, + Self::ETXTBSY => 26, + Self::EFBIG => 27, + Self::ENOSPC => 28, + Self::ESPIPE => 29, + Self::EROFS => 30, + Self::EMLINK => 31, + Self::EPIPE => 32, + Self::EDOM => 33, + Self::ERANGE => 34, + Self::EDEADLK => 35, + Self::ENAMETOOLONG => 36, + Self::ENOLCK => 37, + Self::ENOSYS => 38, + Self::ENOTEMPTY => 39, + Self::ELOOP => 40, + Self::ENOMSG => 42, + Self::EIDRM => 43, + Self::ECHRNG => 44, + Self::EL2NSYNC => 45, + Self::EL3HLT => 46, + Self::EL3RST => 47, + Self::ELNRNG => 48, + Self::EUNATCH => 49, + Self::ENOCSI => 50, + Self::EL2HLT => 51, + Self::EBADE => 52, + Self::EBADR => 53, + Self::EXFULL => 54, + Self::ENOANO => 55, + Self::EBADRQC => 56, + Self::EBADSLT => 57, + Self::EBFONT => 59, + Self::ENOSTR => 60, + Self::ENODATA => 61, + Self::ETIME => 62, + Self::ENOSR => 63, + Self::ENONET => 64, + Self::ENOPKG => 65, + Self::EREMOTE => 66, + Self::ENOLINK => 67, + Self::EADV => 68, + Self::ESRMNT => 69, + Self::ECOMM => 70, + Self::EPROTO => 71, + Self::EMULTIHOP => 72, + Self::EDOTDOT => 73, + Self::EBADMSG => 74, + Self::EOVERFLOW => 75, + Self::ENOTUNIQ => 76, + Self::EBADFD => 77, + Self::EREMCHG => 78, + Self::ELIBACC => 79, + Self::ELIBBAD => 80, + Self::ELIBSCN => 81, + Self::ELIBMAX => 82, + Self::ELIBEXEC => 83, + Self::EILSEQ => 84, + Self::ERESTART => 85, + Self::ESTRPIPE => 86, + Self::EUSERS => 87, + Self::ENOTSOCK => 88, + Self::EDESTADDRREQ => 89, + Self::EMSGSIZE => 90, + Self::EPROTOTYPE => 91, + Self::ENOPROTOOPT => 92, + Self::EPROTONOSUPPORT => 93, + Self::ESOCKTNOSUPPORT => 94, + Self::EOPNOTSUPP => 95, + Self::EPFNOSUPPORT => 96, + Self::EAFNOSUPPORT => 97, + Self::EADDRINUSE => 98, + Self::EADDRNOTAVAIL => 99, + Self::ENETDOWN => 100, + Self::ENETUNREACH => 101, + Self::ENETRESET => 102, + Self::ECONNABORTED => 103, + Self::ECONNRESET => 104, + Self::ENOBUFS => 105, + Self::EISCONN => 106, + Self::ENOTCONN => 107, + Self::ESHUTDOWN => 108, + Self::ETOOMANYREFS => 109, + Self::ETIMEDOUT => 110, + Self::ECONNREFUSED => 111, + Self::EHOSTDOWN => 112, + Self::EHOSTUNREACH => 113, + Self::EALREADY => 114, + Self::EINPROGRESS => 115, + Self::ESTALE => 116, + Self::EUCLEAN => 117, + Self::ENOTNAM => 118, + Self::ENAVAIL => 119, + Self::EISNAM => 120, + Self::EREMOTEIO => 121, + Self::EDQUOT => 122, + Self::ENOMEDIUM => 123, + Self::EMEDIUMTYPE => 124, + Self::ECANCELED => 125, + Self::ENOKEY => 126, + Self::EKEYEXPIRED => 127, + Self::EKEYREVOKED => 128, + Self::EKEYREJECTED => 129, + Self::EOWNERDEAD => 130, + Self::ENOTRECOVERABLE => 131, + Self::ERFKILL => 132, + Self::EHWPOISON => 133, + Self::Other(OtherErrno(code)) => code, + Self::EPROCLIM + | Self::EBADRPC + | Self::ERPCMISMATCH + | Self::EPROGUNAVAIL + | Self::EPROGMISMATCH + | Self::EPROCUNAVAIL + | Self::EFTYPE + | Self::EAUTH + | Self::ENEEDAUTH + | Self::EIPSEC + | Self::ENOATTR + | Self::ENOTSUP + | Self::EDOOFUS + | Self::ENOTCAPABLE + | Self::ECAPMODE + | Self::EINTEGRITY + | Self::EPWROFF + | Self::EDEVERR + | Self::EBADEXEC + | Self::EBADARCH + | Self::ESHLIBVERS + | Self::EBADMACHO + | Self::ENOPOLICY + | Self::EQFULL => impossible!(), + } + } + /// Returns the error pointer. + #[expect(unsafe_code, reason = "safety comment justifies correctness")] + fn get_raw_err_ptr() -> *mut c_int { + #[cfg(any(target_os = "netbsd", target_os = "openbsd"))] + // SAFETY: + // Safe because errno is a thread-local variable. + unsafe { + c::__errno() + } + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + // SAFETY: + // Safe because errno is a thread-local variable. + unsafe { + c::__error() + } + #[cfg(any(target_os = "dragonfly", target_os = "linux"))] + // SAFETY: + // Safe because errno is a thread-local variable. + unsafe { + c::__errno_location() + } + } + /// Sets the platform error to `self`. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::Errno; + /// Errno::ENOENT.set(); + /// ``` + #[expect(unsafe_code, reason = "safety comment justifies correctness")] + #[inline] + pub fn set(self) { + let ptr = Self::get_raw_err_ptr(); + debug_assert!( + !ptr.is_null() && ptr.is_aligned(), + "libc errno returned a pointer that was either null or not aligned. Something is terribly wrong with your system" + ); + let code = self.into_raw(); + // SAFETY: + // Verified above that `ptr` is not null and aligned. Note while we only verify + // this on non-release builds, the situation is so dire that one could argue we are + // already in "undefined" territory. + unsafe { *ptr = code }; + } + /// Returns the current platform error. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::Errno; + /// Errno::ENOENT.set(); + /// assert_eq!(Errno::last(), Errno::ENOENT); + /// ``` + #[expect(unsafe_code, reason = "safety comment justifies correctness")] + #[inline] + #[must_use] + pub fn last() -> Self { + let ptr = Self::get_raw_err_ptr(); + debug_assert!( + !ptr.is_null() && ptr.is_aligned(), + "libc errno returned a pointer that was either null or not aligned. Something is terribly wrong with your system" + ); + // SAFETY: + // Verified above that `ptr` is not null and aligned. Note while we only verify + // this on non-release builds, the situation is so dire that one could argue we are + // already in "undefined" territory. + Self::from_raw(unsafe { *ptr }) + } + /// Clears any error. + /// + /// # Examples + /// + /// ``` + /// # use priv_sep::Errno; + /// Errno::clear(); + /// assert!(matches!(Errno::last(), Errno::Other(other) if other.into_raw() == 0)); + /// ``` + #[inline] + pub fn clear() { + Self::Other(OtherErrno(0)).set(); + } +} +impl From<Errno> for c_int { + #[inline] + fn from(value: Errno) -> Self { + value.into_raw() + } +} +impl From<c_int> for Errno { + #[inline] + fn from(value: c_int) -> Self { + Self::from_raw(value) + } +} +impl Display for Errno { + #[expect(clippy::too_many_lines, reason = "large match expression")] + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match *self { + Self::EPERM => f.write_str("Operation not permitted"), + Self::ENOENT => f.write_str("No such file or directory"), + Self::ESRCH => f.write_str("No such process"), + Self::EINTR => f.write_str("Interrupted system call"), + Self::EIO => f.write_str("Input/output error"), + Self::ENXIO => f.write_str("Device not configured"), + Self::E2BIG => f.write_str("Argument list too long"), + Self::ENOEXEC => f.write_str("Exec format error"), + Self::EBADF => f.write_str("Bad file descriptor"), + Self::ECHILD => f.write_str("No child processes"), + Self::EDEADLK => f.write_str("Resource deadlock avoided"), + Self::ENOMEM => f.write_str("Cannot allocate memory"), + Self::EACCES => f.write_str("Permission denied"), + Self::EFAULT => f.write_str("Bad address"), + Self::ENOTBLK => f.write_str("Block device required"), + Self::EBUSY => f.write_str("Device busy"), + Self::EEXIST => f.write_str("File exists"), + Self::EXDEV => f.write_str("Cross-device link"), + Self::ENODEV => f.write_str("Operation not supported by device"), + Self::ENOTDIR => f.write_str("Not a directory"), + Self::EISDIR => f.write_str("Is a directory"), + Self::EINVAL => f.write_str("Invalid argument"), + Self::ENFILE => f.write_str("Too many open files in system"), + Self::EMFILE => f.write_str("Too many open files"), + Self::ENOTTY => f.write_str("Inappropriate ioctl for device"), + Self::ETXTBSY => f.write_str("Text file busy"), + Self::EFBIG => f.write_str("File too large"), + Self::ENOSPC => f.write_str("No space left on device"), + Self::ESPIPE => f.write_str("Illegal seek"), + Self::EROFS => f.write_str("Read-only file system"), + Self::EMLINK => f.write_str("Too many links"), + Self::EPIPE => f.write_str("Broken pipe"), + Self::EDOM => f.write_str("Numerical argument out of domain"), + Self::ERANGE => f.write_str("Result too large"), + Self::EWOULDBLOCK => f.write_str("Operation would block"), + Self::EINPROGRESS => f.write_str("Operation now in progress"), + Self::EALREADY => f.write_str("Operation already in progress"), + Self::ENOTSOCK => f.write_str("Socket operation on non-socket"), + Self::EDESTADDRREQ => f.write_str("Destination address required"), + Self::EMSGSIZE => f.write_str("Message too long"), + Self::EPROTOTYPE => f.write_str("Protocol wrong type for socket"), + Self::ENOPROTOOPT => f.write_str("Protocol not available"), + Self::EPROTONOSUPPORT => f.write_str("Protocol not supported"), + Self::ESOCKTNOSUPPORT => f.write_str("Socket type not supported"), + Self::EOPNOTSUPP => f.write_str("Operation not supported"), + Self::EPFNOSUPPORT => f.write_str("Protocol family not supported"), + Self::EAFNOSUPPORT => f.write_str("Address family not supported by protocol family"), + Self::EADDRINUSE => f.write_str("Address already in use"), + Self::EADDRNOTAVAIL => f.write_str("Can't assign requested address"), + Self::ENETDOWN => f.write_str("Network is down"), + Self::ENETUNREACH => f.write_str("Network is unreachable"), + Self::ENETRESET => f.write_str("Network dropped connection on reset"), + Self::ECONNABORTED => f.write_str("Software caused connection abort"), + Self::ECONNRESET => f.write_str("Connection reset by peer"), + Self::ENOBUFS => f.write_str("No buffer space available"), + Self::EISCONN => f.write_str("Socket is already connected"), + Self::ENOTCONN => f.write_str("Socket is not connected"), + Self::ESHUTDOWN => f.write_str("Can't send after socket shutdown"), + Self::ETOOMANYREFS => f.write_str("Too many references: can't splice"), + Self::ETIMEDOUT => f.write_str("Operation timed out"), + Self::ECONNREFUSED => f.write_str("Connection refused"), + Self::ELOOP => f.write_str("Too many levels of symbolic links"), + Self::ENAMETOOLONG => f.write_str("File name too long"), + Self::EHOSTDOWN => f.write_str("Host is down"), + Self::EHOSTUNREACH => f.write_str("No route to host"), + Self::ENOTEMPTY => f.write_str("Directory not empty"), + Self::EPROCLIM => f.write_str("Too many processes"), + Self::EUSERS => f.write_str("Too many users"), + Self::EDQUOT => f.write_str("Disk quota exceeded"), + Self::ESTALE => f.write_str("Stale NFS file handle"), + Self::EREMOTE => f.write_str("Too many levels of remote in path"), + Self::EBADRPC => f.write_str("RPC struct is bad"), + Self::ERPCMISMATCH => f.write_str("RPC version wrong"), + Self::EPROGUNAVAIL => f.write_str("RPC program not available"), + Self::EPROGMISMATCH => f.write_str("Program version wrong"), + Self::EPROCUNAVAIL => f.write_str("Bad procedure for program"), + Self::ENOLCK => f.write_str("No locks available"), + Self::ENOSYS => f.write_str("Function not implemented"), + Self::EFTYPE => f.write_str("Inappropriate file type or format"), + Self::EAUTH => f.write_str("Authentication error"), + Self::ENEEDAUTH => f.write_str("Need authenticator"), + Self::EIPSEC => f.write_str("IPsec processing failure"), + Self::ENOATTR => f.write_str("Attribute not found"), + Self::EILSEQ => f.write_str("Illegal byte sequence"), + Self::ENOMEDIUM => f.write_str("No medium found"), + Self::EMEDIUMTYPE => f.write_str("Wrong medium type"), + Self::EOVERFLOW => f.write_str("Value too large to be stored in data type"), + Self::ECANCELED => f.write_str("Operation canceled"), + Self::EIDRM => f.write_str("Identifier removed"), + Self::ENOMSG => f.write_str("No message of desired type"), + Self::ENOTSUP => f.write_str("Not supported"), + Self::EBADMSG => f.write_str("Bad message"), + Self::ENOTRECOVERABLE => f.write_str("State not recoverable"), + Self::EOWNERDEAD => f.write_str("Previous owner died"), + Self::EPROTO => f.write_str("Protocol error"), + Self::EDOOFUS => f.write_str("Programming error"), + Self::EMULTIHOP => f.write_str("Multihop attempted"), + Self::ENOLINK => f.write_str("Link has been severed"), + Self::ENOTCAPABLE => f.write_str("Capabilities insufficient"), + Self::ECAPMODE => f.write_str("Not permitted in capability mode"), + Self::EINTEGRITY => f.write_str("Integrity check failed"), + Self::ECHRNG => f.write_str("Channel number out of range"), + Self::EL2NSYNC => f.write_str("Level 2 not synchronized"), + Self::EL3HLT => f.write_str("Level 3 halted"), + Self::EL3RST => f.write_str("Level 3 reset"), + Self::ELNRNG => f.write_str("Link number out of range"), + Self::EUNATCH => f.write_str("Protocol driver not attached"), + Self::ENOCSI => f.write_str("No CSI structure available"), + Self::EL2HLT => f.write_str("Level 2 halted"), + Self::EBADE => f.write_str("Invalid exchange"), + Self::EBADR => f.write_str("Invalid request descriptor"), + Self::EXFULL => f.write_str("Exchange full"), + Self::ENOANO => f.write_str("No anode"), + Self::EBADRQC => f.write_str("Invalid request code"), + Self::EBADSLT => f.write_str("Invalid slot"), + Self::EBFONT => f.write_str("Bad font file format"), + Self::ENOSTR => f.write_str("Device not a stream"), + Self::ENODATA => f.write_str("No data available"), + Self::ETIME => f.write_str("Timer expired"), + Self::ENOSR => f.write_str("Out of streams resources"), + Self::ENONET => f.write_str("Machine is not on the network"), + Self::ENOPKG => f.write_str("Package not installed"), + Self::EADV => f.write_str("Advertise error"), + Self::ESRMNT => f.write_str("Srmount error"), + Self::ECOMM => f.write_str("Communication error on send"), + Self::EDOTDOT => f.write_str("RFS specific error"), + Self::ENOTUNIQ => f.write_str("Name not unique on network"), + Self::EBADFD => f.write_str("File descriptor in bad state"), + Self::EREMCHG => f.write_str("Remote address changed"), + Self::ELIBACC => f.write_str("Can not access a needed shared library"), + Self::ELIBBAD => f.write_str("Accessing a corrupted shared library"), + Self::ELIBSCN => f.write_str(".lib section in a.out corrupted"), + Self::ELIBMAX => f.write_str("Attempting to link in too many shared libraries"), + Self::ELIBEXEC => f.write_str("Cannot exec a shared library directly"), + Self::ERESTART => f.write_str("Interrupted system call should be restarted"), + Self::ESTRPIPE => f.write_str("Streams pipe error"), + Self::EUCLEAN => f.write_str("Structure needs cleaning"), + Self::ENOTNAM => f.write_str("Not a XENIX named type file"), + Self::ENAVAIL => f.write_str("No XENIX semaphores available"), + Self::EISNAM => f.write_str("Is a named type file"), + Self::EREMOTEIO => f.write_str("Remote I/O error"), + Self::ENOKEY => f.write_str("Required key not available"), + Self::EKEYEXPIRED => f.write_str("Key has expired"), + Self::EKEYREVOKED => f.write_str("Key has been revoked"), + Self::EKEYREJECTED => f.write_str("Key was rejected by service"), + Self::ERFKILL => f.write_str("Operation not possible due to RF-kill"), + Self::EHWPOISON => f.write_str("Memory page has hardware error"), + Self::EPWROFF => f.write_str("Device power is off"), + Self::EDEVERR => f.write_str("Device error, e.g. paper out"), + Self::EBADEXEC => f.write_str("Bad executable"), + Self::EBADARCH => f.write_str("Bad CPU type in executable"), + Self::ESHLIBVERS => f.write_str("Shared library version mismatch"), + Self::EBADMACHO => f.write_str("Malformed Macho file"), + Self::ENOPOLICY => f.write_str("No such policy registered"), + Self::EQFULL => f.write_str("Interface output queue is full"), + Self::Other(other) => write!(f, "Unknown error code: {}", other.0), + } + } +} +impl Error for Errno {} +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(feature = "std")] +impl From<Errno> for StdErr { + #[inline] + fn from(value: Errno) -> Self { + // All platforms we support have `RawOsError` as `c_int`; thus no conversion is needed. + Self::from_raw_os_error(value.into_raw()) + } +} +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[cfg(feature = "std")] +impl TryFrom<StdErr> for Errno { + type Error = StdErr; + #[inline] + fn try_from(value: StdErr) -> Result<Self, Self::Error> { + // All platforms we support have `RawOsError` as `c_int`; thus no conversion is needed. + value.raw_os_error().ok_or(value).map(Self::from_raw) + } +} diff --git a/src/lib.rs b/src/lib.rs @@ -4,11 +4,25 @@ //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs //! -//! `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction. +//! `priv_sep` is a library that uses the system's libc to perform privilege separation and privilege reduction +//! for Unix-like platforms. +//! +//! Note the only platforms that are currently supported are platforms that correspond to the following +//! `target_os` values: +//! +//! * `dragonfly` +//! * `freebsd` +//! * `linux` +//! * `netbsd` +//! * `openbsd` +//! +//! or the `apple` `target_vendor`. //! //! ## `priv_sep` in action for OpenBSD //! //! ```no_run +//! # #[cfg(target_os = "openbsd")] +//! use core::ffi::CStr; //! use core::convert::Infallible; //! # #[cfg(target_os = "openbsd")] //! use priv_sep::{Permissions, PrivDropErr, Promise, Promises}; @@ -24,17 +38,18 @@ //! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<Infallible, PrivDropErr<Error>> { //! /// Config file. -//! const CONFIG: &str = "config"; +//! const CONFIG: &CStr = c"config"; //! // Get the user ID and group ID for nobody from `passwd(5)`. //! // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. //! // `pledge(2)` `id`, `inet`, `rpath`, `stdio`, and `unveil`. //! // Bind to TCP `[::1]:443` as root. +//! // `setgroups(2)` to drop all supplementary groups. //! // `setresgid(2)` to the group ID associated with nobody. //! // `setresuid(2)` to the user ID associated with nobody. //! // Remove `id` from our `pledge(2)`d promises. //! let (listener, mut promises) = Promises::new_chroot_then_priv_drop_async( -//! "nobody", -//! "/path/chroot/", +//! c"nobody", +//! c"/path/chroot/", //! [Promise::Inet, Promise::Rpath, Promise::Unveil], //! false, //! async || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await, @@ -45,7 +60,7 @@ //! // Read `config`. //! // This will of course fail if the file does not exist or nobody does not //! // have read permissions. -//! let config = fs::read(CONFIG)?; +//! let config = fs::read(CONFIG.to_str().unwrap_or_else(|_e| unreachable!("only contains UTF-8"))).map_err(PrivDropErr::Other)?; //! // Remove file system access. //! Permissions::NONE.unveil(CONFIG)?; //! // Remove `rpath` and `unveil` from our `pledge(2)`d promises @@ -75,9 +90,10 @@ //! // Get the user ID and group ID for nobody from `passwd(5)`. //! // `chroot(2)` to `/path/chroot/` and `chdir(2)` to `/`. //! // Bind to TCP `[::1]:443` as root. +//! // `setgroups(2)` to drop all supplementary groups. //! // `setresgid(2)` to the group ID associated with nobody. //! // `setresuid(2)` to the user ID associated with nobody. -//! let listener = UserInfo::chroot_then_priv_drop_async("nobody", "/path/chroot/", false, async || { +//! let listener = UserInfo::chroot_then_priv_drop_async(c"nobody", c"/path/chroot/", false, async || { //! TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await //! }).await?; //! // At this point, the process is running under nobody. @@ -90,15 +106,29 @@ //! } //! ``` #![cfg_attr(docsrs, feature(doc_cfg))] -#![allow(clippy::pub_use, reason = "don't want openbsd types in a module")] -extern crate alloc; +#![no_std] +#![cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + target_vendor = "apple" +))] +#![allow( + clippy::pub_use, + reason = "don't want Errno nor openbsd types in a module" +)] +#[cfg(feature = "std")] +extern crate std; /// C FFI. mod c; +/// Errno. +mod err; /// OpenBSD #[cfg(any(doc, target_os = "openbsd"))] mod openbsd; -use alloc::ffi::{CString, NulError}; -use c::{IdT, SUCCESS}; +use c::SUCCESS; use core::{ error::Error as CoreErr, ffi::{CStr, c_char, c_int}, @@ -106,13 +136,14 @@ use core::{ mem::MaybeUninit, ptr, }; +pub use err::{Errno, OtherErrno}; #[cfg_attr(docsrs, doc(cfg(target_os = "openbsd")))] #[cfg(any(doc, target_os = "openbsd"))] pub use openbsd::{Permission, Permissions, Promise, Promises}; -use std::{io::Error, os::unix::ffi::OsStrExt as _, path::Path}; /// [`uid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html). #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Uid(pub IdT); +#[repr(transparent)] +pub struct Uid(pub u32); impl Uid { /// The root user ID (i.e., 0). pub const ROOT: Self = Self(0); @@ -171,7 +202,7 @@ impl Uid { /// assert!(Uid(1000).setresuid().is_ok()); /// ``` #[inline] - pub fn setresuid(self) -> Result<(), Error> { + pub fn setresuid(self) -> Result<(), Errno> { #[cfg(any( target_os = "dragonfly", target_os = "freebsd", @@ -179,17 +210,12 @@ impl Uid { target_os = "openbsd" ))] let code = c::setresuid(self.0, self.0, self.0); - #[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - )))] + #[cfg(any(target_os = "netbsd", target_vendor = "apple"))] let code = c::setuid(self.0); if code == SUCCESS { Ok(()) } else { - Err(Error::last_os_error()) + Err(Errno::last()) } } } @@ -205,27 +231,28 @@ impl PartialEq<Uid> for &Uid { **self == *other } } -impl PartialEq<IdT> for Uid { +impl PartialEq<u32> for Uid { #[inline] - fn eq(&self, other: &IdT) -> bool { + fn eq(&self, other: &u32) -> bool { self.0 == *other } } -impl From<Uid> for IdT { +impl From<Uid> for u32 { #[inline] fn from(value: Uid) -> Self { value.0 } } -impl From<IdT> for Uid { +impl From<u32> for Uid { #[inline] - fn from(value: IdT) -> Self { + fn from(value: u32) -> Self { Self(value) } } /// [`gid_t`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/basedefs/sys_types.h.html). #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Gid(pub IdT); +#[repr(transparent)] +pub struct Gid(pub u32); impl Gid { /// [`getgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getgid.html). /// @@ -269,7 +296,7 @@ impl Gid { /// assert!(Gid(1000).setresgid().is_ok()); /// ``` #[inline] - pub fn setresgid(self) -> Result<(), Error> { + pub fn setresgid(self) -> Result<(), Errno> { #[cfg(any( target_os = "dragonfly", target_os = "freebsd", @@ -277,17 +304,12 @@ impl Gid { target_os = "openbsd" ))] let code = c::setresgid(self.0, self.0, self.0); - #[cfg(not(any( - target_os = "dragonfly", - target_os = "freebsd", - target_os = "linux", - target_os = "openbsd" - )))] + #[cfg(any(target_os = "netbsd", target_vendor = "apple"))] let code = c::setgid(self.0); if code == SUCCESS { Ok(()) } else { - Err(Error::last_os_error()) + Err(Errno::last()) } } } @@ -303,113 +325,75 @@ impl PartialEq<Gid> for &Gid { **self == *other } } -impl PartialEq<IdT> for Gid { +impl PartialEq<u32> for Gid { #[inline] - fn eq(&self, other: &IdT) -> bool { + fn eq(&self, other: &u32) -> bool { self.0 == *other } } -impl From<Gid> for IdT { +impl From<Gid> for u32 { #[inline] fn from(value: Gid) -> Self { value.0 } } -impl From<IdT> for Gid { +impl From<u32> for Gid { #[inline] - fn from(value: IdT) -> Self { + fn from(value: u32) -> Self { Self(value) } } -/// Error when [`CString::new`] errors or an I/O error occurs due to a libc call. -#[derive(Debug)] -pub enum NulOrIoErr { - /// Error returned from [`CString::new`]. - Nul(NulError), - /// Generic I/O error returned from a libc call. - Io(Error), -} -impl Display for NulOrIoErr { - #[inline] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match *self { - Self::Nul(ref err) => write!(f, "CString could not be created: {err}"), - Self::Io(ref err) => write!(f, "libc I/O error: {err}"), - } - } -} -impl CoreErr for NulOrIoErr {} -impl From<NulError> for NulOrIoErr { - #[inline] - fn from(value: NulError) -> Self { - Self::Nul(value) - } -} -impl From<Error> for NulOrIoErr { - #[inline] - fn from(value: Error) -> Self { - Self::Io(value) - } -} /// [`chroot(2)`](https://manned.org/chroot.2). /// /// # Errors /// -/// Returns [`NulError`] iff [`CString::new`] does. -/// Returns [`Error`] iff `chroot(2)` errors. +/// Errors iff `chroot(2)` does. /// /// # Examples /// /// ```no_run -/// assert!(priv_sep::chroot("./").is_ok()); +/// assert!(priv_sep::chroot(c"./").is_ok()); /// ``` #[expect(unsafe_code, reason = "chroot(2) takes a pointer")] #[inline] -pub fn chroot<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { - CString::new(path.as_ref().as_os_str().as_bytes()) - .map_err(NulOrIoErr::Nul) - .and_then(|c_path| { - let ptr = c_path.as_ptr(); - // SAFETY: - // `ptr` is valid and not null. - if unsafe { c::chroot(ptr) } == SUCCESS { - Ok(()) - } else { - Err(NulOrIoErr::Io(Error::last_os_error())) - } - }) +pub fn chroot(path: &CStr) -> Result<(), Errno> { + let ptr = path.as_ptr(); + // SAFETY: + // `ptr` is valid and not null. + if unsafe { c::chroot(ptr) } == SUCCESS { + Ok(()) + } else { + Err(Errno::last()) + } } /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html). /// /// This function MUST only be called by `chdir` and `chroot_then_chdir`. #[expect(unsafe_code, reason = "chdir(2) takes a pointer")] -fn private_chdir(path: *const c_char) -> Result<(), Error> { +fn private_chdir(path: *const c_char) -> Result<(), Errno> { // SAFETY: // `path` is valid and not null as can be seen in the only functions that call this function: // `chdir` and `chroot_then_chdir`. if unsafe { c::chdir(path) } == SUCCESS { Ok(()) } else { - Err(Error::last_os_error()) + Err(Errno::last()) } } /// [`chdir`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/chdir.html). /// /// # Errors /// -/// Returns [`NulError`] iff [`CString::new`] does. -/// Returns [`Error`] iff `chdir` errors. +/// Errors iff `chdir` does. /// /// # Examples /// /// ```no_run -/// assert!(priv_sep::chdir("/").is_ok()); +/// assert!(priv_sep::chdir(c"/").is_ok()); /// ``` #[inline] -pub fn chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { - CString::new(path.as_ref().as_os_str().as_bytes()) - .map_err(NulOrIoErr::Nul) - .and_then(|c_path| private_chdir(c_path.as_ptr()).map_err(NulOrIoErr::Io)) +pub fn chdir(path: &CStr) -> Result<(), Errno> { + private_chdir(path.as_ptr()) } /// Calls [`chroot`] on `path` followed by a call to [`chdir`] on `"/"`. /// @@ -420,21 +404,19 @@ pub fn chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { /// # Examples /// /// ```no_run -/// assert!(priv_sep::chroot_then_chdir("./").is_ok()); +/// assert!(priv_sep::chroot_then_chdir(c"./").is_ok()); /// ``` #[inline] -pub fn chroot_then_chdir<P: AsRef<Path>>(path: P) -> Result<(), NulOrIoErr> { +pub fn chroot_then_chdir(path: &CStr) -> Result<(), Errno> { /// Root directory. const ROOT: *const c_char = c"/".as_ptr(); - chroot(path).and_then(|()| private_chdir(ROOT).map_err(NulOrIoErr::Io)) + chroot(path).and_then(|()| private_chdir(ROOT)) } /// Error returned when dropping privileges. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum PrivDropErr<E> { - /// Error when [`CString::new`] errors. - Nul(NulError), - /// Error when an I/O error occurs from a libc call. - Io(Error), + /// Error when an error occurs from a libc call. + Libc(Errno), /// Error when there is no entry in the user database corresponding to the passed username. NoPasswdEntry, /// Error when [`UserInfo::is_root`]. @@ -446,11 +428,7 @@ impl<E: Display> Display for PrivDropErr<E> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Self::Nul(ref err) => write!( - f, - "CString could not be created from the username to drop privileges to: {err}" - ), - Self::Io(ref err) => write!(f, "libc I/O error when dropping privileges: {err}"), + Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"), Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"), Self::RootEntry => f.write_str( "setresuid(2) is not allowed to be called on uid 0 when dropping privileges", @@ -463,32 +441,17 @@ impl<E: Display> Display for PrivDropErr<E> { } } impl<E: CoreErr> CoreErr for PrivDropErr<E> {} -impl<E> From<NulError> for PrivDropErr<E> { - #[inline] - fn from(value: NulError) -> Self { - Self::Nul(value) - } -} -impl<E> From<Error> for PrivDropErr<E> { - #[inline] - fn from(value: Error) -> Self { - Self::Io(value) - } -} -impl<E> From<NulOrIoErr> for PrivDropErr<E> { +impl<E> From<Errno> for PrivDropErr<E> { #[inline] - fn from(value: NulOrIoErr) -> Self { - match value { - NulOrIoErr::Nul(e) => Self::Nul(e), - NulOrIoErr::Io(e) => Self::Io(e), - } + fn from(value: Errno) -> Self { + Self::Libc(value) } } /// Error returned from [`UserInfo::setresid_if_valid`]. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum SetresidErr { - /// Error when an I/O error occurs from a libc call. - Io(Error), + /// Error when an error occurs from a libc call. + Libc(Errno), /// Error when there is no entry in the user database corresponding to [`UserInfo::uid`]. NoPasswdEntry, /// Error when the entry in the user database has a different gid than [`UserInfo::gid`]. @@ -498,17 +461,17 @@ impl Display for SetresidErr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { - Self::Io(ref err) => write!(f, "libc I/O error when dropping privileges: {err}"), + Self::Libc(err) => write!(f, "libc error when dropping privileges: {err}"), Self::NoPasswdEntry => f.write_str("no passwd(5) entry to drop privileges to"), Self::GidMismatch => f.write_str("gid in passwd(5) does match the expected gid"), } } } impl CoreErr for SetresidErr {} -impl From<Error> for SetresidErr { +impl From<Errno> for SetresidErr { #[inline] - fn from(value: Error) -> Self { - Self::Io(value) + fn from(value: Errno) -> Self { + Self::Libc(value) } } /// Used by [`UserInfo::getpw_entry`]. @@ -576,6 +539,8 @@ pub struct UserInfo { pub gid: Gid, } impl UserInfo { + /// The buffer size we use to read a `passwd` entry. + const PW_ENT_BUF_LEN: usize = 1024; /// Returns `true` iff [`Uid::is_root`]. /// /// # Examples @@ -589,132 +554,83 @@ impl UserInfo { pub const fn is_root(self) -> bool { self.uid.is_root() } + /// Helper for [`Self::with_buffer`], [`Self::new`], and [`Self::setresid_if_exists`]. + #[expect( + unsafe_code, + reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers" + )] + fn getpw_entry<P: Copy + PwEntry>(u: P, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> { + let mut pwd = MaybeUninit::<c::Passwd>::uninit(); + let pwd_ptr = pwd.as_mut_ptr(); + let buf_ptr = buffer.as_mut_ptr(); + let len = buffer.len(); + let mut result = ptr::null_mut(); + let res_ptr = &mut result; + // SAFETY: + // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine. + // `buf_ptr` is valid, initialized, and not null. + // `len` is the length of `buf_ptr`. + // `res_ptr` is valid, initialized, and not null. + // `result` is valid, initialized, and allowed to be null. + let code = unsafe { u.getpw(pwd_ptr, buf_ptr, len, res_ptr) }; + if code == SUCCESS { + if result.is_null() { + Ok(None) + } else { + debug_assert!( + result.is_aligned(), + "libc getpwnam_r or getpwuid_r result was not aligned. Something is terribly wrong with your system" + ); + // SAFETY: + // Verified above that `result` is not null and aligned. Note while we only verify the pointer + // is aligned on non-release builds, the situation is so dire that one could argue we are + // already in "undefined" territory. + // When `result` is not null, the platform is supposed to have written to `pwd`. + Ok(Some(unsafe { pwd.assume_init() }.into_user_info())) + } + } else { + Err(Errno::from_raw(code)) + } + } /// [`getpwnam_r`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/getpwnam_r.html). /// /// Uses `buffer` to write the user database entry into returning `None` iff there is no entry; otherwise /// returns `Self`. /// - /// Note it is the caller's responsibility to ensure `buffer` is large enough; otherwise an [`Error`] will + /// Note it is the caller's responsibility to ensure `buffer` is large enough; otherwise an [`Errno`] will /// be returned. /// /// # Errors /// - /// Returns [`NulError`] iff [`CString::new`] does. - /// Returns [`Error`] iff `getpwnam_r` errors. + /// Errors iff `getpwnam_r` does. /// /// # Examples /// /// ```no_run /// # use priv_sep::{Uid, UserInfo}; - /// assert!(UserInfo::with_buffer("root", [0; 128].as_mut_slice())?.map_or(false, |info| info.is_root())); - /// # Ok::<_, priv_sep::NulOrIoErr>(()) + /// assert!(UserInfo::with_buffer(c"root", [0; 128].as_mut_slice())?.map_or(false, |info| info.is_root())); + /// # Ok::<_, priv_sep::Errno>(()) /// ``` - #[expect(unsafe_code, reason = "getpwnam_r(3) takes in pointers")] #[inline] - pub fn with_buffer<T: Into<Vec<u8>>>( - name: T, - buffer: &mut [c_char], - ) -> Result<Option<Self>, NulOrIoErr> { - CString::new(name).map_err(NulOrIoErr::Nul).and_then(|n| { - let ptr = n.as_ptr(); - let mut pwd = MaybeUninit::<c::Passwd>::uninit(); - let pwd_ptr = pwd.as_mut_ptr(); - let buf_ptr = buffer.as_mut_ptr(); - let len = buffer.len(); - let mut result = ptr::null_mut(); - let res_ptr = &mut result; - // SAFETY: - // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine. - // `buf_ptr` is valid, initialized, and not null. - // `len` is the length of `buf_ptr`. - // `res_ptr` is valid, initialized, and not null. - // `result` is valid, initialized, and allowed to be null. - let code = unsafe { c::getpwnam_r(ptr, pwd_ptr, buf_ptr, len, res_ptr) }; - if code == SUCCESS { - if result.is_null() { - Ok(None) - } else { - // SAFETY: - // `c::getpwnam_r` writes to `pwd` iff `result` is not null. - Ok(Some(unsafe { pwd.assume_init() }.into_user_info())) - } - } else { - Err(NulOrIoErr::Io(Error::from_raw_os_error(code))) - } - }) + pub fn with_buffer(name: &CStr, buffer: &mut [c_char]) -> Result<Option<Self>, Errno> { + Self::getpw_entry(CStrWrapper(name), buffer) } - /// Helper for [`Self::new`] and [`Self::setresid_if_exists`]. - #[expect( - unsafe_code, - reason = "getpwnam_r(3) and getpwuid_r(3) take in pointers" - )] - fn getpw_entry<P: Copy + PwEntry>(u: P) -> Result<Option<Self>, Error> { - /// Initial buffer size. - const INIT_CAP: usize = 128; - // `2 * (MAX_CAP - 1) <= isize::MAX` MUST be true. - /// Maximum buffer size. - const MAX_CAP: usize = 0x4000; - /// [`ERANGE`](https://man.openbsd.org/errno#Result). - const ERANGE: c_int = 34; - let mut buffer = Vec::with_capacity(INIT_CAP); - let mut cap = buffer.capacity(); - let mut pwd = MaybeUninit::<c::Passwd>::uninit(); - let mut result = ptr::null_mut(); - let mut pwd_ptr; - let mut res_ptr; - let mut code; - let mut buf_ptr; - loop { - pwd_ptr = pwd.as_mut_ptr(); - res_ptr = &mut result; - buf_ptr = buffer.as_mut_ptr(); - // SAFETY: - // `pwd_ptr` is only written to; thus the fact `pwd` is unitialized is fine. - // `buf_ptr` is valid, initialized, and not null. - // `cap` is the length of `buf_ptr`. - // `res_ptr` is valid, initialized, and not null. - // `result` is valid, initialized, and allowed to be null. - code = unsafe { u.getpw(pwd_ptr, buf_ptr, cap, res_ptr) }; - if code == SUCCESS { - return Ok(if result.is_null() { - None - } else { - // SAFETY: - // `CStrWrapper::getpw` writes to `pwd` iff `result` is not null. - Some(unsafe { pwd.assume_init() }.into_user_info()) - }); - } else if code == ERANGE { - if cap >= MAX_CAP { - return Err(Error::from_raw_os_error(code)); - } - // `cap < MAX_CAP` and - // `2 * (MAX_CAP - 1) < isize::MAX`, so overflow is not possible. - buffer.reserve(cap << 1); - cap = buffer.capacity(); - } else { - return Err(Error::from_raw_os_error(code)); - } - } - } - /// Same as [`Self::with_buffer`] except repeated attempts are made with progressively larger buffers up to - /// 16 KiB. + /// Same as [`Self::with_buffer`] except a stack-allocated buffer of 1024 bytes is used. /// /// # Errors /// - /// Errors iff [`Self::with_buffer`] does for a 16 KiB buffer. + /// Errors iff [`Self::with_buffer`] does for a 1024-byte buffer. /// /// # Examples /// /// ```no_run /// # use priv_sep::UserInfo; - /// assert!(UserInfo::new("root")?.map_or(false, |info| info.is_root())); - /// # Ok::<_, priv_sep::NulOrIoErr>(()) + /// assert!(UserInfo::new(c"root")?.map_or(false, |info| info.is_root())); + /// # Ok::<_, priv_sep::Errno>(()) /// ``` #[inline] - pub fn new<T: Into<Vec<u8>>>(name: T) -> Result<Option<Self>, NulOrIoErr> { - CString::new(name) - .map_err(NulOrIoErr::Nul) - .and_then(|n| Self::getpw_entry(CStrWrapper(n.as_c_str())).map_err(NulOrIoErr::Io)) + pub fn new(name: &CStr) -> Result<Option<Self>, Errno> { + Self::with_buffer(name, &mut [0; Self::PW_ENT_BUF_LEN]) } /// Calls [`Gid::setresgid`] and [`Uid::setresuid`]. /// @@ -726,13 +642,13 @@ impl UserInfo { /// /// ```no_run /// # use priv_sep::UserInfo; - /// if let Some(user) = UserInfo::new("nobody")? { + /// if let Some(user) = UserInfo::new(c"nobody")? { /// user.setresid()?; /// } - /// # Ok::<_, priv_sep::NulOrIoErr>(()) + /// # Ok::<_, priv_sep::Errno>(()) /// ``` #[inline] - pub fn setresid(self) -> Result<(), Error> { + pub fn setresid(self) -> Result<(), Errno> { self.gid.setresgid().and_then(|()| self.uid.setresuid()) } /// Same as [`Self::setresid`] except @@ -758,29 +674,49 @@ impl UserInfo { /// ``` #[inline] pub fn setresid_if_valid(self) -> Result<(), SetresidErr> { - Self::getpw_entry(self.uid) - .map_err(SetresidErr::Io) + Self::getpw_entry(self.uid, &mut [0; Self::PW_ENT_BUF_LEN]) + .map_err(SetresidErr::Libc) .and_then(|opt| { opt.ok_or(SetresidErr::NoPasswdEntry).and_then(|info| { if info.gid == self.gid { - self.setresid().map_err(SetresidErr::Io) + self.setresid().map_err(SetresidErr::Libc) } else { Err(SetresidErr::GidMismatch) } }) }) } - /// Calls [`Self::new`], invokes `f`, then calls [`Self::setresid`]. + /// Helper to unify targets that don't support `setgroups(2)`. + /// + /// No-op. + #[cfg(target_vendor = "apple")] + const fn drop_sup_groups() -> Result<(), Errno> { + Ok(()) + } + /// Helper to unify targets that don't support `setgroups(2)`. + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + fn drop_sup_groups() -> Result<(), Errno> { + drop_supplementary_groups() + } + /// Calls [`Self::new`], invokes `f`, calls [`drop_supplementary_groups`] (if the platform supports + /// `setgroups(2)`), then calls [`Self::setresid`]. /// /// Dropping privileges is necessary when needing to perform certain actions as root before no longer needing /// such abilities; at which point, one calls - /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html) and + /// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html), + /// [`setresgid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresgid.html), and /// [`setresuid`](https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/setresuid.html) /// using a lesser privileged gid and uid. /// /// # Errors /// - /// Errors iff [`Self::new`], `f`, or [`Self::setresid`] do or there is no entry in the user database + /// Errors iff [`Self::new`], `f`, [`drop_supplementary_groups`], or [`Self::setresid`] do or there is no entry in the user database /// corresponding to `name` or the entry has uid 0. /// /// # Examples @@ -789,24 +725,27 @@ impl UserInfo { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use priv_sep::{PrivDropErr, UserInfo}; /// # use std::{io::Error, net::TcpListener}; - /// let listener = UserInfo::priv_drop("nobody", || { + /// let listener = UserInfo::priv_drop(c"nobody", || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) /// })?; /// # Ok::<_, PrivDropErr<Error>>(()) /// ``` #[inline] - pub fn priv_drop<U: Into<Vec<u8>>, T, E, F: FnOnce() -> Result<T, E>>( - name: U, + pub fn priv_drop<T, E, F: FnOnce() -> Result<T, E>>( + name: &CStr, f: F, ) -> Result<T, PrivDropErr<E>> { - Self::new(name).map_err(PrivDropErr::from).and_then(|opt| { + Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| { opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) .and_then(|info| { if info.is_root() { Err(PrivDropErr::RootEntry) } else { - f().map_err(PrivDropErr::Other) - .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res)) + f().map_err(PrivDropErr::Other).and_then(|res| { + Self::drop_sup_groups() + .and_then(|()| info.setresid().map(|()| res)) + .map_err(PrivDropErr::Libc) + }) } }) }) @@ -823,13 +762,13 @@ impl UserInfo { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use priv_sep::UserInfo; /// # use tokio::net::TcpListener; - /// let listener_fut = UserInfo::priv_drop_async("nobody", async || { + /// let listener_fut = UserInfo::priv_drop_async(c"nobody", async || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await /// }); /// ``` #[inline] - pub async fn priv_drop_async<U: Into<Vec<u8>>, T, E, F: AsyncFnOnce() -> Result<T, E>>( - name: U, + pub async fn priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>( + name: &CStr, f: F, ) -> Result<T, PrivDropErr<E>> { match Self::new(name) { @@ -839,13 +778,15 @@ impl UserInfo { if info.is_root() { Err(PrivDropErr::RootEntry) } else { - f().await - .map_err(PrivDropErr::Other) - .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res)) + f().await.map_err(PrivDropErr::Other).and_then(|res| { + Self::drop_sup_groups() + .and_then(|()| info.setresid().map(|()| res)) + .map_err(PrivDropErr::Libc) + }) } } }, - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } /// Same as [`Self::priv_drop`] except [`chroot_then_chdir`] is called before or after invoking `f` based on @@ -861,25 +802,19 @@ impl UserInfo { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use priv_sep::{PrivDropErr, UserInfo}; /// # use std::{io::Error, net::TcpListener}; - /// let listener = UserInfo::chroot_then_priv_drop("nobody", "./", false, || { + /// let listener = UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) /// })?; /// # Ok::<_, PrivDropErr<Error>>(()) /// ``` #[inline] - pub fn chroot_then_priv_drop< - U: Into<Vec<u8>>, - P: AsRef<Path>, - T, - E, - F: FnOnce() -> Result<T, E>, - >( - name: U, - path: P, + pub fn chroot_then_priv_drop<T, E, F: FnOnce() -> Result<T, E>>( + name: &CStr, + path: &CStr, chroot_after_f: bool, f: F, ) -> Result<T, PrivDropErr<E>> { - Self::new(name).map_err(PrivDropErr::from).and_then(|opt| { + Self::new(name).map_err(PrivDropErr::Libc).and_then(|opt| { opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) .and_then(|info| { if info.is_root() { @@ -887,15 +822,19 @@ impl UserInfo { } else if chroot_after_f { f().map_err(PrivDropErr::Other).and_then(|res| { chroot_then_chdir(path) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .map(|()| res) }) } else { chroot_then_chdir(path) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|()| f().map_err(PrivDropErr::Other)) } - .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res)) + .and_then(|res| { + Self::drop_sup_groups() + .and_then(|()| info.setresid().map(|()| res)) + .map_err(PrivDropErr::Libc) + }) }) }) } @@ -911,20 +850,14 @@ impl UserInfo { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use priv_sep::UserInfo; /// # use tokio::net::TcpListener; - /// let listener_fut = UserInfo::chroot_then_priv_drop_async("nobody", "./", false, async || { + /// let listener_fut = UserInfo::chroot_then_priv_drop_async(c"nobody", c"./", false, async || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await /// }); /// ``` #[inline] - pub async fn chroot_then_priv_drop_async< - U: Into<Vec<u8>>, - P: AsRef<Path>, - T, - E, - F: AsyncFnOnce() -> Result<T, E>, - >( - name: U, - path: P, + pub async fn chroot_then_priv_drop_async<T, E, F: AsyncFnOnce() -> Result<T, E>>( + name: &CStr, + path: &CStr, chroot_after_f: bool, f: F, ) -> Result<T, PrivDropErr<E>> { @@ -936,18 +869,22 @@ impl UserInfo { } else if chroot_after_f { f().await.map_err(PrivDropErr::Other).and_then(|res| { chroot_then_chdir(path) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .map(|()| res) }) } else { match chroot_then_chdir(path) { Ok(()) => f().await.map_err(PrivDropErr::Other), - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } - .and_then(|res| info.setresid().map_err(PrivDropErr::Io).map(|()| res)), + .and_then(|res| { + Self::drop_sup_groups() + .and_then(|()| info.setresid().map(|()| res)) + .map_err(PrivDropErr::Libc) + }), }, - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } } @@ -963,15 +900,126 @@ impl PartialEq<UserInfo> for &UserInfo { **self == *other } } +/// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html). +/// +/// # Errors +/// +/// Errors iff `setgroups` does. +#[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))) +)] +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +#[expect(unsafe_code, reason = "setgroups(2) takes a pointer")] +#[inline] +pub fn setgroups(groups: &[Gid]) -> Result<(), Errno> { + #[cfg(target_os = "linux")] + let size = groups.len(); + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + let size = c_int::try_from(groups.len()).map_err(|_e| Errno::EINVAL)?; + let gids = groups.as_ptr().cast(); + // SAFETY: + // `size` is the length of `gids`, and `gids` is a valid, non-null, alligned pointer to + // `u32`s. Note that `Gid` is a `newtype` around `u32` with `repr(transparent)`; thus the above + // pointer cast is fine. + let code = unsafe { c::setgroups(size, gids) }; + if code == SUCCESS { + Ok(()) + } else { + Err(Errno::last()) + } +} +/// [`setgroups(2)`](https://www.man7.org/linux/man-pages/man2/setgroups.2.html) passing in +/// 0 and a null pointer. +/// +/// If successful, this drops all supplementary groups. +/// +/// # Errors +/// +/// Errors iff `setgroups` does. +/// +/// # Examples +/// +/// ```no_run +/// assert!(priv_sep::drop_supplementary_groups().is_ok()); +/// ``` +#[cfg_attr( + docsrs, + doc(cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))) +)] +#[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" +))] +#[expect(unsafe_code, reason = "setgroups(2) takes a pointer")] +#[inline] +pub fn drop_supplementary_groups() -> Result<(), Errno> { + #[cfg(target_os = "linux")] + let size: usize = 0; + #[cfg(any( + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + let size: c_int = 0; + let gids = ptr::null(); + // SAFETY: + // Passing in 0 and a null pointer is valid and causes all supplementary groups to be dropped. + let code = unsafe { c::setgroups(size, gids) }; + if code == SUCCESS { + Ok(()) + } else { + Err(Errno::last()) + } +} #[cfg(test)] mod tests { - use super::{Gid, NulOrIoErr, PrivDropErr, SetresidErr, Uid, UserInfo}; - #[cfg(target_os = "openbsd")] + #[cfg(feature = "std")] + use super::{CStr, PrivDropErr}; + use super::{Errno, Gid, SetresidErr, Uid, UserInfo}; + #[cfg(all(feature = "std", target_os = "openbsd"))] use super::{Permissions, Promise, Promises}; + #[cfg(feature = "std")] use core::net::{Ipv6Addr, SocketAddrV6}; + #[cfg(all(feature = "std", target_os = "openbsd"))] + use std::{format, print}; + #[cfg(feature = "std")] use std::{fs, io::Error, net::TcpListener}; use tokio as _; - const README: &str = "README.md"; + #[cfg(feature = "std")] + const README: &CStr = c"README.md"; + #[cfg(feature = "std")] + const README_STR: &str = match README.to_str() { + Ok(val) => val, + Err(_) => panic!("not possible"), + }; #[test] fn test_getuid() { _ = Uid::getuid(); @@ -989,29 +1037,29 @@ mod tests { _ = Gid::getegid(); } #[test] - fn test_setresuid() -> Result<(), Error> { + fn test_setresuid() -> Result<(), Errno> { Uid::geteuid().setresuid() } #[test] - fn test_setresgid() -> Result<(), Error> { + fn test_setresgid() -> Result<(), Errno> { Gid::getegid().setresgid() } #[test] - fn test_user_info_new() -> Result<(), NulOrIoErr> { - if let Some(user) = UserInfo::new("root")? { + fn test_user_info_new() -> Result<(), Errno> { + if let Some(user) = UserInfo::new(c"root")? { assert!(user.is_root()); } Ok(()) } #[test] - fn test_user_info_with_buffer() -> Result<(), NulOrIoErr> { - if let Some(user) = UserInfo::with_buffer("root", [0; 512].as_mut_slice())? { + fn test_user_info_with_buffer() -> Result<(), Errno> { + if let Some(user) = UserInfo::with_buffer(c"root", [0; 512].as_mut_slice())? { assert!(user.is_root()); } Ok(()) } #[test] - fn test_user_info_setresid() -> Result<(), Error> { + fn test_user_info_setresid() -> Result<(), Errno> { UserInfo { uid: Uid::geteuid(), gid: Gid::getegid(), @@ -1037,11 +1085,12 @@ mod tests { .map_or_else(|e| matches!(e, SetresidErr::GidMismatch), |_| false) ); } + #[cfg(feature = "std")] #[test] #[ignore] fn test_priv_drop() -> Result<(), PrivDropErr<Error>> { if Uid::geteuid().is_root() { - UserInfo::priv_drop("zack", || { + UserInfo::priv_drop(c"nobody", || { TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) }) .map(|_| { @@ -1051,49 +1100,63 @@ mod tests { }) } else { assert!( - UserInfo::priv_drop("root", || Ok::<_, Error>(())) + UserInfo::priv_drop(c"root", || Ok::<_, Error>(())) .map_or_else(|e| matches!(e, PrivDropErr::RootEntry), |_| false) ); Ok(()) } } + #[cfg(feature = "std")] #[test] #[ignore] fn test_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> { if Uid::geteuid().is_root() { - UserInfo::chroot_then_priv_drop("zack", "./", false, || { - TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) + UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || { + TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 987, 0, 0)) }) .and_then(|_| { - fs::exists(README).map_err(PrivDropErr::Io).map(|exists| { - assert!(exists); - assert!( - TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)) - .is_err() - ); - }) + fs::exists(README_STR) + .map_err(PrivDropErr::Other) + .map(|exists| { + assert!(exists); + assert!( + TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 82, 0, 0)) + .is_err() + ); + }) }) } else { Ok(()) } } - #[cfg(target_os = "openbsd")] + #[cfg(all(feature = "std", target_os = "openbsd"))] #[test] #[ignore] fn test_pledge_unveil() { - const FILE_EXISTS: &str = "/home/zack/foo.txt"; - _ = fs::metadata(FILE_EXISTS) - .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str()); - const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23"; - drop(fs::metadata(FILE_NOT_EXISTS).expect_err( - format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), - )); - const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/"; - drop( - fs::metadata(DIR_NOT_EXISTS).expect_err( - format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), - ), + const FILE_EXISTS: &CStr = c"/home/zack/foo.txt"; + const FILE_EXISTS_STR: &str = match FILE_EXISTS.to_str() { + Ok(val) => val, + Err(_) => panic!("not possible"), + }; + _ = fs::metadata(FILE_EXISTS_STR).expect( + format!("{FILE_EXISTS_STR} does not exist, so unit testing cannot occur").as_str(), ); + const FILE_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23"; + const FILE_NOT_EXISTS_STR: &str = match FILE_NOT_EXISTS.to_str() { + Ok(val) => val, + Err(_) => panic!("not possible"), + }; + drop(fs::metadata(FILE_NOT_EXISTS_STR).expect_err( + format!("{FILE_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(), + )); + const DIR_NOT_EXISTS: &CStr = c"/home/zack/aadkjfasj3s23/"; + const DIR_NOT_EXISTS_STR: &str = match DIR_NOT_EXISTS.to_str() { + Ok(val) => val, + Err(_) => panic!("not possible"), + }; + drop(fs::metadata(DIR_NOT_EXISTS_STR).expect_err( + format!("{DIR_NOT_EXISTS_STR} exists, so unit testing cannot occur").as_str(), + )); // This tests that a NULL `promise` does nothing. assert!(Promises::pledge_none().is_ok()); print!(""); @@ -1125,11 +1188,11 @@ mod tests { assert!(initial_promises.pledge().is_ok()); // This tests unveil with no permissions. assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok()); - assert!(fs::metadata(FILE_EXISTS).is_err()); + assert!(fs::metadata(FILE_EXISTS_STR).is_err()); // This tests unveil with read permissions, // and one can unveil more permissions (unlike pledge which can only remove promises). assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok()); - assert!(fs::metadata(FILE_EXISTS).is_ok()); + assert!(fs::metadata(FILE_EXISTS_STR).is_ok()); // This tests that calls to unveil on missing files don't error. assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok()); // This tests that calls to unveil on missing directories error. @@ -1137,7 +1200,7 @@ mod tests { // This tests that unveil can no longer be called. assert!(Permissions::unveil_no_more().is_ok()); assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err()); - assert!(fs::metadata(FILE_EXISTS).is_ok()); + assert!(fs::metadata(FILE_EXISTS_STR).is_ok()); // The below tests that Promises can only be removed and not added. initial_promises.remove_promises([Promise::Unveil]); assert_eq!(initial_promises.len(), 2); @@ -1150,43 +1213,38 @@ mod tests { assert!(Promises::new([Promise::Rpath]).pledge().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. - // drop(fs::metadata(FILE_EXISTS)); + // drop(fs::metadata(FILE_EXISTS_STR)); } - #[cfg(target_os = "openbsd")] + #[cfg(all(feature = "std", target_os = "openbsd"))] #[test] #[ignore] fn test_pledge_priv_drop() -> Result<(), PrivDropErr<Error>> { if Uid::geteuid().is_root() { Promises::new_priv_drop( - "zack", + c"nobody", [Promise::Inet, Promise::Rpath, Promise::Unveil], false, - || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), + || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 47, 0, 0)), ) - .and_then(|(_, mut promises)| { - Permissions::READ - .unveil(README) - .map_err(PrivDropErr::from) + .and_then(|(_, _)| { + Permissions::unveil_raw(README, c"r") + .map_err(PrivDropErr::Libc) .and_then(|()| { - fs::exists(README) - .map_err(PrivDropErr::Io) + fs::exists(README_STR) + .map_err(PrivDropErr::Other) .and_then(|exists| { Permissions::NONE .unveil(README) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|()| { - promises - .remove_promises_then_pledge([ - Promise::Rpath, - Promise::Unveil, - ]) - .map_err(PrivDropErr::Io) + Promises::pledge_raw(c"inet stdio") + .map_err(PrivDropErr::Libc) .map(|()| { assert!(exists); assert!( TcpListener::bind(SocketAddrV6::new( Ipv6Addr::LOCALHOST, - 80, + 792, 0, 0 )) @@ -1201,42 +1259,42 @@ mod tests { Ok(()) } } - #[cfg(target_os = "openbsd")] + #[cfg(all(feature = "std", target_os = "openbsd"))] #[test] #[ignore] fn test_pledge_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> { if Uid::geteuid().is_root() { Promises::new_chroot_then_priv_drop( - "zack", - "./", + c"nobody", + c"./", [Promise::Inet, Promise::Rpath, Promise::Unveil], false, - || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), + || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 382, 0, 0)), ) .and_then(|(_, mut promises)| { Permissions::READ .unveil(README) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|()| { - fs::exists(README) - .map_err(PrivDropErr::Io) + fs::exists(README_STR) + .map_err(PrivDropErr::Other) .and_then(|exists| { Permissions::NONE .unveil(README) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|()| { promises .remove_promises_then_pledge([ Promise::Rpath, Promise::Unveil, ]) - .map_err(PrivDropErr::Io) + .map_err(PrivDropErr::Libc) .map(|()| { assert!(exists); assert!( TcpListener::bind(SocketAddrV6::new( Ipv6Addr::LOCALHOST, - 80, + 588, 0, 0 )) diff --git a/src/openbsd.rs b/src/openbsd.rs @@ -1,16 +1,12 @@ #[cfg(doc)] use super::chroot_then_chdir; -use super::{NulOrIoErr, PrivDropErr, SUCCESS, UserInfo}; -use alloc::ffi::CString; -#[cfg(doc)] -use alloc::ffi::NulError; +use super::{Errno, PrivDropErr, SUCCESS, UserInfo}; use core::{ - ffi::{c_char, c_int}, + ffi::{CStr, c_char, c_int}, fmt::{self, Display, Formatter}, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, ptr, }; -use std::{io::Error, os::unix::ffi::OsStrExt as _, path::Path}; #[expect(unsafe_code, reason = "FFI requires unsafe")] unsafe extern "C" { /// [`pledge(2)`](https://man.openbsd.org/pledge.2). @@ -287,20 +283,14 @@ impl Promises { /// # use priv_sep::PrivDropErr; /// # use std::{io::Error, net::TcpListener}; /// # #[cfg(target_os = "openbsd")] - /// let (listener, promises) = Promises::new_priv_drop("nobody", [Promise::Inet], false, || { + /// let (listener, promises) = Promises::new_priv_drop(c"nobody", [Promise::Inet], false, || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) /// })?; /// # Ok::<_, PrivDropErr<Error>>(()) /// ``` #[inline] - pub fn new_priv_drop< - U: Into<Vec<u8>>, - Prom: AsRef<[Promise]>, - T, - E, - F: FnOnce() -> Result<T, E>, - >( - name: U, + pub fn new_priv_drop<Prom: AsRef<[Promise]>, T, E, F: FnOnce() -> Result<T, E>>( + name: &CStr, initial: Prom, retain_id_promise: bool, f: F, @@ -310,22 +300,22 @@ impl Promises { |val, promise| val.add(*promise), ); UserInfo::new(name) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|opt| { opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) .and_then(|info| { if info.is_root() { Err(PrivDropErr::RootEntry) } else { - promises.pledge().map_err(PrivDropErr::Io).and_then(|()| { + promises.pledge().map_err(PrivDropErr::Libc).and_then(|()| { f().map_err(PrivDropErr::Other).and_then(|res| { - info.setresid().map_err(PrivDropErr::Io).and_then(|()| { + info.setresid().map_err(PrivDropErr::Libc).and_then(|()| { if retain_id_promise { Ok(()) } else { promises .remove_then_pledge(Promise::Id) - .map_err(PrivDropErr::Io) + .map_err(PrivDropErr::Libc) } .map(|()| (res, promises)) }) @@ -349,19 +339,18 @@ impl Promises { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use tokio::net::TcpListener; /// # #[cfg(target_os = "openbsd")] - /// let listener_fut = Promises::new_priv_drop_async("nobody", [Promise::Inet], false, async || { + /// let listener_fut = Promises::new_priv_drop_async(c"nobody", [Promise::Inet], false, async || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await /// }); /// ``` #[inline] pub async fn new_priv_drop_async< - U: Into<Vec<u8>>, Prom: AsRef<[Promise]>, T, E, F: AsyncFnOnce() -> Result<T, E>, >( - name: U, + name: &CStr, initial: Prom, retain_id_promise: bool, f: F, @@ -379,23 +368,23 @@ impl Promises { } else { match promises.pledge() { Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| { - info.setresid().map_err(PrivDropErr::Io).and_then(|()| { + info.setresid().map_err(PrivDropErr::Libc).and_then(|()| { if retain_id_promise { Ok(()) } else { promises .remove_then_pledge(Promise::Id) - .map_err(PrivDropErr::Io) + .map_err(PrivDropErr::Libc) } .map(|()| (res, promises)) }) }), - Err(err) => Err(PrivDropErr::Io(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } } }, - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } /// Same as [`Self::new_priv_drop`] except [`chroot_then_chdir`] is called before [`Self::pledge`]ing `initial`. @@ -413,22 +402,15 @@ impl Promises { /// # use priv_sep::PrivDropErr; /// # use std::{io::Error, net::TcpListener}; /// # #[cfg(target_os = "openbsd")] - /// let (listener, promises) = Promises::new_chroot_then_priv_drop("nobody", "./", [Promise::Inet], false, || { + /// let (listener, promises) = Promises::new_chroot_then_priv_drop(c"nobody", c"./", [Promise::Inet], false, || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) /// })?; /// # Ok::<_, PrivDropErr<Error>>(()) /// ``` #[inline] - pub fn new_chroot_then_priv_drop< - U: Into<Vec<u8>>, - P: AsRef<Path>, - Prom: AsRef<[Promise]>, - T, - E, - F: FnOnce() -> Result<T, E>, - >( - name: U, - path: P, + pub fn new_chroot_then_priv_drop<Prom: AsRef<[Promise]>, T, E, F: FnOnce() -> Result<T, E>>( + name: &CStr, + path: &CStr, initial: Prom, retain_id_promise: bool, f: F, @@ -438,7 +420,7 @@ impl Promises { |val, promise| val.add(*promise), ); UserInfo::new(name) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|opt| { opt.ok_or_else(|| PrivDropErr::NoPasswdEntry) .and_then(|info| { @@ -446,18 +428,18 @@ impl Promises { Err(PrivDropErr::RootEntry) } else { super::chroot_then_chdir(path) - .map_err(PrivDropErr::from) + .map_err(PrivDropErr::Libc) .and_then(|()| { - promises.pledge().map_err(PrivDropErr::Io).and_then(|()| { + promises.pledge().map_err(PrivDropErr::Libc).and_then(|()| { f().map_err(PrivDropErr::Other).and_then(|res| { - info.setresid().map_err(PrivDropErr::Io).and_then( + info.setresid().map_err(PrivDropErr::Libc).and_then( |()| { if retain_id_promise { Ok(()) } else { promises .remove_then_pledge(Promise::Id) - .map_err(PrivDropErr::Io) + .map_err(PrivDropErr::Libc) } .map(|()| (res, promises)) }, @@ -483,21 +465,19 @@ impl Promises { /// # use core::net::{Ipv6Addr, SocketAddrV6}; /// # use tokio::net::TcpListener; /// # #[cfg(target_os = "openbsd")] - /// let listener_fut = Promises::new_chroot_then_priv_drop_async("nobody", "./", [Promise::Inet], false, async || { + /// let listener_fut = Promises::new_chroot_then_priv_drop_async(c"nobody", c"./", [Promise::Inet], false, async || { /// TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)).await /// }); /// ``` #[inline] pub async fn new_chroot_then_priv_drop_async< - U: Into<Vec<u8>>, - P: AsRef<Path>, Prom: AsRef<[Promise]>, T, E, F: AsyncFnOnce() -> Result<T, E>, >( - name: U, - path: P, + name: &CStr, + path: &CStr, initial: Prom, retain_id_promise: bool, f: F, @@ -516,25 +496,25 @@ impl Promises { match super::chroot_then_chdir(path) { Ok(()) => match promises.pledge() { Ok(()) => f().await.map_err(PrivDropErr::Other).and_then(|res| { - info.setresid().map_err(PrivDropErr::Io).and_then(|()| { + info.setresid().map_err(PrivDropErr::Libc).and_then(|()| { if retain_id_promise { Ok(()) } else { promises .remove_then_pledge(Promise::Id) - .map_err(PrivDropErr::Io) + .map_err(PrivDropErr::Libc) } .map(|()| (res, promises)) }) }), - Err(err) => Err(PrivDropErr::Io(err)), + Err(err) => Err(PrivDropErr::Libc(err)), }, - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } } }, - Err(err) => Err(PrivDropErr::from(err)), + Err(err) => Err(PrivDropErr::Libc(err)), } } /// Returns the number of [`Promise`]s. @@ -621,7 +601,7 @@ impl Promises { /// /// # Errors /// - /// Returns [`Error`] iff `pledge` does. + /// Errors iff `pledge(2)` does. /// /// # Examples /// @@ -632,7 +612,7 @@ impl Promises { /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).retain_then_pledge([Promise::Rpath]).is_ok()); /// ``` #[inline] - pub fn retain_then_pledge<P: AsRef<[Promise]>>(&mut self, promises: P) -> Result<(), Error> { + pub fn retain_then_pledge<P: AsRef<[Promise]>>(&mut self, promises: P) -> Result<(), Errno> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. let cur = Self(self.0); @@ -672,7 +652,7 @@ impl Promises { /// /// # Errors /// - /// Returns [`Error`] iff `pledge` does. + /// Errors iff `pledge(2)` does. /// /// # Examples /// @@ -686,7 +666,7 @@ impl Promises { pub fn remove_promises_then_pledge<P: AsRef<[Promise]>>( &mut self, promises: P, - ) -> Result<(), Error> { + ) -> Result<(), Errno> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. let cur = Self(self.0); @@ -721,7 +701,7 @@ impl Promises { /// /// # Errors /// - /// Returns [`Error`] iff `pledge` does. + /// Errors iff `pledge(2)` does. /// /// # Examples /// @@ -732,7 +712,7 @@ impl Promises { /// assert!(Promises::new([Promise::Rpath, Promise::Stdio]).remove_then_pledge(Promise::Rpath).is_ok()); /// ``` #[inline] - pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), Error> { + pub fn remove_then_pledge(&mut self, promise: Promise) -> Result<(), Errno> { // We opt for the easy way by copying `self`. This should be the fastest for the // typical case since `self` likely has very few `Promise`s, and it makes for less code. let cur = Self(self.0); @@ -745,9 +725,9 @@ impl Promises { } /// Invokes `pledge(2)` always passing `NULL` for `execpromises` and `promises` for `promises`. /// - /// This function MUST only be called by [`Self::pledge`] and [`Self::pledge_none`]. + /// This function MUST only be called by [`Self::pledge`], [`Self::pledge_none`] and [`Self::pledge_raw`]. #[expect(unsafe_code, reason = "pledge(2) takes in pointers")] - fn inner_pledge(promises: *const c_char) -> Result<(), Error> { + fn inner_pledge(promises: *const c_char) -> Result<(), Errno> { // SAFETY: // `promises` is either null or valid as can be seen in the only functions that call this // function: @@ -755,7 +735,7 @@ impl Promises { if unsafe { pledge(promises, ptr::null()) } == SUCCESS { Ok(()) } else { - Err(Error::last_os_error()) + Err(Errno::last()) } } /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) always passing in @@ -763,7 +743,7 @@ impl Promises { /// /// # Errors /// - /// Returns [`Error`] iff `pledge(2)` errors. + /// Errors iff `pledge(2)` does. /// /// # Examples /// @@ -773,146 +753,329 @@ impl Promises { /// # #[cfg(target_os = "openbsd")] /// assert!(Promises::new([Promise::Stdio]).pledge().is_ok()); /// ``` - #[expect( - unsafe_code, - reason = "we manually construct a valid CString and ensure its safety" - )] + #[expect(unsafe_code, reason = "comment justifies correctness")] #[expect( clippy::arithmetic_side_effects, clippy::indexing_slicing, - reason = "we replace a trailing space with 0 to ensure CString is correctly constructed" + reason = "comments justify correctness" )] #[expect( clippy::cognitive_complexity, clippy::too_many_lines, - reason = "many if blocks to handle each Promise" + reason = "a lot of Promises to check" )] #[inline] - pub fn pledge(&self) -> Result<(), Error> { - let mut p = Vec::new(); + pub fn pledge(&self) -> Result<(), Errno> { + // Most of this code was programmatically generated. + /// b"audio ". + const AUDIO: &[u8; 6] = b"audio "; + /// b"bpf ". + const BPF: &[u8; 4] = b"bpf "; + /// b"chown ". + const CHOWN: &[u8; 6] = b"chown "; + /// b"cpath ". + const CPATH: &[u8; 6] = b"cpath "; + /// b"disklabel ". + const DISKLABEL: &[u8; 10] = b"disklabel "; + /// b"dns ". + const DNS: &[u8; 4] = b"dns "; + /// b"dpath ". + const DPATH: &[u8; 6] = b"dpath "; + /// b"drm ". + const DRM: &[u8; 4] = b"drm "; + /// b"error ". + const ERROR: &[u8; 6] = b"error "; + /// b"exec ". + const EXEC: &[u8; 5] = b"exec "; + /// b"fattr ". + const FATTR: &[u8; 6] = b"fattr "; + /// b"flock ". + const FLOCK: &[u8; 6] = b"flock "; + /// b"getpw ". + const GETPW: &[u8; 6] = b"getpw "; + /// b"id ". + const ID: &[u8; 3] = b"id "; + /// b"inet ". + const INET: &[u8; 5] = b"inet "; + /// b"mcast ". + const MCAST: &[u8; 6] = b"mcast "; + /// b"pf ". + const PF: &[u8; 3] = b"pf "; + /// b"proc ". + const PROC: &[u8; 5] = b"proc "; + /// b"prot_exec ". + #[expect(clippy::doc_markdown, reason = "false positive")] + const PROT_EXEC: &[u8; 10] = b"prot_exec "; + /// b"ps ". + const PS: &[u8; 3] = b"ps "; + /// b"recvfd ". + const RECVFD: &[u8; 7] = b"recvfd "; + /// b"route ". + const ROUTE: &[u8; 6] = b"route "; + /// b"rpath ". + const RPATH: &[u8; 6] = b"rpath "; + /// b"sendfd ". + const SENDFD: &[u8; 7] = b"sendfd "; + /// b"settime ". + const SETTIME: &[u8; 8] = b"settime "; + /// b"stdio ". + const STDIO: &[u8; 6] = b"stdio "; + /// b"tape ". + const TAPE: &[u8; 5] = b"tape "; + /// b"tmppath ". + const TMPPATH: &[u8; 8] = b"tmppath "; + /// b"tty ". + const TTY: &[u8; 4] = b"tty "; + /// b"unix ". + const UNIX: &[u8; 5] = b"unix "; + /// b"unveil ". + const UNVEIL: &[u8; 7] = b"unveil "; + /// b"video ". + const VIDEO: &[u8; 6] = b"video "; + /// b"vminfo ". + const VMINFO: &[u8; 7] = b"vminfo "; + /// b"vmm ". + const VMM: &[u8; 4] = b"vmm "; + /// b"wpath ". + const WPATH: &[u8; 6] = b"wpath "; + /// b"wroute ". + const WROUTE: &[u8; 7] = b"wroute "; + /// Maximum length needed to pledge all promises. + const MAX_LEN: usize = AUDIO.len() + + BPF.len() + + CHOWN.len() + + CPATH.len() + + DISKLABEL.len() + + DNS.len() + + DPATH.len() + + DRM.len() + + ERROR.len() + + EXEC.len() + + FATTR.len() + + FLOCK.len() + + GETPW.len() + + ID.len() + + INET.len() + + MCAST.len() + + PF.len() + + PROC.len() + + PROT_EXEC.len() + + PS.len() + + RECVFD.len() + + ROUTE.len() + + RPATH.len() + + SENDFD.len() + + SETTIME.len() + + STDIO.len() + + TAPE.len() + + TMPPATH.len() + + TTY.len() + + UNIX.len() + + UNVEIL.len() + + VIDEO.len() + + VMINFO.len() + + VMM.len() + + WPATH.len() + + WROUTE.len(); + let mut max_buffer = [0; MAX_LEN]; + let mut idx = 0; + let mut len = 0; + // Our additions are all free from overflow since a compilation error would have occurred for + // `MAX_LEN`. + // Our indexing is correct since `len - idx` is always the length of the value we copy from. if self.contains(Promise::Audio) { - p.extend_from_slice(b"audio "); + len = AUDIO.len(); + max_buffer[..len].copy_from_slice(AUDIO); + idx = len; } if self.contains(Promise::Bpf) { - p.extend_from_slice(b"bpf "); + len += BPF.len(); + max_buffer[idx..len].copy_from_slice(BPF); + idx = len; } if self.contains(Promise::Chown) { - p.extend_from_slice(b"chown "); + len += CHOWN.len(); + max_buffer[idx..len].copy_from_slice(CHOWN); + idx = len; } if self.contains(Promise::Cpath) { - p.extend_from_slice(b"cpath "); + len += CPATH.len(); + max_buffer[idx..len].copy_from_slice(CPATH); + idx = len; } if self.contains(Promise::Disklabel) { - p.extend_from_slice(b"disklabel "); + len += DISKLABEL.len(); + max_buffer[idx..len].copy_from_slice(DISKLABEL); + idx = len; } if self.contains(Promise::Dns) { - p.extend_from_slice(b"dns "); + len += DNS.len(); + max_buffer[idx..len].copy_from_slice(DNS); + idx = len; } if self.contains(Promise::Dpath) { - p.extend_from_slice(b"dpath "); + len += DPATH.len(); + max_buffer[idx..len].copy_from_slice(DPATH); + idx = len; } if self.contains(Promise::Drm) { - p.extend_from_slice(b"drm "); + len += DRM.len(); + max_buffer[idx..len].copy_from_slice(DRM); + idx = len; } if self.contains(Promise::Error) { - p.extend_from_slice(b"error "); + len += ERROR.len(); + max_buffer[idx..len].copy_from_slice(ERROR); + idx = len; } if self.contains(Promise::Exec) { - p.extend_from_slice(b"exec "); + len += EXEC.len(); + max_buffer[idx..len].copy_from_slice(EXEC); + idx = len; } if self.contains(Promise::Fattr) { - p.extend_from_slice(b"fattr "); + len += FATTR.len(); + max_buffer[idx..len].copy_from_slice(FATTR); + idx = len; } if self.contains(Promise::Flock) { - p.extend_from_slice(b"flock "); + len += FLOCK.len(); + max_buffer[idx..len].copy_from_slice(FLOCK); + idx = len; } if self.contains(Promise::Getpw) { - p.extend_from_slice(b"getpw "); + len += GETPW.len(); + max_buffer[idx..len].copy_from_slice(GETPW); + idx = len; } if self.contains(Promise::Id) { - p.extend_from_slice(b"id "); + len += ID.len(); + max_buffer[idx..len].copy_from_slice(ID); + idx = len; } if self.contains(Promise::Inet) { - p.extend_from_slice(b"inet "); + len += INET.len(); + max_buffer[idx..len].copy_from_slice(INET); + idx = len; } if self.contains(Promise::Mcast) { - p.extend_from_slice(b"mcast "); + len += MCAST.len(); + max_buffer[idx..len].copy_from_slice(MCAST); + idx = len; } if self.contains(Promise::Pf) { - p.extend_from_slice(b"pf "); + len += PF.len(); + max_buffer[idx..len].copy_from_slice(PF); + idx = len; } if self.contains(Promise::Proc) { - p.extend_from_slice(b"proc "); + len += PROC.len(); + max_buffer[idx..len].copy_from_slice(PROC); + idx = len; } if self.contains(Promise::ProtExec) { - p.extend_from_slice(b"prot_exec "); + len += PROT_EXEC.len(); + max_buffer[idx..len].copy_from_slice(PROT_EXEC); + idx = len; } if self.contains(Promise::Ps) { - p.extend_from_slice(b"ps "); + len += PS.len(); + max_buffer[idx..len].copy_from_slice(PS); + idx = len; } if self.contains(Promise::Recvfd) { - p.extend_from_slice(b"recvfd "); + len += RECVFD.len(); + max_buffer[idx..len].copy_from_slice(RECVFD); + idx = len; } if self.contains(Promise::Route) { - p.extend_from_slice(b"route "); + len += ROUTE.len(); + max_buffer[idx..len].copy_from_slice(ROUTE); + idx = len; } if self.contains(Promise::Rpath) { - p.extend_from_slice(b"rpath "); + len += RPATH.len(); + max_buffer[idx..len].copy_from_slice(RPATH); + idx = len; } if self.contains(Promise::Sendfd) { - p.extend_from_slice(b"sendfd "); + len += SENDFD.len(); + max_buffer[idx..len].copy_from_slice(SENDFD); + idx = len; } if self.contains(Promise::Settime) { - p.extend_from_slice(b"settime "); + len += SETTIME.len(); + max_buffer[idx..len].copy_from_slice(SETTIME); + idx = len; } if self.contains(Promise::Stdio) { - p.extend_from_slice(b"stdio "); + len += STDIO.len(); + max_buffer[idx..len].copy_from_slice(STDIO); + idx = len; } if self.contains(Promise::Tape) { - p.extend_from_slice(b"tape "); + len += TAPE.len(); + max_buffer[idx..len].copy_from_slice(TAPE); + idx = len; } if self.contains(Promise::Tmppath) { - p.extend_from_slice(b"tmppath "); + len += TMPPATH.len(); + max_buffer[idx..len].copy_from_slice(TMPPATH); + idx = len; } if self.contains(Promise::Tty) { - p.extend_from_slice(b"tty "); + len += TTY.len(); + max_buffer[idx..len].copy_from_slice(TTY); + idx = len; } if self.contains(Promise::Unix) { - p.extend_from_slice(b"unix "); + len += UNIX.len(); + max_buffer[idx..len].copy_from_slice(UNIX); + idx = len; } if self.contains(Promise::Unveil) { - p.extend_from_slice(b"unveil "); + len += UNVEIL.len(); + max_buffer[idx..len].copy_from_slice(UNVEIL); + idx = len; } if self.contains(Promise::Video) { - p.extend_from_slice(b"video "); + len += VIDEO.len(); + max_buffer[idx..len].copy_from_slice(VIDEO); + idx = len; } if self.contains(Promise::Vminfo) { - p.extend_from_slice(b"vminfo "); + len += VMINFO.len(); + max_buffer[idx..len].copy_from_slice(VMINFO); + idx = len; } if self.contains(Promise::Vmm) { - p.extend_from_slice(b"vmm "); + len += VMM.len(); + max_buffer[idx..len].copy_from_slice(VMM); + idx = len; } if self.contains(Promise::Wpath) { - p.extend_from_slice(b"wpath "); + len += WPATH.len(); + max_buffer[idx..len].copy_from_slice(WPATH); + idx = len; } if self.contains(Promise::Wroute) { - p.extend_from_slice(b"wroute "); + len += WROUTE.len(); + max_buffer[idx..len].copy_from_slice(WROUTE); } - let idx = p.len(); - if idx == 0 { - p.push(0); + if len == 0 { + len += 1; } else { // All promises have a space after them which means // we must replace the last promise's space with // 0/nul byte. - // `idx` is at least 1 based on the above check. - p[idx - 1] = 0; + // `len` is at least 1 based on the above check. + max_buffer[len - 1] = 0; } // SAFETY: - // `p` was populated above with correct ASCII-encoding of the literal + // `max_buffer[..len]` 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. - let arg = unsafe { CString::from_vec_with_nul_unchecked(p) }; + // `max_buffer[..len]` ends with a 0/nul byte. + let arg = unsafe { CStr::from_bytes_with_nul_unchecked(&max_buffer[..len]) }; Self::inner_pledge(arg.as_ptr()) } /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) with `NULL` for both `promises` and @@ -920,7 +1083,7 @@ impl Promises { /// /// # Errors /// - /// Returns [`Error`] iff `pledge(2)` errors. + /// Errors iff `pledge(2)` does. /// /// # Examples /// @@ -931,10 +1094,31 @@ impl Promises { /// assert!(Promises::pledge_none().is_ok()); /// ``` #[inline] - pub fn pledge_none() -> Result<(), Error> { + pub fn pledge_none() -> Result<(), Errno> { // `NULL` is always valid for `promises`. Self::inner_pledge(ptr::null()) } + /// Invokes [`pledge(2)`](https://man.openbsd.org/pledge.2) with [`CStr::as_ptr`] from `promises` for + /// `promises` and `NULL` for `execpromises`. + /// + /// Exposes an API that allows one to `pledge(2)` "directly". + /// + /// # Errors + /// + /// Errors iff `pledge(2)` does. + /// + /// # Examples + /// + /// ```no_run + /// # #[cfg(target_os = "openbsd")] + /// # use priv_sep::Promises; + /// # #[cfg(target_os = "openbsd")] + /// assert!(Promises::pledge_raw(c"rpath stdio").is_ok()); + /// ``` + #[inline] + pub fn pledge_raw(promises: &CStr) -> Result<(), Errno> { + Self::inner_pledge(promises.as_ptr()) + } } impl PartialEq<&Self> for Promises { #[inline] @@ -1033,7 +1217,7 @@ impl Permissions { /// /// This function MUST only be called by the functions [`Self::unveil`] and [`Self::unveil_no_more`]. #[expect(unsafe_code, reason = "unveil(2) takes in pointers")] - fn inner_unveil(path: *const c_char, permissions: *const c_char) -> Result<(), Error> { + fn inner_unveil(path: *const c_char, permissions: *const c_char) -> Result<(), Errno> { // SAFETY: // `path` and `permissions` are either null or valid as can be seen in the only functions that call this // function: @@ -1041,7 +1225,7 @@ impl Permissions { if unsafe { unveil(path, permissions) } == SUCCESS { Ok(()) } else { - Err(Error::last_os_error()) + Err(Errno::last()) } } /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) @@ -1049,76 +1233,78 @@ impl Permissions { /// /// # Errors /// - /// Returns [`NulError`] iff [`CString::new`] does. - /// Returns [`Error`] iff `unveil(2)` errors. + /// Errors iff `unveil(2)` does. /// /// # Examples /// /// ```no_run /// # #[cfg(target_os = "openbsd")] - /// # use std::io::ErrorKind; - /// # #[cfg(target_os = "openbsd")] - /// # use priv_sep::{Permissions, NulOrIoErr}; + /// # use priv_sep::{Errno, Permissions}; /// # #[cfg(target_os = "openbsd")] - /// assert!(Permissions::READ.unveil("/path/to/read").is_ok()); + /// assert!(Permissions::READ.unveil(c"/path/to/read").is_ok()); /// # #[cfg(target_os = "openbsd")] - /// assert!(Permissions::READ.unveil("/path/does/not/exist").map_or_else( - /// |err| match err { - /// NulOrIoErr::Io(e) => e.kind() == ErrorKind::NotFound, - /// NulOrIoErr::Nul(_) => false, - /// }, + /// assert!(Permissions::READ.unveil(c"/path/does/not/exist").map_or_else( + /// |err| err == Errno::ENOENT, /// |()| false /// )); /// ``` - #[expect( - unsafe_code, - reason = "we manually construct a valid CString and ensure its safety" - )] - #[expect( - clippy::arithmetic_side_effects, - reason = "we pre-allocate a Vec with the exact capacity which is capped at 5" - )] #[inline] - pub fn unveil<P: AsRef<Path>>(self, path: P) -> Result<(), NulOrIoErr> { - CString::new(path.as_ref().as_os_str().as_bytes()).map_or_else( - |e| Err(NulOrIoErr::Nul(e)), - |path_c| { - // The max sum is 5, so overflow is not possible. - let mut vec = Vec::with_capacity( - usize::from(self.is_enabled(Permission::Create)) - + usize::from(self.is_enabled(Permission::Execute)) - + usize::from(self.is_enabled(Permission::Read)) - + usize::from(self.is_enabled(Permission::Write)) - + 1, - ); - if self.is_enabled(Permission::Create) { - vec.push(b'c'); - } - if self.is_enabled(Permission::Execute) { - vec.push(b'x'); - } + pub fn unveil(self, path: &CStr) -> Result<(), Errno> { + let perms = if self.is_enabled(Permission::Create) { + if self.is_enabled(Permission::Execute) { if self.is_enabled(Permission::Read) { - vec.push(b'r'); + if self.is_enabled(Permission::Write) { + c"crwx" + } else { + c"crx" + } + } else if self.is_enabled(Permission::Write) { + c"cwx" + } else { + c"cx" } + } else if self.is_enabled(Permission::Read) { if self.is_enabled(Permission::Write) { - vec.push(b'w'); + c"crw" + } else { + c"cr" } - vec.push(0); - // SAFETY: - // `vec` was populated above with correct ASCII-encoding of the literal - // values all of which do not have 0 bytes. - // `vec` ends with a 0/nul byte. - let perm_c = unsafe { CString::from_vec_with_nul_unchecked(vec) }; - Self::inner_unveil(path_c.as_ptr(), perm_c.as_ptr()).map_err(NulOrIoErr::Io) - }, - ) + } else if self.is_enabled(Permission::Write) { + c"cw" + } else { + c"c" + } + } else if self.is_enabled(Permission::Execute) { + if self.is_enabled(Permission::Read) { + if self.is_enabled(Permission::Write) { + c"rwx" + } else { + c"rx" + } + } else if self.is_enabled(Permission::Write) { + c"wx" + } else { + c"x" + } + } else if self.is_enabled(Permission::Read) { + if self.is_enabled(Permission::Write) { + c"rw" + } else { + c"r" + } + } else if self.is_enabled(Permission::Write) { + c"w" + } else { + c"" + }; + Self::inner_unveil(path.as_ptr(), perms.as_ptr()) } /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) by passing `NULL` for both `path` and /// `permissions`. /// /// # Errors /// - /// Returns [`Error`] when a problem occurs. + /// Errors iff `unveil(2)` does. /// /// # Examples /// @@ -1129,10 +1315,37 @@ impl Permissions { /// assert!(Permissions::unveil_no_more().is_ok()); /// ``` #[inline] - pub fn unveil_no_more() -> Result<(), Error> { + pub fn unveil_no_more() -> Result<(), Errno> { // `NULL` is valid for both `path` and `permissions`. Self::inner_unveil(ptr::null(), ptr::null()) } + /// Invokes [`unveil(2)`](https://man.openbsd.org/unveil.2) + /// passing [`CStr::as_ptr`] from `path` for `path` and `permissions.as_ptr()` for `permissions`. + /// + /// Exposes an API that allows one to `unveil(2)` "directly". + /// + /// # Errors + /// + /// Errors iff `unveil(2)` does. + /// + /// # Examples + /// + /// ```no_run + /// # #[cfg(target_os = "openbsd")] + /// # use priv_sep::{Errno, Permissions}; + /// # #[cfg(target_os = "openbsd")] + /// assert!(Permissions::unveil_raw(c"/path/to/read", c"r").is_ok()); + /// # #[cfg(target_os = "openbsd")] + /// assert!(Permissions::unveil_raw(c"/path/does/not/exist", c"r").map_or_else( + /// |err| err == Errno::ENOENT, + /// |()| false + /// )); + /// ``` + #[inline] + pub fn unveil_raw(path: &CStr, permissions: &CStr) -> Result<(), Errno> { + // `NULL` is valid for both `path` and `permissions`. + Self::inner_unveil(path.as_ptr(), permissions.as_ptr()) + } } impl Display for Permissions { #[inline] @@ -1221,182 +1434,3 @@ impl PartialEq<Permissions> for &Permissions { **self == *other } } -#[cfg(test)] -mod tests { - use super::{Permissions, Promise, Promises}; - use crate::{PrivDropErr, Uid}; - use core::net::{Ipv6Addr, SocketAddrV6}; - use std::{fs, io::Error, net::TcpListener}; - const README: &str = "README.md"; - #[test] - #[ignore] - fn test_pledge_unveil() { - const FILE_EXISTS: &str = "/home/zack/foo.txt"; - _ = fs::metadata(FILE_EXISTS) - .expect(format!("{FILE_EXISTS} does not exist, so unit testing cannot occur").as_str()); - const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23"; - drop(fs::metadata(FILE_NOT_EXISTS).expect_err( - format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), - )); - const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/"; - drop( - fs::metadata(DIR_NOT_EXISTS).expect_err( - format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str(), - ), - ); - // This tests that a NULL `promise` does nothing. - assert!(Promises::pledge_none().is_ok()); - print!(""); - assert!(Promises::ALL.pledge().is_ok()); - // This tests that duplicates are ignored as well as the implementation of PartialEq. - let mut initial_promises = Promises::new([ - Promise::Stdio, - Promise::Unveil, - Promise::Rpath, - Promise::Stdio, - ]); - assert!(initial_promises.len() == 3); - assert!( - initial_promises == Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil]) - ); - // Test retain. - assert!({ - let mut vals = Promises::new([ - Promise::Audio, - Promise::Bpf, - Promise::Chown, - Promise::Cpath, - Promise::Error, - Promise::Exec, - ]); - vals.retain([Promise::Error, Promise::Chown]); - vals.len() == 2 && vals.contains(Promise::Chown) && vals.contains(Promise::Error) - }); - assert!(initial_promises.pledge().is_ok()); - // This tests unveil with no permissions. - assert!(Permissions::NONE.unveil(FILE_EXISTS).is_ok()); - assert!(fs::metadata(FILE_EXISTS).is_err()); - // This tests unveil with read permissions, - // and one can unveil more permissions (unlike pledge which can only remove promises). - assert!(Permissions::READ.unveil(FILE_EXISTS).is_ok()); - assert!(fs::metadata(FILE_EXISTS).is_ok()); - // This tests that calls to unveil on missing files don't error. - assert!(Permissions::NONE.unveil(FILE_NOT_EXISTS).is_ok()); - // This tests that calls to unveil on missing directories error. - assert!(Permissions::NONE.unveil(DIR_NOT_EXISTS).is_err()); - // This tests that unveil can no longer be called. - assert!(Permissions::unveil_no_more().is_ok()); - assert!(Permissions::NONE.unveil(FILE_EXISTS).is_err()); - assert!(fs::metadata(FILE_EXISTS).is_ok()); - // The below tests that Promises can only be removed and not added. - initial_promises.remove_promises([Promise::Unveil]); - assert_eq!(initial_promises.len(), 2); - initial_promises.remove(Promise::Rpath); - assert_eq!(initial_promises.len(), 1); - initial_promises.remove(Promise::Rpath); - assert_eq!(initial_promises.len(), 1); - assert!(initial_promises.pledge().is_ok()); - print!(""); - assert!(Promises::new([Promise::Rpath]).pledge().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. - // drop(fs::metadata(FILE_EXISTS)); - } - #[test] - #[ignore] - fn test_pledge_priv_drop() -> Result<(), PrivDropErr<Error>> { - if Uid::geteuid().is_root() { - Promises::new_priv_drop( - "zack", - [Promise::Inet, Promise::Rpath, Promise::Unveil], - false, - || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), - ) - .and_then(|(_, mut promises)| { - Permissions::READ - .unveil(README) - .map_err(PrivDropErr::from) - .and_then(|()| { - fs::exists(README) - .map_err(PrivDropErr::Io) - .and_then(|exists| { - Permissions::NONE - .unveil(README) - .map_err(PrivDropErr::from) - .and_then(|()| { - promises - .remove_promises_then_pledge([ - Promise::Rpath, - Promise::Unveil, - ]) - .map_err(PrivDropErr::Io) - .map(|()| { - assert!(exists); - assert!( - TcpListener::bind(SocketAddrV6::new( - Ipv6Addr::LOCALHOST, - 80, - 0, - 0 - )) - .is_err() - ); - }) - }) - }) - }) - }) - } else { - Ok(()) - } - } - #[test] - #[ignore] - fn test_pledge_chroot_priv_drop() -> Result<(), PrivDropErr<Error>> { - if Uid::geteuid().is_root() { - Promises::new_chroot_then_priv_drop( - "zack", - "./", - [Promise::Inet, Promise::Rpath, Promise::Unveil], - false, - || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)), - ) - .and_then(|(_, mut promises)| { - Permissions::READ - .unveil(README) - .map_err(PrivDropErr::from) - .and_then(|()| { - fs::exists(README) - .map_err(PrivDropErr::Io) - .and_then(|exists| { - Permissions::NONE - .unveil(README) - .map_err(PrivDropErr::from) - .and_then(|()| { - promises - .remove_promises_then_pledge([ - Promise::Rpath, - Promise::Unveil, - ]) - .map_err(PrivDropErr::Io) - .map(|()| { - assert!(exists); - assert!( - TcpListener::bind(SocketAddrV6::new( - Ipv6Addr::LOCALHOST, - 80, - 0, - 0 - )) - .is_err() - ); - }) - }) - }) - }) - }) - } else { - Ok(()) - } - } -}