244 lines
8.1 KiB
Rust
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(¤t_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));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|