From d37ab45d68fb92ea28006e9358e0afec7d8b870d Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Tue, 23 Aug 2022 11:07:17 +0200 Subject: [PATCH] Implement hostent handling --- Cargo.toml | 2 +- src/gethostbyname.rs | 19 +++-- src/helpers.rs | 162 ++++++++++++++++++++++++++++++-------- src/nss_api.rs | 11 ++- tests/integration_test.rs | 35 ++++++++ 5 files changed, 179 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8f115f..115bcff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later" strip = true lto = true codegen-units = 1 -panic = "abort" +panic = "unwind" # We rely on this [lib] crate-type = ["cdylib"] diff --git a/src/gethostbyname.rs b/src/gethostbyname.rs index b136abb..e6fc8af 100644 --- a/src/gethostbyname.rs +++ b/src/gethostbyname.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use { - crate::helpers::{records_to_gaih_addr, records_to_hostent, set_if_valid}, + crate::helpers::{records_to_gaih_addr, records_to_hostent, set_if_valid, DnsResult}, crate::nss_bindings::{gaih_addrtuple, nss_status, HErrno}, crate::policy_checker::{PolicyChecker as _, POLICY_CHECKER}, libc::{c_char, c_int, hostent, size_t}, @@ -29,15 +29,10 @@ pub struct Args { } pub enum Result { - V3(HostEnt), + V3(*mut 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); @@ -119,14 +114,18 @@ unsafe fn prepare_response( 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() { + if hostent.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, + match records_to_hostent(lookup.into(), &mut **hostent, buf) { + Ok(DnsResult::Found) => nss_status::NSS_STATUS_SUCCESS, + Ok(DnsResult::NxDomain) => { + set_if_valid(args.h_errnop, HErrno::HostNotFound); + nss_status::NSS_STATUS_SUCCESS + } Err(err) => { set_if_valid( args.errnop, diff --git a/src/helpers.rs b/src/helpers.rs index 23b9289..b679812 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -2,14 +2,14 @@ // 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}, + libc::{c_char, hostent, AF_INET, AF_INET6}, nix::errno::Errno, once_cell::sync::Lazy, std::ffi::CString, - std::mem::{align_of, size_of}, + std::mem::{align_of, discriminant, size_of}, + trust_dns_proto::rr::resource::Record, trust_dns_proto::rr::RData, trust_dns_resolver::lookup::Lookup, }; @@ -34,22 +34,10 @@ pub unsafe fn records_to_gaih_addr( let mut ret = std::ptr::null_mut(); let mut prev_link: *mut *mut gaih_addrtuple = std::ptr::null_mut(); + let mut name_dest; 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::()); - let name_src = name.as_bytes_with_nul(); - let name_dest = buf.as_mut_ptr().add(offset); - - let l = name_src.len(); - if buf.len() < offset + l { - return Err(Errno::ERANGE.into()); - } - - std::ptr::copy_nonoverlapping(name_src.as_ptr(), name_dest, l); - buf = &mut buf[(offset + l)..]; + (buf, name_dest) = write_record_name_to_buf(record, buf)?; // Then add the tuple with the address let offset = buf.as_ptr().align_offset(align_of::()); @@ -60,7 +48,7 @@ pub unsafe fn records_to_gaih_addr( let tuple = &mut *(buf.as_mut_ptr().add(offset) as *mut gaih_addrtuple); tuple.next = std::ptr::null_mut(); - tuple.name = name_dest as *mut i8; + tuple.name = name_dest; tuple.scopeid = 0; // how to set tuple.scopeid ???? set_if_valid(prev_link, &mut *tuple); // link from previous tuple to this tuple prev_link = &mut (*tuple).next; @@ -88,22 +76,17 @@ pub unsafe fn records_to_gaih_addr( Ok(ret) } +pub enum DnsResult { + NxDomain, + Found, +} + +// TODO: refactor to be less ugly 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. - + host: &mut hostent, + mut buf: &mut [u8], +) -> std::io::Result { // In C struct hostent: // // - for the type of queries we perform, we can assume h_aliases @@ -112,11 +95,117 @@ pub unsafe fn records_to_hostent( // 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!(); + let first_record = match lookup.record_iter().peekable().peek() { + Some(record) => *record, + None => return Ok(DnsResult::NxDomain), + }; + + let first_rdata = match first_record.data() { + Some(rdata) => rdata, + None => return Err(Errno::ENODATA.into()), + }; + + // First put the name in the buffer + let name_dest; + (buf, name_dest) = write_record_name_to_buf(first_record, buf)?; + + // Then the empty aliases array (just a null pointer) + let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); + let l = size_of::<*mut c_char>(); + if buf.len() < offset + l { + return Err(Errno::ERANGE.into()); + } + let aliases_ptr = buf[offset..].as_mut_ptr().cast::<*mut c_char>(); + aliases_ptr.write(std::ptr::null_mut()); + buf = &mut buf[(offset + l)..]; + + // Then the address list + let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); + buf = &mut buf[offset..]; + + let records = lookup + .record_iter() + .flat_map(Record::data) + .filter(|r| discriminant(*r) == discriminant(first_rdata)); + + let mut addresses = vec![]; + for record in records { + let offset = buf.as_ptr().align_offset(align_of::<*mut c_char>()); + buf = &mut buf[offset..]; + addresses.push(buf.as_ptr()); + + let l = match record { + RData::A(addr) => { + let octets = addr.octets(); + let l = octets.len(); + if buf.len() < l { + return Err(Errno::ERANGE.into()); + } + buf[..l].copy_from_slice(&octets); + l + } + RData::AAAA(addr) => { + let octets = addr.octets(); + let l = octets.len(); + if buf.len() < l { + return Err(Errno::ERANGE.into()); + } + buf[..l].copy_from_slice(&octets); + l + } + _ => unreachable!(), + }; + + buf = &mut buf[l..]; } - Ok(()) + addresses.push(std::ptr::null_mut()); + + let offset = buf.as_ptr().align_offset(align_of::<*mut *mut c_char>()); + let l = size_of::<*mut c_char>() * addresses.len(); + if buf.len() < offset + l { + return Err(Errno::ERANGE.into()); + } + let addr_list = buf[offset..].as_mut_ptr().cast::<*mut c_char>(); + std::ptr::copy_nonoverlapping(addresses.as_ptr() as *const *mut c_char, addr_list, l); + + // Finally, populate the hostent structure + host.h_name = name_dest; + host.h_aliases = aliases_ptr; + host.h_addr_list = addr_list; + match first_rdata { + RData::A(_) => { + host.h_addrtype = AF_INET; + host.h_length = 4; + } + RData::AAAA(_) => { + host.h_addrtype = AF_INET6; + host.h_length = 16; + } + _ => return Err(Errno::EBADMSG.into()), + } + + Ok(DnsResult::Found) +} + +unsafe fn write_record_name_to_buf<'a>( + record: &Record, + buf: &'a mut [u8], +) -> std::io::Result<(&'a mut [u8], *mut c_char)> { + use std::io::{Error, ErrorKind}; + let name = CString::new(record.name().to_utf8()) + .map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))?; + + let offset = buf.as_ptr().align_offset(align_of::()); + let name_src = name.as_bytes_with_nul(); + let l = name_src.len(); + if buf.len() < offset + l { + return Err(Errno::ERANGE.into()); + } + + let name_dest = buf.as_mut_ptr().add(offset); + std::ptr::copy_nonoverlapping(name_src.as_ptr(), name_dest, l); + Ok((&mut buf[(offset + l)..], name_dest as *mut c_char)) } pub fn set_if_valid(ptr: *mut T, val: T) { @@ -129,6 +218,9 @@ pub fn block_on(f: F) -> Result where F: std::future::Future, { + // TODO: use std::panic::catch_unwind() to be resilient + // to OOM problems + use std::ops::Deref; match RUNTIME.deref() { Ok(rt) => Ok(rt.block_on(async { f.await })), diff --git a/src/nss_api.rs b/src/nss_api.rs index c0e7c8e..f8fca07 100644 --- a/src/nss_api.rs +++ b/src/nss_api.rs @@ -57,12 +57,10 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyname3_r( ttlp: *mut i32, canonp: *mut *mut char, ) -> nss_status { - let result = gethostbyname::HostEnt { host, canonp }; - let mut args = gethostbyname::Args { name, family: af, - result: gethostbyname::Result::V3(result), + result: gethostbyname::Result::V3(host), buffer, buflen, errnop, @@ -71,7 +69,12 @@ pub unsafe extern "C" fn _nss_malcontent_gethostbyname3_r( }; match block_on(async { gethostbyname::with(&mut args).await }) { - Ok(status) => status, + Ok(status) => { + if !host.is_null() { + set_if_valid(canonp, (*host).h_name as *mut char); + } + status + } Err(runtime_error) => { log::error!("gethostbyname3_r: {}", runtime_error); set_if_valid(h_errnop, HErrno::Internal); diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 47323ce..f1ba24f 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -66,6 +66,41 @@ fork_test! { }) } + #[test] + fn getaddrinfo_resolution() -> Result<()> { + common::setup()?; + tokio::runtime::Runtime::new().unwrap().block_on(async { + let _dbus = common::mock_dbus(HashMap::from([( + getuid(), + vec![CLOUDFLARE_PARENTALCONTROL_ADDRS.clone()], + )])); + + let hostname = std::ffi::CString::new("gnome.org").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::Success.0, + "Should have gotten an hostname, instead got {}", + error.to_str().unwrap() + ); + freeaddrinfo(addr); + }; + + //timeout(Duration::from_secs(1), dbus).await?? + Ok(()) + }) + } + + #[test] fn wikipedia_is_unrestricted() -> Result<()> { common::setup()?;