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"]
license = "GPL-3.0-or-later"
[profile.release]
strip = true
lto = true
codegen-units = 1
panic = "abort"
[lib]
crate-type = ["cdylib"]
name = "nss_malcontent"
@ -31,4 +37,5 @@ log = "0.4"
nix = { version = "0.25", features = ["socket", "user", "sched"] }
tokio = { version = "1", features = ["rt"] }
trust-dns-resolver = "0.21"
trust-dns-proto = "0.21"
zbus = { version = "3.0", default-features = false, features = ["tokio"] }

View File

@ -2,38 +2,57 @@
// SPDX-License-Identifier: GPL-3.0-or-later
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::policy_checker::{PolicyChecker as _, POLICY_CHECKER},
libc::{hostent, size_t},
libc::{c_char, c_int, hostent, size_t},
nix::errno,
nix::errno::Errno,
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
const CANARY_HOSTNAME: &str = "use-application-dns.net";
pub async unsafe fn gethostbyname4_r(
name: *const c_char,
pat: *mut *mut gaih_addrtuple,
buffer: *mut c_char,
buflen: size_t,
errnop: *mut Errno,
h_errnop: *mut HErrno,
ttlp: *mut i32,
) -> nss_status {
pub struct Args {
pub name: *const c_char,
pub family: c_int,
pub buffer: *mut c_char,
pub buflen: size_t,
pub errnop: *mut Errno,
pub h_errnop: *mut HErrno,
pub ttlp: *mut i32,
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) {
Ok(None) => {
// no restrictions for user, the next NSS module will decide
nss_status::NSS_STATUS_NOTFOUND
}
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,
Err(_) => {
set_if_valid(errnop, Errno::EINVAL);
set_if_valid(h_errnop, HErrno::Internal);
set_if_valid(args.errnop, Errno::EINVAL);
set_if_valid(args.h_errnop, HErrno::Internal);
return nss_status::NSS_STATUS_TRYAGAIN;
}
};
@ -41,54 +60,27 @@ pub async unsafe fn gethostbyname4_r(
// disable application-based DNS for those applications
// (notably, Firefox) that support it
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;
}
match resolver.lookup_ip(name).await {
Ok(result) if result.iter().peekable().peek().is_none() => {
set_if_valid(h_errnop, HErrno::HostNotFound);
nss_status::NSS_STATUS_SUCCESS
let lookup: std::result::Result<Lookup, _> = match args.family {
libc::AF_UNSPEC => resolver.lookup_ip(name).await.map(LookupIp::into),
libc::AF_INET => {
resolver
.lookup(name, RecordType::A, DnsRequestOptions::default())
.await
}
Ok(result) => {
if pat == std::ptr::null_mut() {
set_if_valid(errnop, Errno::EINVAL);
set_if_valid(h_errnop, HErrno::Internal);
return nss_status::NSS_STATUS_TRYAGAIN;
}
let ttl = result
.valid_until()
.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
}
}
libc::AF_INET6 => {
resolver
.lookup(name, RecordType::AAAA, DnsRequestOptions::default())
.await
}
_ => return nss_status::NSS_STATUS_NOTFOUND,
};
match lookup {
Ok(result) => prepare_response(args, result),
Err(err) => {
log::warn!("{}", err);
nss_status::NSS_STATUS_UNAVAIL
@ -102,16 +94,77 @@ pub async unsafe fn gethostbyname4_r(
}
}
pub async unsafe fn gethostbyname3_r(
_name: *const c_char,
_af: c_int,
_host: *mut hostent,
_buffer: *mut c_char,
_buflen: size_t,
_errnop: *mut Errno,
_h_errnop: *mut HErrno,
_ttlp: *mut i32,
_canonp: *mut *mut char,
unsafe fn prepare_response(
args: &mut Args,
lookup: trust_dns_resolver::lookup::Lookup,
) -> 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
use {
crate::gethostbyname::HostEnt,
crate::nss_bindings::gaih_addrtuple,
anyhow::{bail, Result},
libc::{AF_INET, AF_INET6},
@ -9,31 +10,35 @@ use {
once_cell::sync::Lazy,
std::ffi::CString,
std::mem::{align_of, size_of},
std::net::IpAddr,
trust_dns_resolver::lookup_ip::LookupIp,
trust_dns_proto::rr::RData,
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
// programs depend on it (e.g. programs calling unshare())
let rt = tokio::runtime::Builder::new_current_thread().build()?;
Ok(rt.handle().clone())
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.enable_io()
.build()?;
Ok(rt)
});
pub unsafe fn ips_to_gaih_addr(
ips: LookupIp,
// TODO: error handling codes chosen a bit sloppily
pub unsafe fn records_to_gaih_addr(
lookup: Lookup,
mut buf: &mut [u8],
) -> std::io::Result<*mut gaih_addrtuple> {
const GAIH_ADDRTUPLE_SZ: usize = size_of::<gaih_addrtuple>();
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();
for addr in ips {
for record in lookup.record_iter() {
// 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 name_src = name.as_bytes_with_nul();
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
prev_link = &mut (*tuple).next;
match addr {
IpAddr::V4(addr) => {
match record.data() {
Some(RData::A(addr)) => {
tuple.family = AF_INET;
tuple.addr[0] = std::mem::transmute_copy(&addr.octets());
}
IpAddr::V6(addr) => {
Some(RData::AAAA(addr)) => {
tuple.family = AF_INET6;
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() {
@ -81,6 +88,37 @@ pub unsafe fn ips_to_gaih_addr(
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) {
if !ptr.is_null() {
unsafe { *ptr = val };
@ -93,7 +131,7 @@ where
{
use std::ops::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),
}
}

View File

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

View File

@ -11,8 +11,8 @@ use {
once_cell::sync::Lazy,
std::collections::HashMap,
std::net::{IpAddr, Ipv4Addr, Ipv6Addr},
std::time::Duration,
tokio::time::timeout,
//std::time::Duration,
//tokio::time::timeout,
};
static CLOUDFLARE_PARENTALCONTROL_ADDRS: Lazy<Vec<IpAddr>> = Lazy::new(|| {
@ -36,7 +36,7 @@ fork_test! {
fn application_dns_is_nxdomain() -> Result<()> {
common::setup()?;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([(
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)]));
@ -61,7 +61,8 @@ fork_test! {
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()?;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([(
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)]));
@ -83,7 +84,8 @@ fork_test! {
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()?;
tokio::runtime::Runtime::new().unwrap().block_on(async {
let dbus = common::mock_dbus(HashMap::from([(
let _dbus = common::mock_dbus(HashMap::from([(
getuid(),
vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()],
)]));
@ -108,7 +110,8 @@ fork_test! {
assert_ne!(system_addr, our_addr);
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()?;
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";
@ -127,7 +130,8 @@ fork_test! {
assert_eq!(system_addr, our_addr);
}
timeout(Duration::from_secs(1), dbus).await??
//timeout(Duration::from_secs(1), dbus).await??
Ok(())
})
}