malcontent/nss/tests/integration_test.rs

244 lines
8.1 KiB
Rust

// SPDX-FileCopyrightText: Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
// SPDX-License-Identifier: GPL-3.0-or-later
mod common;
use {
crate::common::{Eai, Restriction, Restrictions},
anyhow::Result,
elf::ElfStream,
libc::{freeaddrinfo, gai_strerror, getaddrinfo},
once_cell::sync::Lazy,
rusty_fork::rusty_fork_test,
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
std::os::unix::fs::FileExt,
std::path::PathBuf,
};
static DNS0_PARENTALCONTROL_ADDRS: Lazy<Restrictions> = Lazy::new(|| {
vec![
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(193, 110, 81, 1)),
hostname: "kids.dns0.eu".into(),
},
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(185, 253, 5, 1)),
hostname: "kids.dns0.eu".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2a0f, 0xfc80, 0, 0, 0, 0, 0, 0x1)),
hostname: "kids.dns0.eu".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2a0f, 0xfc81, 0, 0, 0, 0, 0, 0x1)),
hostname: "kids.dns0.eu".into(),
},
]
});
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Restrictions> = Lazy::new(|| {
vec![
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(1, 1, 1, 3)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V4(Ipv4Addr::new(1, 0, 0, 3)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1113)),
hostname: "family.cloudflare-dns.com".into(),
},
Restriction {
ip: IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1003)),
hostname: "family.cloudflare-dns.com".into(),
},
]
});
static NO_RESTRICTIONS: Lazy<Restrictions> = Lazy::new(|| vec![]);
#[test]
fn sanity_check() -> Result<()> {
let current_exe = PathBuf::from(std::env::current_exe().unwrap());
let myself = std::fs::File::open(&current_exe)?;
let mut elf_stream: ElfStream<elf::endian::AnyEndian, _> =
elf::ElfStream::open_stream(&myself)?;
let dynamic_section = elf_stream.dynamic()?.expect("No dynamic table");
let rpath_off = dynamic_section
.iter()
.find(|x| x.d_tag == elf::abi::DT_RPATH)
.map(|x| x.d_val())
.expect("No DT_RPATH found in test executable ELF dyn section");
let strtable = dynamic_section
.into_iter()
.find(|x| x.d_tag == elf::abi::DT_STRTAB)
.map(|x| x.d_ptr())
.expect("No DT_STRTAB found in test executable ELF dyn section");
let mut buf = [0u8; 1024];
let _ = myself.read_at(buf.as_mut_slice(), strtable + rpath_off)?;
let end = buf
.iter()
.position(|c| *c == 0u8)
.expect(&format!("RPATH is bigger than {} bytes", buf.len()));
let rpath = std::str::from_utf8(&buf[..end])?;
assert_eq!(rpath, "$ORIGIN");
common::setup()?;
let lib = current_exe.parent().unwrap().join("libnss_malcontent.so.2");
assert!(lib.exists());
Ok(())
}
rusty_fork_test! {
#[test]
fn application_dns_is_nodata() {
common::setup().unwrap();
let _dbus = common::DBusMockServerGuard::new(vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()]).unwrap();
let hostname = std::ffi::CString::new("use-application-dns.net").unwrap();
unsafe {
let mut addr = std::ptr::null_mut();
let getaddrinfo_status = getaddrinfo(
hostname.as_ptr(),
std::ptr::null(),
std::ptr::null(),
&mut addr,
);
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
assert_eq!(
getaddrinfo_status,
Eai::NoData.0,
"Should have gotten no data (NODATA), instead got {}",
error.to_str().unwrap()
);
freeaddrinfo(addr);
};
}
#[test]
fn getaddrinfo_resolution() {
common::setup().unwrap();
const HOSTNAME: &str = "gnome.org";
let _dbus = common::DBusMockServerGuard::new(vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()]).unwrap();
unsafe {
let mut addr = std::ptr::null_mut();
let hostname = std::ffi::CString::new(HOSTNAME).unwrap();
let getaddrinfo_status = getaddrinfo(
hostname.as_ptr(),
std::ptr::null(),
std::ptr::null(),
&mut addr,
);
let error = std::ffi::CStr::from_ptr(gai_strerror(getaddrinfo_status));
assert_eq!(
getaddrinfo_status,
Eai::Success.0,
"Should have gotten an hostname, instead got {}",
error.to_str().unwrap()
);
freeaddrinfo(addr);
};
}
#[test]
fn wikipedia_is_unrestricted() {
common::setup().unwrap();
const HOSTNAME: &str = "wikipedia.org";
let _dbus = common::DBusMockServerGuard::new(vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()]).unwrap();
for family in [libc::AF_INET, libc::AF_INET6] {
let system_addr = common::resolve_with_system(family, HOSTNAME).unwrap();
let our_addrs = common::resolve_with_module(family, HOSTNAME).unwrap();
assert!(our_addrs.contains(&system_addr));
}
}
#[test]
fn dns0_adultsites_are_restricted() {
common::setup().unwrap();
const HOSTNAME: &str = "pornhub.com";
let _dbus = common::DBusMockServerGuard::new(vec![DNS0_PARENTALCONTROL_ADDRS.clone()]).unwrap();
let our_addrs_ipv4 = common::resolve_with_module(libc::AF_INET, HOSTNAME).unwrap();
let our_addrs_ipv6 = common::resolve_with_module(libc::AF_INET, HOSTNAME).unwrap();
assert!(
our_addrs_ipv4.is_empty(),
"Resolver answered with {:?}, should be empty",
our_addrs_ipv4
);
assert!(
our_addrs_ipv6.is_empty(),
"Resolver answered with {:?}, should be empty",
our_addrs_ipv6
);
}
#[test]
fn cloudflare_adultsite_is_restricted_ipv4() {
common::setup().unwrap();
const HOSTNAME: &str = "nudity.testcategory.com";
let _dbus = common::DBusMockServerGuard::new(vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()]).unwrap();
let system_addr = common::resolve_with_system(libc::AF_INET, HOSTNAME).unwrap();
let our_addrs = common::resolve_with_module(libc::AF_INET, HOSTNAME).unwrap();
assert!(
!our_addrs.contains(&system_addr),
"Resolver answered with {:?}, should not contain {}",
our_addrs,
system_addr
);
assert_eq!(our_addrs, [IpAddr::V4(Ipv4Addr::UNSPECIFIED)]);
}
#[test]
fn cloudflare_adultsite_is_restricted_ipv6() {
common::setup().unwrap();
const HOSTNAME: &str = "nudity.testcategory.com";
let _dbus = common::DBusMockServerGuard::new(vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()]).unwrap();
let system_addr = common::resolve_with_system(libc::AF_INET6, HOSTNAME).unwrap();
let our_addrs = common::resolve_with_module(libc::AF_INET6, HOSTNAME).unwrap();
assert!(
!our_addrs.contains(&system_addr),
"Resolver answered with {:?}, should not contain {}",
our_addrs,
system_addr
);
assert_eq!(our_addrs, [IpAddr::V6(Ipv6Addr::UNSPECIFIED)]);
}
#[test]
fn privileged_user_bypasses_restrictions() {
common::setup().unwrap();
const HOSTNAME: &str = "malware.testcategory.com";
let _dbus = common::DBusMockServerGuard::new(vec![NO_RESTRICTIONS.clone()]).unwrap();
for family in [libc::AF_INET, libc::AF_INET6] {
let system_addr = common::resolve_with_system(family, HOSTNAME).unwrap();
let our_addrs = common::resolve_with_module(family, HOSTNAME).unwrap();
assert!(our_addrs.contains(&system_addr));
}
}
}