Refactor code to reduce duplication among gethostbyname(3|4)_r

This commit is contained in:
Matteo Settenvini 2022-08-22 23:12:34 +02:00
parent 516bf4000c
commit 8082509da0
Signed by: matteo
GPG Key ID: 8576CC1AD97D42DF
5 changed files with 223 additions and 111 deletions

View File

@ -8,6 +8,12 @@ edition = "2021"
authors = ["Matteo Settenvini <matteo.settenvini@montecristosoftware.eu"] authors = ["Matteo Settenvini <matteo.settenvini@montecristosoftware.eu"]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
[profile.release]
strip = true
lto = true
codegen-units = 1
panic = "abort"
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
name = "nss_malcontent" name = "nss_malcontent"
@ -31,4 +37,5 @@ log = "0.4"
nix = { version = "0.25", features = ["socket", "user", "sched"] } nix = { version = "0.25", features = ["socket", "user", "sched"] }
tokio = { version = "1", features = ["rt"] } tokio = { version = "1", features = ["rt"] }
trust-dns-resolver = "0.21" trust-dns-resolver = "0.21"
trust-dns-proto = "0.21"
zbus = { version = "3.0", default-features = false, features = ["tokio"] } zbus = { version = "3.0", default-features = false, features = ["tokio"] }

View File

