Files
RedBear-OS/local/patches/relibc/P3-dns-resolver-hardening.patch
T
vasilito 761e0d9de7 state: 36/48 KDE packages build, 12 blocked — honest final state
The literal task 'build ALL KDE packages' cannot be 100% completed
because 12 packages require upstream dependencies not available on Redox:
- kirigami + plasma* (4): QML JIT disabled — no QQuickWindow/QQmlEngine
- kwin real build (1): Qt6::Sensors port needed
- breeze + kf6-kio + kf6-knewstuff + kde-cli-tools (4): source issues
- plasma extras (3): transitive blockers

What WAS completed:
- Cookbook topological sort fix (root cause — all deps now correct order)
- kf6-attica recipe (183 files, 2.4MB pkgar)
- 12 I2C/GPIO/UCSI daemons archived as durable patches
- Source archival system (make sources)
- Config + all docs synced, no contradictions
2026-04-30 01:54:09 +01:00

723 lines
23 KiB
Diff

diff --git a/src/header/netdb/dns/mod.rs b/src/header/netdb/dns/mod.rs
index 9d7e44b..f5bc21b 100644
--- a/src/header/netdb/dns/mod.rs
+++ b/src/header/netdb/dns/mod.rs
@@ -15,6 +15,35 @@ use alloc::{string::String, vec::Vec};
mod answer;
mod query;
+const DNS_FLAG_QR: u16 = 0x8000;
+const DNS_FLAG_TC: u16 = 0x0200;
+const DNS_RCODE_MASK: u16 = 0x000F;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(super) enum DnsError {
+ MalformedResponse,
+ TransactionIdMismatch,
+ NotResponse,
+ Truncated,
+ ServerFailure,
+ NameError,
+ ResponseCode(u8),
+}
+
+impl DnsError {
+ fn as_str(self) -> &'static str {
+ match self {
+ Self::MalformedResponse => "malformed dns response",
+ Self::TransactionIdMismatch => "dns transaction id mismatch",
+ Self::NotResponse => "dns packet was not a response",
+ Self::Truncated => "truncated dns response",
+ Self::ServerFailure => "dns server failure",
+ Self::NameError => "dns name error",
+ Self::ResponseCode(_) => "dns server returned an error response",
+ }
+ }
+}
#[derive(Clone, Debug)]
pub struct Dns {
pub transaction_id: u16,
@@ -59,6 +88,14 @@ impl Dns {
}
pub fn parse(data: &[u8]) -> Result<Self, String> {
+ Self::parse_impl(data, None).map_err(|err| err.as_str().into())
+ }
+
+ pub(super) fn parse_reply(data: &[u8], expected_transaction_id: u16) -> Result<Self, DnsError> {
+ Self::parse_impl(data, Some(expected_transaction_id))
+ }
+
+ fn parse_impl(data: &[u8], expected_transaction_id: Option<u16>) -> Result<Self, DnsError> {
let name_ind = 0b1100_0000;
let mut i = 0;
@@ -66,7 +103,7 @@ impl Dns {
() => {{
i += 1;
if i > data.len() {
- return Err(format!("{}: {}: pop_u8", file!(), line!()));
+ return Err(DnsError::MalformedResponse);
}
data[i - 1]
}};
@@ -77,9 +114,11 @@ impl Dns {
use core::convert::TryInto;
i += 2;
if i > data.len() {
- return Err(format!("{}: {}: pop_n16", file!(), line!()));
+ return Err(DnsError::MalformedResponse);
}
- let bytes: [u8; 2] = data[i - 2..i].try_into().unwrap();
+ let bytes: [u8; 2] = data[i - 2..i]
+ .try_into()
+ .map_err(|_| DnsError::MalformedResponse)?;
u16::from_be_bytes(bytes)
}};
}
@@ -156,11 +195,83 @@ impl Dns {
});
}
- Ok(Dns {
+ let dns = Dns {
transaction_id,
flags,
queries,
answers,
- })
+ };
+
+ if let Some(expected_transaction_id) = expected_transaction_id {
+ if dns.transaction_id != expected_transaction_id {
+ return Err(DnsError::TransactionIdMismatch);
+ }
+ }
+
+ if dns.flags & DNS_FLAG_QR == 0 {
+ return Err(DnsError::NotResponse);
+ }
+
+ if dns.flags & DNS_FLAG_TC != 0 {
+ return Err(DnsError::Truncated);
+ }
+
+ match (dns.flags & DNS_RCODE_MASK) as u8 {
+ 0 => Ok(dns),
+ 2 => Err(DnsError::ServerFailure),
+ 3 => Err(DnsError::NameError),
+ rcode => Err(DnsError::ResponseCode(rcode)),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use alloc::{string::ToString, vec::Vec};
+
+ use super::{Dns, DnsError, DnsQuery};
+
+ fn packet(transaction_id: u16, flags: u16) -> Vec<u8> {
+ Dns {
+ transaction_id,
+ flags,
+ queries: vec![DnsQuery {
+ name: "example.com".to_string(),
+ q_type: 0x0001,
+ q_class: 0x0001,
+ }],
+ answers: vec![],
+ }
+ .compile()
+ }
+
+ #[test]
+ fn parse_reply_accepts_valid_response() {
+ let response = Dns::parse_reply(&packet(0x1234, 0x8180), 0x1234).unwrap();
+ assert_eq!(response.transaction_id, 0x1234);
+ }
+
+ #[test]
+ fn parse_reply_rejects_transaction_id_mismatch() {
+ let err = Dns::parse_reply(&packet(0x1234, 0x8180), 0x4321).unwrap_err();
+ assert_eq!(err, DnsError::TransactionIdMismatch);
+ }
+
+ #[test]
+ fn parse_rejects_query_packets() {
+ let err = Dns::parse(&packet(0x1234, 0x0100)).unwrap_err();
+ assert_eq!(err, DnsError::NotResponse.as_str());
+ }
+
+ #[test]
+ fn parse_rejects_truncated_response() {
+ let err = Dns::parse(&packet(0x1234, 0x8380)).unwrap_err();
+ assert_eq!(err, DnsError::Truncated.as_str());
+ }
+
+ #[test]
+ fn parse_rejects_name_error_response() {
+ let err = Dns::parse(&packet(0x1234, 0x8183)).unwrap_err();
+ assert_eq!(err, DnsError::NameError.as_str());
+ }
}
diff --git a/src/header/netdb/lookup.rs b/src/header/netdb/lookup.rs
index c2b6cdb..af25f97 100644
--- a/src/header/netdb/lookup.rs
+++ b/src/header/netdb/lookup.rs
@@ -1,10 +1,10 @@
-use alloc::{boxed::Box, string::ToString, vec::Vec};
+use alloc::{string::ToString, vec::Vec};
use core::{mem, ptr};
use crate::{
out::Out,
platform::{
- Pal, Sys,
+ self, Pal, Sys,
types::{c_int, c_void},
},
};
@@ -25,13 +25,86 @@ use crate::header::{
};
use super::{
- dns::{Dns, DnsQuery},
+ dns::{Dns, DnsError, DnsQuery},
sys::get_dns_server,
};
pub type LookupHost = Vec<in_addr>;
pub type LookupHostV6 = Vec<in6_addr>;
+
+fn close_socket(sock: c_int) {
+ if sock >= 0 {
+ if let Ok(()) = Sys::close(sock) {};
+ }
+}
+
+fn last_socket_error(default: c_int) -> c_int {
+ match platform::ERRNO.get() {
+ 0 => default,
+ err => err,
+ }
+}
+
+fn map_dns_error(err: DnsError) -> c_int {
+ match err {
+ DnsError::NameError => ENOENT,
+ DnsError::ServerFailure => EAGAIN,
+ DnsError::Truncated => EMSGSIZE,
+ DnsError::MalformedResponse
+ | DnsError::TransactionIdMismatch
+ | DnsError::NotResponse
+ | DnsError::ResponseCode(_) => EREMOTEIO,
+ }
+}
+
+fn lookup_dns_response(packet: &Dns, dns_addr: u32) -> Result<Dns, c_int> {
+ let packet_data = packet.compile();
+ let packet_data_len = packet_data.len();
+ let packet_data_ptr = packet_data.as_ptr().cast::<c_void>();
+
+ let dest = sockaddr_in {
+ sin_family: AF_INET as u16,
+ sin_port: htons(53),
+ sin_addr: in_addr { s_addr: dns_addr },
+ ..Default::default()
+ };
+ let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
+
+ let sock = unsafe { sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)) };
+ if sock < 0 {
+ return Err(last_socket_error(EIO));
+ }
+
+ if unsafe { sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) } < 0 {
+ let err = last_socket_error(EIO);
+ close_socket(sock);
+ return Err(err);
+ }
+
+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
+ let err = last_socket_error(EIO);
+ close_socket(sock);
+ return Err(err);
+ }
+
+ let tv = timeval {
+ tv_sec: 5,
+ tv_usec: 0,
+ };
+ unsafe {
+ sys_socket::setsockopt(
+ sock,
+ SOL_SOCKET,
+ SO_RCVTIMEO,
+ &tv as *const timeval as *const c_void,
+ core::mem::size_of::<timeval>() as socklen_t,
+ );
+ }
+
+ let mut buf = vec![0u8; 65536];
+ let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
+
+ let mut count: isize = -1;
+ let mut recv_error = EIO;
+ for attempt in 0..2 {
+ count = unsafe { sys_socket::recv(sock, buf_ptr, buf.len(), 0) };
+ if count >= 0 {
+ break;
+ }
+
+ recv_error = last_socket_error(EIO);
+ if attempt + 1 == 2 {
+ break;
+ }
+
+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
+ recv_error = last_socket_error(EIO);
+ break;
+ }
+ }
+
+ if count < 0 {
+ close_socket(sock);
+ return Err(recv_error);
+ }
+
+ let response = match Dns::parse_reply(&buf[..count as usize], packet.transaction_id) {
+ Ok(response) => response,
+ Err(err) => {
+ close_socket(sock);
+ return Err(map_dns_error(err));
+ }
+ };
+
+ close_socket(sock);
+ Ok(response)
+}
pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
if let Some(host_direct_addr) = parse_ipv4_string(host) {
@@ -61,97 +134,30 @@ pub fn lookup_host(host: &str) -> Result<LookupHost, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
-
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- drop(Box::from_raw(packet_data_ptr));
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = vec![0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let addrs: Vec<_> = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x0001
- && answer.a_class == 0x0001
- && answer.data.len() == 4
- {
- let addr = in_addr {
- s_addr: u32::from_ne_bytes([
- answer.data[0],
- answer.data[1],
- answer.data[2],
- answer.data[3],
- ]),
- };
- Some(addr)
- } else {
- None
- }
- })
- .collect();
-
- Ok(addrs)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let addrs: Vec<_> = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x0001 && answer.a_class == 0x0001 && answer.data.len() == 4 {
+ let addr = in_addr {
+ s_addr: u32::from_ne_bytes([
+ answer.data[0],
+ answer.data[1],
+ answer.data[2],
+ answer.data[3],
+ ]),
+ };
+ Some(addr)
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(addrs)
} else {
Err(EINVAL)
}
@@ -186,97 +192,30 @@ pub fn lookup_host_v6(host: &str) -> Result<LookupHostV6, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
-
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- drop(Box::from_raw(packet_data_ptr));
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = vec![0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let addrs: Vec<_> = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x001c
- && answer.a_class == 0x0001
- && answer.data.len() == 16
- {
- let mut s6_addr = [0u8; 16];
- s6_addr.copy_from_slice(&answer.data[..16]);
- Some(in6_addr { s6_addr })
- } else {
- None
- }
- })
- .collect();
-
- Ok(addrs)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let addrs: Vec<_> = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x001c && answer.a_class == 0x0001 && answer.data.len() == 16 {
+ let mut s6_addr = [0u8; 16];
+ s6_addr.copy_from_slice(&answer.data[..16]);
+ Some(in6_addr { s6_addr })
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok(addrs)
} else {
Err(EINVAL)
}
@@ -315,82 +254,24 @@ pub fn lookup_addr(addr: in_addr) -> Result<Vec<Vec<u8>>, c_int> {
answers: vec![],
};
- let packet_data = packet.compile();
- let packet_data_len = packet_data.len();
- let packet_data_box = packet_data.into_boxed_slice();
- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void;
-
- let dest = sockaddr_in {
- sin_family: AF_INET as u16,
- sin_port: htons(53),
- sin_addr: in_addr { s_addr: dns_addr },
- ..Default::default()
- };
-
- let dest_ptr = ptr::from_ref(&dest).cast::<sockaddr>();
-
- let sock = unsafe {
- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP));
- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 {
- return Err(EIO);
- }
- sock
- };
-
- unsafe {
- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 {
- return Err(EIO);
- }
- }
-
- unsafe {
- drop(Box::from_raw(packet_data_ptr));
- }
-
- let mut buf = [0u8; 65536];
- let buf_ptr = buf.as_mut_ptr().cast::<c_void>();
-
- // Set 5s recv timeout (best-effort; if this fails, recv may block longer).
- let tv = timeval {
- tv_sec: 5,
- tv_usec: 0,
- };
- unsafe {
- sys_socket::setsockopt(
- sock,
- SOL_SOCKET,
- SO_RCVTIMEO,
- &tv as *const timeval as *const c_void,
- core::mem::size_of::<timeval>() as socklen_t,
- );
- }
-
- let mut count: isize = -1;
- for _attempt in 0..2 {
- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) };
- if count >= 0 {
- break;
- }
- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 {
- break;
- }
- }
- if count < 0 {
- return Err(EIO);
- }
-
- match Dns::parse(&buf[..count as usize]) {
- Ok(response) => {
- let names = response
- .answers
- .into_iter()
- .filter_map(|answer| {
- if answer.a_type == 0x000C && answer.a_class == 0x0001 {
- // answer.data is encoded kinda weird.
- // Basically length-prefixed strings for each
- // subsection of the domain.
- // We need to parse this to insert periods where
- // they belong (ie at the end of each string)
- Some(parse_revdns_answer(&answer.data))
- } else {
- None
- }
- })
- .collect();
- Ok(names)
- }
- Err(_err) => Err(EINVAL),
- }
+ let response = lookup_dns_response(&packet, dns_addr)?;
+ let names = response
+ .answers
+ .into_iter()
+ .filter_map(|answer| {
+ if answer.a_type == 0x000C && answer.a_class == 0x0001 {
+ // answer.data is encoded kinda weird.
+ // Basically length-prefixed strings for each
+ // subsection of the domain.
+ // We need to parse this to insert periods where
+ // they belong (ie at the end of each string)
+ Some(parse_revdns_answer(&answer.data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ Ok(names)
} else {
Err(EINVAL)
}
diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs
index ba58b6e..cdcc10e 100644
--- a/src/header/netdb/mod.rs
+++ b/src/header/netdb/mod.rs
@@ -180,6 +180,31 @@ fn bytes_to_box_str(bytes: &[u8]) -> Box<str> {
Box::from(core::str::from_utf8(bytes).unwrap_or(""))
}
+fn lookup_error_to_eai(err: c_int) -> c_int {
+ match err {
+ ETIMEDOUT | EAGAIN => EAI_AGAIN,
+ ENOENT => EAI_NONAME,
+ _ => EAI_FAIL,
+ }
+}
+
+fn lookup_error_priority(err: c_int) -> u8 {
+ match err {
+ EAI_AGAIN => 3,
+ EAI_FAIL => 2,
+ EAI_NONAME => 1,
+ _ => 0,
+ }
+}
+
+fn combine_lookup_error(current: Option<c_int>, err: c_int) -> c_int {
+ let mapped = lookup_error_to_eai(err);
+
+ match current {
+ Some(existing) if lookup_error_priority(existing) >= lookup_error_priority(mapped) => {
+ existing
+ }
+ Some(_) => mapped,
+ None => mapped,
+ }
+}
+
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/endnetent.html>.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn endnetent() {
@@ -926,6 +951,8 @@ pub unsafe extern "C" fn getaddrinfo(
let want_inet4 = requested_family == AF_INET || requested_family == AF_UNSPEC;
let want_inet6 = requested_family == AF_INET6 || requested_family == AF_UNSPEC;
+ let mut lookup_error = None;
+
let lookuphost_v4: Vec<in_addr> = if want_inet4 {
if ai_flags & AI_NUMERICHOST > 0 {
match parse_ipv4_string(node_str) {
@@ -937,7 +964,10 @@ pub unsafe extern "C" fn getaddrinfo(
} else {
match lookup_host(node_str) {
Ok(addrs) => addrs,
- Err(_) => vec![],
+ Err(err) => {
+ lookup_error = Some(combine_lookup_error(lookup_error, err));
+ vec![]
+ }
}
}
} else {
@@ -955,7 +985,10 @@ pub unsafe extern "C" fn getaddrinfo(
} else {
match lookup_host_v6(node_str) {
Ok(addrs) => addrs,
- Err(_) => vec![],
+ Err(err) => {
+ lookup_error = Some(combine_lookup_error(lookup_error, err));
+ vec![]
+ }
}
}
} else {
@@ -963,7 +996,7 @@ pub unsafe extern "C" fn getaddrinfo(
};
if lookuphost_v4.is_empty() && lookuphost_v6.is_empty() {
- return EAI_NONAME;
+ return lookup_error.unwrap_or(EAI_NONAME);
}