tests.rs (10549B)
1 #![cfg_attr( 2 feature = "std", 3 expect( 4 clippy::std_instead_of_core, 5 reason = "false positive until core::io::ErrorKind is stable" 6 ) 7 )] 8 #[cfg(feature = "std")] 9 use super::PrivDropErr; 10 use super::{Errno, Gid, SetresidErr, Uid, UserInfo}; 11 #[cfg(all(feature = "std", target_os = "openbsd"))] 12 use super::{Permissions, Promise, Promises}; 13 #[cfg(feature = "std")] 14 use core::net::{Ipv6Addr, SocketAddrV6}; 15 #[cfg(all(feature = "std", target_os = "openbsd"))] 16 extern crate alloc; 17 #[cfg(all(feature = "std", target_os = "openbsd"))] 18 use alloc::format; 19 #[cfg(all(feature = "std", target_os = "openbsd"))] 20 use std::io::{self, Write as _}; 21 #[cfg(feature = "std")] 22 use std::{ 23 fs, 24 io::{Error, ErrorKind}, 25 net::TcpListener, 26 }; 27 use tokio as _; 28 #[cfg(feature = "std")] 29 const README: &str = "README.md"; 30 #[test] 31 fn chdir() { 32 assert_eq!(crate::chdir(c"./"), Ok(())); 33 } 34 #[cfg(not(target_os = "macos"))] 35 #[test] 36 fn setgroups() { 37 if Uid::geteuid().is_root() { 38 assert_eq!(crate::setgroups(&[Gid::getegid()]), Ok(())); 39 } else { 40 assert_eq!(crate::setgroups(&[Gid::getegid()]), Err(Errno::EPERM)); 41 } 42 } 43 #[test] 44 fn getuid() { 45 _ = Uid::getuid(); 46 } 47 #[test] 48 fn geteuid() { 49 _ = Uid::geteuid(); 50 } 51 #[test] 52 fn getgid() { 53 _ = Gid::getgid(); 54 } 55 #[test] 56 fn getegid() { 57 _ = Gid::getegid(); 58 } 59 #[test] 60 fn setresuid() -> Result<(), Errno> { 61 Uid::geteuid().setresuid() 62 } 63 #[test] 64 fn setresgid() -> Result<(), Errno> { 65 Gid::getegid().setresgid() 66 } 67 #[test] 68 fn user_info_new() { 69 assert_eq!( 70 UserInfo::new(c"root"), 71 Ok(Some(UserInfo { 72 uid: Uid::ROOT, 73 gid: Gid(0), 74 })) 75 ); 76 } 77 #[test] 78 fn user_info_setresid() -> Result<(), Errno> { 79 UserInfo { 80 uid: Uid::geteuid(), 81 gid: Gid::getegid(), 82 } 83 .setresid() 84 } 85 #[test] 86 fn user_info_setresid_if_valid() -> Result<(), SetresidErr> { 87 UserInfo { 88 uid: Uid::geteuid(), 89 gid: Gid::getegid(), 90 } 91 .setresid_if_valid() 92 } 93 #[test] 94 fn user_info_setresid_if_valid_failure() { 95 assert_eq!( 96 UserInfo { 97 uid: Uid::geteuid(), 98 gid: Gid(u32::MAX), 99 } 100 .setresid_if_valid(), 101 Err(SetresidErr::GidMismatch) 102 ); 103 } 104 #[cfg(feature = "std")] 105 #[test] 106 #[ignore = "primarily useful for root and interferes with chroot_drop_priv"] 107 fn priv_drop() { 108 if Uid::geteuid().is_root() { 109 assert!( 110 UserInfo::priv_drop(c"nobody", || { 111 TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 443, 0, 0)) 112 }) 113 .is_ok_and(|_| true) 114 ); 115 assert!( 116 TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)).map_or_else( 117 |e| matches!(e.kind(), ErrorKind::PermissionDenied), 118 |_| false 119 ) 120 ); 121 } else { 122 assert!( 123 UserInfo::priv_drop(c"root", || Ok::<_, Error>(())) 124 .map_or_else(|e| matches!(e, PrivDropErr::RootEntry), |()| false) 125 ); 126 } 127 } 128 #[cfg(feature = "std")] 129 #[test] 130 #[ignore = "primarily useful for root and interferes with priv_drop"] 131 fn chroot_drop_priv() { 132 if Uid::geteuid().is_root() { 133 assert!( 134 UserInfo::chroot_then_priv_drop(c"nobody", c"./", false, || { 135 TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 987, 0, 0)) 136 }) 137 .is_ok_and(|_| { 138 fs::exists(README).is_ok_and(|exists| { 139 exists 140 && TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 82, 0, 0)) 141 .map_or_else( 142 |e| matches!(e.kind(), ErrorKind::PermissionDenied), 143 |_| false, 144 ) 145 }) 146 }) 147 ); 148 } 149 } 150 #[expect( 151 clippy::panic, 152 reason = "reasonable to expect files to not exist that need to in order to test pledge and unveil" 153 )] 154 #[expect( 155 clippy::expect_used, 156 reason = "kinda reasonable to expect files to exist that mustn't in order to test pledge and unveil" 157 )] 158 #[cfg(all(feature = "std", target_os = "openbsd"))] 159 #[test] 160 #[ignore = "interferes with other tests"] 161 fn unveil_pledge() { 162 const FILE_EXISTS: &str = "/home/zack/foo.txt"; 163 const FILE_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23"; 164 const DIR_NOT_EXISTS: &str = "/home/zack/aadkjfasj3s23/"; 165 _ = fs::metadata(FILE_EXISTS) 166 .unwrap_or_else(|_e| panic!("{FILE_EXISTS} does not exist, so unit testing cannot occur")); 167 drop( 168 fs::metadata(FILE_NOT_EXISTS) 169 .expect_err(format!("{FILE_NOT_EXISTS} exists, so unit testing cannot occur").as_str()), 170 ); 171 drop( 172 fs::metadata(DIR_NOT_EXISTS) 173 .expect_err(format!("{DIR_NOT_EXISTS} exists, so unit testing cannot occur").as_str()), 174 ); 175 // This tests that a NULL `promise` does nothing. 176 assert_eq!(Promises::pledge_none(), Ok(())); 177 assert!(writeln!(io::stdout()).is_ok_and(|()| true)); 178 assert_eq!(Promises::ALL.pledge(), Ok(())); 179 // This tests that duplicates are ignored as well as the implementation of PartialEq. 180 let mut initial_promises = Promises::new([ 181 Promise::Stdio, 182 Promise::Unveil, 183 Promise::Rpath, 184 Promise::Stdio, 185 ]); 186 assert_eq!(initial_promises.len(), 3); 187 assert_eq!( 188 initial_promises, 189 Promises::new([Promise::Rpath, Promise::Stdio, Promise::Unveil]) 190 ); 191 // Test retain. 192 let mut vals = Promises::new([ 193 Promise::Audio, 194 Promise::Bpf, 195 Promise::Chown, 196 Promise::Cpath, 197 Promise::Error, 198 Promise::Exec, 199 ]); 200 vals.retain([Promise::Error, Promise::Chown]); 201 assert_eq!(vals.len(), 2); 202 assert!(vals.contains(Promise::Chown)); 203 assert!(vals.contains(Promise::Error)); 204 assert_eq!(initial_promises.pledge(), Ok(())); 205 // This tests unveil with no permissions. 206 assert_eq!(Permissions::NONE.unveil(FILE_EXISTS), Ok(())); 207 assert!(fs::metadata(FILE_EXISTS).map_or_else( 208 |e| matches!(e.kind(), ErrorKind::PermissionDenied), 209 |_| false 210 )); 211 // This tests unveil with read permissions, 212 // and one can unveil more permissions (unlike pledge which can only remove promises). 213 assert_eq!(Permissions::READ.unveil(FILE_EXISTS), Ok(())); 214 assert!(fs::metadata(FILE_EXISTS).is_ok_and(|_| true)); 215 // This tests that calls to unveil on missing files don't error. 216 assert_eq!(Permissions::NONE.unveil(FILE_NOT_EXISTS), Ok(())); 217 // This tests that calls to unveil on missing directories error. 218 assert_eq!(Permissions::NONE.unveil(DIR_NOT_EXISTS), Err(Errno::ENOENT)); 219 // This tests that unveil can no longer be called. 220 assert_eq!(Permissions::unveil_no_more(), Ok(())); 221 assert_eq!(Permissions::NONE.unveil(FILE_EXISTS), Err(Errno::EPERM)); 222 assert!(fs::metadata(FILE_EXISTS).is_ok_and(|_| true)); 223 // The below tests that Promises can only be removed and not added. 224 initial_promises.remove_promises([Promise::Unveil]); 225 assert_eq!(initial_promises.len(), 2); 226 initial_promises.remove(Promise::Rpath); 227 assert_eq!(initial_promises.len(), 1); 228 initial_promises.remove(Promise::Rpath); 229 assert_eq!(initial_promises.len(), 1); 230 assert_eq!(initial_promises.pledge(), Ok(())); 231 assert!(writeln!(io::stdout()).is_ok_and(|()| true)); 232 assert_eq!(Promises::new([Promise::Rpath]).pledge(), Err(Errno::EPERM)); 233 // If the below is uncommented, the program should crash since the above 234 // call to pledge no longer allows access to the file system. 235 // drop(fs::metadata(FILE_EXISTS)); 236 } 237 #[cfg(all(feature = "std", target_os = "openbsd"))] 238 #[test] 239 #[ignore = "interferes with other tests when root"] 240 fn pledge_inet_drop_priv() { 241 if Uid::geteuid().is_root() { 242 assert!( 243 Promises::new_priv_drop( 244 c"nobody", 245 [Promise::Inet, Promise::Rpath, Promise::Unveil], 246 false, 247 || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 47, 0, 0)), 248 ) 249 .is_ok_and(|(_, _)| { 250 Permissions::unveil_raw(README, c"r").is_ok_and(|()| { 251 fs::exists(README).is_ok_and(|exists| { 252 Permissions::NONE.unveil(README).is_ok_and(|()| { 253 Promises::pledge_raw(c"inet stdio").is_ok_and(|()| { 254 exists 255 && TcpListener::bind(SocketAddrV6::new( 256 Ipv6Addr::LOCALHOST, 257 792, 258 0, 259 0, 260 )) 261 .map_or_else( 262 |e| matches!(e.kind(), ErrorKind::PermissionDenied), 263 |_| false, 264 ) 265 }) 266 }) 267 }) 268 }) 269 }) 270 ); 271 } 272 } 273 #[cfg(all(feature = "std", target_os = "openbsd"))] 274 #[test] 275 #[ignore = "interferes with other tests when root"] 276 fn inet_chroot_priv_pledge() { 277 if Uid::geteuid().is_root() { 278 assert!( 279 Promises::new_chroot_then_priv_drop( 280 c"nobody", 281 c"./", 282 [Promise::Inet, Promise::Rpath, Promise::Unveil], 283 false, 284 || TcpListener::bind(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 382, 0, 0)) 285 ) 286 .is_ok_and( 287 |(_, mut promises)| Permissions::READ.unveil(README).is_ok_and(|()| fs::exists( 288 README 289 ) 290 .is_ok_and( 291 |exists| Permissions::NONE.unveil(README).is_ok_and(|()| promises 292 .remove_promises_then_pledge([Promise::Rpath, Promise::Unveil]) 293 .is_ok_and(|()| exists 294 && TcpListener::bind(SocketAddrV6::new( 295 Ipv6Addr::LOCALHOST, 296 588, 297 0, 298 0 299 )) 300 .map_or_else( 301 |e| matches!(e.kind(), ErrorKind::PermissionDenied), 302 |_| false 303 ))) 304 )) 305 ) 306 ); 307 } 308 }