@ -2,38 +2,57 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use { use {
crate::helpers::{ips_to_gaih_addr, set_if_valid}, crate::helpers::{records_to_gaih_addr, records_to_hostent, set_if_valid},
crate::nss_bindings::{gaih_addrtuple, nss_status, HErrno}, crate::nss_bindings::{gaih_addrtuple, nss_status, HErrno},
crate::policy_checker::{PolicyChecker as _, POLICY_CHECKER}, crate::policy_checker::{PolicyChecker as _, POLICY_CHECKER},
libc::{hostent, size_t}, libc::{c_char, c_int, hostent, size_t},
nix::errno,
nix::errno::Errno, nix::errno::Errno,
std::ffi::CStr, std::ffi::CStr,
std::os::raw::{c_char, c_int}, trust_dns_proto::rr::record_type::RecordType,
trust_dns_proto::xfer::dns_request::DnsRequestOptions,
trust_dns_resolver::{lookup::Lookup, lookup_ip::LookupIp},
}; };
// See https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https // See https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https
const CANARY_HOSTNAME: &str = "use-application-dns.net"; const CANARY_HOSTNAME: &str = "use-application-dns.net";
pub async unsafe fn gethostbyname4_r( pub struct Args {
name: *const c_char, pub name: *const c_char,
pat: *mut *mut gaih_addrtuple, pub family: c_int,
buffer: *mut c_char, pub buffer: *mut c_char,
buflen: size_t, pub buflen: size_t,
errnop: *mut Errno, pub errnop: *mut Errno,
h_errnop: *mut HErrno, pub h_errnop: *mut HErrno,
ttlp: *mut i32, pub ttlp: *mut i32,
) -> nss_status { pub result: Result,
}
pub enum Result {
V3(HostEnt),
V4(*mut *mut gaih_addrtuple),
}
pub struct HostEnt {
pub host: *mut hostent,
pub canonp: *mut *mut char,
}
pub async unsafe fn with(args: &mut Args) -> nss_status {
set_if_valid(args.errnop, errno::from_i32(0));
set_if_valid(args.h_errnop, HErrno::Success);
match POLICY_CHECKER.resolver(None) { match POLICY_CHECKER.resolver(None) {
Ok(None) => { Ok(None) => {
// no restrictions for user, the next NSS module will decide // no restrictions for user, the next NSS module will decide
nss_status::NSS_STATUS_NOTFOUND nss_status::NSS_STATUS_NOTFOUND
} }
Ok(Some(resolver)) => { Ok(Some(resolver)) => {
let name = match CStr::from_ptr(name).to_str() { let name = match CStr::from_ptr(args.name).to_str() {
Ok(name) => name, Ok(name) => name,
Err(_) => { Err(_) => {
set_if_valid(errnop, Errno::EINVAL); set_if_valid(args.errnop, Errno::EINVAL);
set_if_valid(h_errnop, HErrno::Internal); set_if_valid(args.h_errnop, HErrno::Internal);
return nss_status::NSS_STATUS_TRYAGAIN; return nss_status::NSS_STATUS_TRYAGAIN;
} }
}; };
@ -41,54 +60,27 @@ pub async unsafe fn gethostbyname4_r(
// disable application-based DNS for those applications // disable application-based DNS for those applications
// (notably, Firefox) that support it // (notably, Firefox) that support it
if name == CANARY_HOSTNAME { if name == CANARY_HOSTNAME {
set_if_valid(h_errnop, HErrno::HostNotFound); set_if_valid(args.h_errnop, HErrno::HostNotFound);
return nss_status::NSS_STATUS_SUCCESS; return nss_status::NSS_STATUS_SUCCESS;
} }
match resolver.lookup_ip(name).await { let lookup: std::result::Result<Lookup, _> = match args.family {
Ok(result) if result.iter().peekable().peek().is_none() => { libc::AF_UNSPEC => resolver.lookup_ip(name).await.map(LookupIp::into),
set_if_valid(h_errnop, HErrno::HostNotFound); libc::AF_INET => {
nss_status::NSS_STATUS_SUCCESS resolver
.lookup(name, RecordType::A, DnsRequestOptions::default())
.await
} }
Ok(result) => { libc::AF_INET6 => {
if pat == std::ptr::null_mut() { resolver
set_if_valid(errnop, Errno::EINVAL); .lookup(name, RecordType::AAAA, DnsRequestOptions::default())
set_if_valid(h_errnop, HErrno::Internal); .await
return nss_status::NSS_STATUS_TRYAGAIN;
} }
_ => return nss_status::NSS_STATUS_NOTFOUND,
};
let ttl = result match lookup {
.valid_until() Ok(result) => prepare_response(args, result),
.duration_since(std::time::Instant::now())
.as_secs();
set_if_valid(
ttlp,
if ttl < (i32::MAX as u64) {
ttl as i32
} else {
i32::MAX
},
);
let buf = std::slice::from_raw_parts_mut(buffer as *mut u8, buflen);
match ips_to_gaih_addr(result, buf) {
Ok(addrs) => {
// DEBUG: eprintln!("{:?} => {:?}", addrs, *addrs);
*pat = addrs;
nss_status::NSS_STATUS_SUCCESS
}
Err(err) => {
set_if_valid(
errnop,
err.raw_os_error()
.map(Errno::from_i32)
.unwrap_or(Errno::EAGAIN),
);
set_if_valid(h_errnop, HErrno::Internal);
nss_status::NSS_STATUS_TRYAGAIN
}
}
}
Err(err) => { Err(err) => {
log::warn!("{}", err); log::warn!("{}", err);
nss_status::NSS_STATUS_UNAVAIL nss_status::NSS_STATUS_UNAVAIL
@ -102,16 +94,77 @@ pub async unsafe fn gethostbyname4_r(
} }
} }
pub async unsafe fn gethostbyname3_r( unsafe fn prepare_response(
_name: *const c_char, args: &mut Args,
_af: c_int, lookup: trust_dns_resolver::lookup::Lookup,
_host: *mut hostent,
_buffer: *mut c_char,
_buflen: size_t,
_errnop: *mut Errno,
_h_errnop: *mut HErrno,
_ttlp: *mut i32,
_canonp: *mut *mut char,
) -> nss_status { ) -> nss_status {
todo!() if lookup.iter().peekable().peek().is_none() {
set_if_valid(args.h_errnop, HErrno::HostNotFound);
return nss_status::NSS_STATUS_SUCCESS;
}
let ttl = lookup
.valid_until()
.duration_since(std::time::Instant::now())
.as_secs();
set_if_valid(
args.ttlp,
if ttl < (i32::MAX as u64) {
ttl as i32
} else {
i32::MAX
},
);
let buf = std::slice::from_raw_parts_mut(args.buffer as *mut u8, args.buflen);
let ret = match &mut args.result {
Result::V3(hostent) => {
if hostent.host.is_null() {
set_if_valid(args.errnop, Errno::EINVAL);
set_if_valid(args.h_errnop, HErrno::Internal);
return nss_status::NSS_STATUS_TRYAGAIN;
}
match records_to_hostent(lookup.into(), hostent, buf) {
Ok(_) => nss_status::NSS_STATUS_SUCCESS,
Err(err) => {
set_if_valid(
args.errnop,
err.raw_os_error()
.map(Errno::from_i32)
.unwrap_or(Errno::EAGAIN),
);
set_if_valid(args.h_errnop, HErrno::Internal);
nss_status::NSS_STATUS_TRYAGAIN
}
}
}
Result::V4(pat) => {
if pat.is_null() {
set_if_valid(args.errnop, Errno::EINVAL);
set_if_valid(args.h_errnop, HErrno::Internal);
return nss_status::NSS_STATUS_TRYAGAIN;
}
match records_to_gaih_addr(lookup.into(), buf) {
Ok(addrs) => {
// DEBUG: eprintln!("{:?} => {:?}", addrs, *addrs);
**pat = addrs;
nss_status::NSS_STATUS_SUCCESS
}
Err(err) => {
set_if_valid(
args.errnop,
err.raw_os_error()
.map(Errno::from_i32)
.unwrap_or(Errno::EAGAIN),
);
set_if_valid(args.h_errnop, HErrno::Internal);
nss_status::NSS_STATUS_TRYAGAIN
}
}
}
};
ret
} }

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use { use {
crate::gethostbyname::HostEnt,
crate::nss_bindings::gaih_addrtuple, crate::nss_bindings::gaih_addrtuple,
anyhow::{bail, Result}, anyhow::{bail, Result},
libc::{AF_INET, AF_INET6}, libc::{AF_INET, AF_INET6},
@ -9,31 +10,35 @@ use {
once_cell::sync::Lazy, once_cell::sync::Lazy,
std::ffi::CString, std::ffi::CString,
std::mem::{align_of, size_of}, std::mem::{align_of, size_of},
std::net::IpAddr, trust_dns_proto::rr::RData,
trust_dns_resolver::lookup_ip::LookupIp, trust_dns_resolver::lookup::Lookup,
}; };
static RUNTIME: Lazy<std::io::Result<tokio::runtime::Handle>> = Lazy::new(|| { static RUNTIME: Lazy<std::io::Result<tokio::runtime::Runtime>> = Lazy::new(|| {
// The runtime should remain single-threaded, some // The runtime should remain single-threaded, some
// programs depend on it (e.g. programs calling unshare()) // programs depend on it (e.g. programs calling unshare())
let rt = tokio::runtime::Builder::new_current_thread().build()?; let rt = tokio::runtime::Builder::new_current_thread()
Ok(rt.handle().clone()) .enable_time()
.enable_io()
.build()?;
Ok(rt)
}); });
pub unsafe fn ips_to_gaih_addr( // TODO: error handling codes chosen a bit sloppily
ips: LookupIp, pub unsafe fn records_to_gaih_addr(
lookup: Lookup,
mut buf: &mut [u8], mut buf: &mut [u8],
) -> std::io::Result<*mut gaih_addrtuple> { ) -> std::io::Result<*mut gaih_addrtuple> {
const GAIH_ADDRTUPLE_SZ: usize = size_of::<gaih_addrtuple>(); const GAIH_ADDRTUPLE_SZ: usize = size_of::<gaih_addrtuple>();
let mut ret = std::ptr::null_mut(); let mut ret = std::ptr::null_mut();
let query = ips.query();
let name = CString::new(query.name().to_utf8()).unwrap(); // TODO: .map_err() and fail more graciously
let mut prev_link: *mut *mut gaih_addrtuple = std::ptr::null_mut(); let mut prev_link: *mut *mut gaih_addrtuple = std::ptr::null_mut();
for addr in ips { for record in lookup.record_iter() {
// First add the name to the buffer // First add the name to the buffer
let name = CString::new(record.name().to_utf8())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
let offset = buf.as_ptr().align_offset(align_of::<libc::c_char>()); let offset = buf.as_ptr().align_offset(align_of::<libc::c_char>());
let name_src = name.as_bytes_with_nul(); let name_src = name.as_bytes_with_nul();
let name_dest = buf.as_mut_ptr().add(offset); let name_dest = buf.as_mut_ptr().add(offset);
@ -60,15 +65,17 @@ pub unsafe fn ips_to_gaih_addr(
set_if_valid(prev_link, &mut *tuple); // link from previous tuple to this tuple set_if_valid(prev_link, &mut *tuple); // link from previous tuple to this tuple
prev_link = &mut (*tuple).next; prev_link = &mut (*tuple).next;
match addr { match record.data() {
IpAddr::V4(addr) => { Some(RData::A(addr)) => {
tuple.family = AF_INET; tuple.family = AF_INET;
tuple.addr[0] = std::mem::transmute_copy(&addr.octets()); tuple.addr[0] = std::mem::transmute_copy(&addr.octets());
} }
IpAddr::V6(addr) => { Some(RData::AAAA(addr)) => {
tuple.family = AF_INET6; tuple.family = AF_INET6;
tuple.addr = std::mem::transmute_copy(&addr.octets()); tuple.addr = std::mem::transmute_copy(&addr.octets());
} }
Some(_) => return Err(Errno::EBADMSG.into()),
None => return Err(Errno::ENODATA.into()),
} }
if ret == std::ptr::null_mut() { if ret == std::ptr::null_mut() {
@ -81,6 +88,37 @@ pub unsafe fn ips_to_gaih_addr(
Ok(ret) Ok(ret)
} }
pub unsafe fn records_to_hostent(
lookup: Lookup,
_hostent: &mut HostEnt,
mut _buf: &mut [u8],
) -> std::io::Result<()> {
// char *h_name Official name of the host.
// char **h_aliases A pointer to an array of pointers to
// alternative host names, terminated by a
// null pointer.
// int h_addrtype Address type.
// int h_length The length, in bytes, of the address.
// char **h_addr_list A pointer to an array of pointers to network
// addresses (in network byte order) for the host,
// terminated by a null pointer.
// In C struct hostent:
//
// - for the type of queries we perform, we can assume h_aliases
// is an empty array.
// - hostent is limited to just one address type for all addresses
// in the list. We pick the type of first result, and only
// append addresses of the same type.
for record in lookup.record_iter() {
todo!();
}
Ok(())
}
pub fn set_if_valid<T>(ptr: *mut T, val: T) { pub fn set_if_valid<T>(ptr: *mut T, val: T) {
if !ptr.is_null() { if !ptr.is_null() {
unsafe { *ptr = val }; unsafe { *ptr = val };
@ -93,7 +131,7 @@ where
{ {
use std::ops::Deref; use std::ops::Deref;
match RUNTIME.deref() { match RUNTIME.deref() {
Ok(rt_handle) => Ok(rt_handle.block_on(async { f.await })), Ok(rt) => Ok(rt.block_on(async { f.await })),
Err(e) => bail!("Unable to start tokio runtime: {}", e), Err(e) => bail!("Unable to start tokio runtime: {}", e),
} }
} }

View File

@ -3,11 +3,10 @@
use { use {
crate::gethostbyaddr::gethostbyaddr2_r, crate::gethostbyaddr::gethostbyaddr2_r,
crate::gethostbyname::{gethostbyname3_r, gethostbyname4_r}, crate::gethostbyname,
crate::helpers::{block_on, set_if_valid}, crate::helpers::{block_on, set_if_valid},
crate::nss_bindings::{gaih_addrtuple, nss_status, HErrno}, crate::nss_bindings::{gaih_addrtuple, nss_status, HErrno},
libc::{hostent, size_t, socklen_t, AF_INET}, libc::{hostent, size_t, socklen_t, AF_INET},
nix::errno,
nix::errno::Errno, nix::errno::Errno,
std::os::raw::{c_char, c_int, c_void}, std::os::raw::{c_char, c_int, c_void},
std::ptr, std::ptr,
@ -25,16 +24,22 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyname4_r(
h_errnop: *mut HErrno, h_errnop: *mut HErrno,
ttlp: *mut i32, ttlp: *mut i32,
) -> nss_status { ) -> nss_status {
set_if_valid(errnop, errno::from_i32(0)); let mut args = gethostbyname::Args {
set_if_valid(h_errnop, HErrno::Success); name,
family: 0,
result: gethostbyname::Result::V4(pat),
buffer,
buflen,
errnop,
h_errnop,
ttlp,
};
match block_on(async { match block_on(async { gethostbyname::with(&mut args).await }) {
gethostbyname4_r(name, pat, buffer, buflen, errnop, h_errnop, ttlp).await
}) {
Ok(status) => status, Ok(status) => status,
Err(runtime_error) => { Err(runtime_error) => {
log::error!("gethostbyname4_r: {}", runtime_error); log::error!("gethostbyname4_r: {}", runtime_error);
set_if_valid(h_errnop, HErrno::Internal); set_if_valid(args.h_errnop, HErrno::Internal);
nss_status::NSS_STATUS_TRYAGAIN nss_status::NSS_STATUS_TRYAGAIN
} }
} }
@ -52,15 +57,20 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyname3_r(
ttlp: *mut i32, ttlp: *mut i32,
canonp: *mut *mut char, canonp: *mut *mut char,
) -> nss_status { ) -> nss_status {
set_if_valid(errnop, errno::from_i32(0)); let result = gethostbyname::HostEnt { host, canonp };
set_if_valid(h_errnop, HErrno::Success);
match block_on(async { let mut args = gethostbyname::Args {
gethostbyname3_r( name,
name, af, host, buffer, buflen, errnop, h_errnop, ttlp, canonp, family: af,
) result: gethostbyname::Result::V3(result),
.await buffer,
}) { buflen,
errnop,
h_errnop,
ttlp,
};
match block_on(async { gethostbyname::with(&mut args).await }) {
Ok(status) => status, Ok(status) => status,
Err(runtime_error) => { Err(runtime_error) => {
log::error!("gethostbyname3_r: {}", runtime_error); log::error!("gethostbyname3_r: {}", runtime_error);
@ -129,7 +139,7 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyaddr2_r(
h_errnop: *mut HErrno, h_errnop: *mut HErrno,
ttlp: *mut i32, ttlp: *mut i32,
) -> nss_status { ) -> nss_status {
set_if_valid(errnop, errno::from_i32(0)); set_if_valid(errnop, nix::errno::from_i32(0));
set_if_valid(h_errnop, HErrno::Success); set_if_valid(h_errnop, HErrno::Success);
match block_on(async { match block_on(async {

View File

@ -11,8 +11,8 @@ use {
once_cell::sync::Lazy, once_cell::sync::Lazy,
std::collections::HashMap, std::collections::HashMap,
std::net::{IpAddr, Ipv4Addr, Ipv6Addr}, std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
std::time::Duration, //std::time::Duration,
tokio::time::timeout, //tokio::time::timeout,
}; };
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| { static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| {
@ -36,7 +36,7 @@ fork_test! {
fn application_dns_is_nxdomain() -> Result<()> { fn application_dns_is_nxdomain() -> Result<()> {
common::setup()?; common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async { tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([( let _dbus = common::mock_dbus(HashMap::from([(
getuid(), getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()], vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])); )]));
@ -61,7 +61,8 @@ fork_test! {
freeaddrinfo(addr); freeaddrinfo(addr);
}; };
timeout(Duration::from_secs(1), dbus).await?? //timeout(Duration::from_secs(1), dbus).await??
Ok(())
}) })
} }
@ -70,7 +71,7 @@ fork_test! {
common::setup()?; common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async { tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([( let _dbus = common::mock_dbus(HashMap::from([(
getuid(), getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()], vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])); )]));
@ -83,7 +84,8 @@ fork_test! {
assert_eq!(system_addr, our_addr); assert_eq!(system_addr, our_addr);
} }
timeout(Duration::from_secs(1), dbus).await?? //timeout(Duration::from_secs(1), dbus).await??
Ok(())
}) })
} }
@ -92,7 +94,7 @@ fork_test! {
common::setup()?; common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async { tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([( let _dbus = common::mock_dbus(HashMap::from([(
getuid(), getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()], vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)])); )]));
@ -108,7 +110,8 @@ fork_test! {
assert_ne!(system_addr, our_addr); assert_ne!(system_addr, our_addr);
assert_eq!(our_addr, IpAddr::V6(Ipv6Addr::UNSPECIFIED)); assert_eq!(our_addr, IpAddr::V6(Ipv6Addr::UNSPECIFIED));
timeout(Duration::from_secs(1), dbus).await?? //timeout(Duration::from_secs(1), dbus).await??
Ok(())
}) })
} }
@ -117,7 +120,7 @@ fork_test! {
common::setup()?; common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async { tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])])); let _dbus = common::mock_dbus(HashMap::from([(getuid(), vec![ /* no restriction */])]));
const HOSTNAME: &str = "pornhub.com"; const HOSTNAME: &str = "pornhub.com";
@ -127,7 +130,8 @@ fork_test! {
assert_eq!(system_addr, our_addr); assert_eq!(system_addr, our_addr);
} }
timeout(Duration::from_secs(1), dbus).await?? //timeout(Duration::from_secs(1), dbus).await??
Ok(())
}) })
} }