Skip to content

Commit e5bda10

Browse files
authored
Add sanity checks in DNS message decoding (#169)
And: * fixed a few new cargo clippy warnings. * defined DnsIncoming::HEADER_LEN const.
1 parent b503309 commit e5bda10

File tree

2 files changed

+75
-7
lines changed

2 files changed

+75
-7
lines changed

src/dns_parser.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,8 @@ pub(crate) struct DnsIncoming {
841841
}
842842

843843
impl DnsIncoming {
844+
const HEADER_LEN: usize = 12;
845+
844846
pub(crate) fn new(data: Vec<u8>) -> Result<Self> {
845847
let mut incoming = Self {
846848
offset: 0,
@@ -870,7 +872,7 @@ impl DnsIncoming {
870872
}
871873

872874
fn read_header(&mut self) -> Result<()> {
873-
if self.data.len() < 12 {
875+
if self.data.len() < Self::HEADER_LEN {
874876
return Err(Error::Msg(format!(
875877
"DNS incoming: header is too short: {} bytes",
876878
self.data.len()
@@ -885,7 +887,7 @@ impl DnsIncoming {
885887
self.num_authorities = u16_from_be_slice(&data[8..10]);
886888
self.num_additionals = u16_from_be_slice(&data[10..12]);
887889

888-
self.offset = 12;
890+
self.offset = Self::HEADER_LEN;
889891

890892
debug!(
891893
"read_header: id {}, {} questions {} answers {} authorities {} additionals",
@@ -954,6 +956,16 @@ impl DnsIncoming {
954956
for _ in 0..n {
955957
let name = self.read_name()?;
956958
let slice = &self.data[self.offset..];
959+
960+
// Muse have at least TYPE, CLASS, TTL, RDLENGTH fields: 10 bytes.
961+
if slice.len() < 10 {
962+
return Err(Error::Msg(format!(
963+
"read_others: RR '{}' is too short after name: {} bytes",
964+
&name,
965+
slice.len()
966+
)));
967+
}
968+
957969
let ty = u16_from_be_slice(&slice[..2]);
958970
let class = u16_from_be_slice(&slice[2..4]);
959971
let ttl = u32_from_be_slice(&slice[4..8]);
@@ -1113,7 +1125,18 @@ impl DnsIncoming {
11131125
0x00 => {
11141126
// regular utf8 string with length
11151127
offset += 1;
1116-
name += str::from_utf8(&data[offset..(offset + length as usize)])
1128+
let ending = offset + length as usize;
1129+
1130+
// Never read beyond the whole data length.
1131+
if ending > data.len() {
1132+
return Err(Error::Msg(format!(
1133+
"read_name: ending {} exceeds data length {}",
1134+
ending,
1135+
data.len()
1136+
)));
1137+
}
1138+
1139+
name += str::from_utf8(&data[offset..ending])
11171140
.map_err(|e| Error::Msg(format!("read_name: from_utf8: {}", e)))?;
11181141
name += ".";
11191142
offset += length as usize;
@@ -1176,7 +1199,10 @@ fn get_expiration_time(created: u64, ttl: u32, percent: u32) -> u64 {
11761199

11771200
#[cfg(test)]
11781201
mod tests {
1179-
use super::{DnsIncoming, DnsOutgoing, FLAGS_QR_QUERY, TYPE_PTR};
1202+
use super::{
1203+
DnsIncoming, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_UNIQUE, FLAGS_QR_QUERY,
1204+
FLAGS_QR_RESPONSE, TYPE_PTR,
1205+
};
11801206

11811207
#[test]
11821208
fn test_read_name_invalid_length() {
@@ -1186,7 +1212,9 @@ mod tests {
11861212
let data = out.to_packet_data();
11871213

11881214
// construct invalid data.
1215+
let max_len = data.len() as u8;
11891216
let mut data_with_invalid_name_length = data.clone();
1217+
let mut data_with_larger_name_length = data.clone();
11901218
let name_length_offset = 12;
11911219

11921220
// 0x9 is the length of `name`
@@ -1203,5 +1231,43 @@ mod tests {
12031231
if let Err(e) = invalid {
12041232
println!("error: {}", e);
12051233
}
1234+
1235+
// Another error case: `length`` is larger than the actual string length.
1236+
data_with_larger_name_length[name_length_offset] = max_len + 1;
1237+
let invalid = DnsIncoming::new(data_with_larger_name_length);
1238+
assert!(invalid.is_err());
1239+
if let Err(e) = invalid {
1240+
println!("error: {}", e);
1241+
}
1242+
}
1243+
1244+
/// Tests DnsIncoming::read_others()
1245+
#[test]
1246+
fn test_rr_too_short_after_name() {
1247+
let name = "test_rr_too_short._udp.local.";
1248+
let mut response = DnsOutgoing::new(FLAGS_QR_RESPONSE);
1249+
response.add_additional_answer(Box::new(DnsSrv::new(
1250+
name,
1251+
CLASS_IN | CLASS_UNIQUE,
1252+
1,
1253+
1,
1254+
1,
1255+
9000,
1256+
"instance1".to_string(),
1257+
)));
1258+
let data = response.to_packet_data();
1259+
let mut data_too_short = data.clone();
1260+
1261+
// verify the original data is good.
1262+
let incoming = DnsIncoming::new(data);
1263+
assert!(incoming.is_ok());
1264+
1265+
// verify that truncated data will cause an error.
1266+
data_too_short.truncate(DnsIncoming::HEADER_LEN + name.len() + 2);
1267+
let invalid = DnsIncoming::new(data_too_short);
1268+
assert!(invalid.is_err());
1269+
if let Err(e) = invalid {
1270+
println!("error: {}", e);
1271+
}
12061272
}
12071273
}

src/service_daemon.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,8 @@ impl Zeroconf {
14171417
return false;
14181418
}
14191419

1420+
buf.truncate(sz); // reduce potential processing errors
1421+
14201422
match DnsIncoming::new(buf) {
14211423
Ok(msg) => {
14221424
if msg.is_query() {
@@ -1532,7 +1534,7 @@ impl Zeroconf {
15321534

15331535
// resolve SRV record
15341536
if let Some(records) = self.cache.srv.get(fullname) {
1535-
if let Some(answer) = records.get(0) {
1537+
if let Some(answer) = records.first() {
15361538
if let Some(dns_srv) = answer.any().downcast_ref::<DnsSrv>() {
15371539
info.set_hostname(dns_srv.host.clone());
15381540
info.set_port(dns_srv.port);
@@ -1542,7 +1544,7 @@ impl Zeroconf {
15421544

15431545
// resolve TXT record
15441546
if let Some(records) = self.cache.txt.get(fullname) {
1545-
if let Some(record) = records.get(0) {
1547+
if let Some(record) = records.first() {
15461548
if let Some(dns_txt) = record.any().downcast_ref::<DnsTxt>() {
15471549
info.set_properties_from_txt(&dns_txt.text);
15481550
}
@@ -2001,7 +2003,7 @@ impl DnsCache {
20012003
self.srv
20022004
.iter()
20032005
.filter_map(|(instance, srv_list)| {
2004-
if let Some(item) = srv_list.get(0) {
2006+
if let Some(item) = srv_list.first() {
20052007
if let Some(dns_srv) = item.any().downcast_ref::<DnsSrv>() {
20062008
if dns_srv.host == host {
20072009
return Some(instance.clone());

0 commit comments

Comments
 (0)