use super::report::LandlockNetProbeStatus;
fn abi_supports_connect_tcp(abi_version: Option<u32>) -> bool {
abi_version.is_some_and(|v| v >= 4)
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn classify_child(exit_code: Option<i32>, signaled: bool) -> (LandlockNetProbeStatus, Option<i32>) {
if signaled {
return (LandlockNetProbeStatus::Failed, None);
}
match exit_code {
Some(0) => (LandlockNetProbeStatus::Usable, None),
Some(code) => (LandlockNetProbeStatus::Failed, Some(code)),
None => (LandlockNetProbeStatus::Failed, None),
}
}
pub(super) fn probe_net_connect_ruleset(
abi_version: Option<u32>,
) -> (LandlockNetProbeStatus, Option<i32>) {
if !abi_supports_connect_tcp(abi_version) {
return (LandlockNetProbeStatus::Unsupported, None);
}
#[cfg(target_os = "linux")]
{
run_smoke()
}
#[cfg(not(target_os = "linux"))]
{
(LandlockNetProbeStatus::Unsupported, None)
}
}
#[cfg(target_os = "linux")]
fn errno_from_error(err: &(dyn std::error::Error + 'static)) -> Option<i32> {
let mut cur: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(e) = cur {
if let Some(io) = e.downcast_ref::<std::io::Error>() {
return io.raw_os_error();
}
cur = e.source();
}
None
}
#[cfg(target_os = "linux")]
const SMOKE_TEST_PORT: u16 = 443;
#[cfg(target_os = "linux")]
fn run_smoke() -> (LandlockNetProbeStatus, Option<i32>) {
use landlock::{
AccessNet, CompatLevel, Compatible, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr,
};
use std::os::fd::{AsRawFd, OwnedFd};
let built = Ruleset::default()
.set_compatibility(CompatLevel::HardRequirement)
.handle_access(AccessNet::ConnectTcp)
.and_then(|r| r.create())
.and_then(|r| r.add_rule(NetPort::new(SMOKE_TEST_PORT, AccessNet::ConnectTcp)));
let created = match built {
Ok(c) => c,
Err(e) => return (LandlockNetProbeStatus::Failed, errno_from_error(&e)),
};
let owned_fd: Option<OwnedFd> = created.into();
let owned_fd = match owned_fd {
Some(fd) => fd,
None => return (LandlockNetProbeStatus::Failed, Some(libc::EBADF)),
};
let ruleset_fd = owned_fd.as_raw_fd();
const PR_SET_NO_NEW_PRIVS: libc::c_int = 38;
let pid = unsafe { libc::fork() };
if pid == 0 {
if unsafe { libc::prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) } != 0 {
unsafe { libc::_exit(clamp_errno()) };
}
let ret = unsafe { libc::syscall(libc::SYS_landlock_restrict_self, ruleset_fd, 0) };
if ret != 0 {
unsafe { libc::_exit(clamp_errno()) };
}
unsafe { libc::_exit(0) };
} else if pid < 0 {
return (
LandlockNetProbeStatus::Failed,
std::io::Error::last_os_error().raw_os_error(),
);
}
let mut status: libc::c_int = 0;
let wait = unsafe { libc::waitpid(pid, &mut status, 0) };
drop(owned_fd); if wait < 0 {
return (LandlockNetProbeStatus::Failed, None);
}
if libc::WIFEXITED(status) {
classify_child(Some(libc::WEXITSTATUS(status)), false)
} else {
classify_child(None, true)
}
}
#[cfg(target_os = "linux")]
fn clamp_errno() -> libc::c_int {
let e = unsafe { *libc::__errno_location() };
if e <= 0 || e > 255 {
255
} else {
e
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn abi_gate_requires_v4() {
assert!(!abi_supports_connect_tcp(None));
assert!(!abi_supports_connect_tcp(Some(3)));
assert!(abi_supports_connect_tcp(Some(4)));
assert!(abi_supports_connect_tcp(Some(5)));
}
#[test]
fn classify_child_success_is_usable() {
assert_eq!(
classify_child(Some(0), false),
(LandlockNetProbeStatus::Usable, None)
);
}
#[test]
fn classify_child_nonzero_exit_is_failed_with_errno() {
assert_eq!(
classify_child(Some(libc::EACCES), false),
(LandlockNetProbeStatus::Failed, Some(libc::EACCES))
);
assert_eq!(
classify_child(Some(libc::EPERM), false),
(LandlockNetProbeStatus::Failed, Some(libc::EPERM))
);
}
#[test]
fn classify_child_signaled_is_failed_no_errno() {
assert_eq!(
classify_child(None, true),
(LandlockNetProbeStatus::Failed, None)
);
}
#[test]
fn classify_child_no_exit_code_is_failed() {
assert_eq!(
classify_child(None, false),
(LandlockNetProbeStatus::Failed, None)
);
}
#[test]
fn probe_is_unsupported_below_abi_4() {
assert_eq!(
probe_net_connect_ruleset(None),
(LandlockNetProbeStatus::Unsupported, None)
);
assert_eq!(
probe_net_connect_ruleset(Some(3)),
(LandlockNetProbeStatus::Unsupported, None)
);
}
#[test]
fn probe_returns_without_panic_at_abi_4() {
let (_status, _errno) = probe_net_connect_ruleset(Some(4));
}
}