Skip to content

Commit b2791c6

Browse files
committed
explicitly check for assumptions in api/dns.rs
1 parent 329f6d4 commit b2791c6

File tree

1 file changed

+141
-2
lines changed

1 file changed

+141
-2
lines changed

src/rust/api/dns.rs

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ pub fn parse_replacement(input: &[&str]) -> jsg::Result<String, DnsParserError>
9292
// Each frame starts with the length of the remaining frame.
9393
while length_index < input.len() {
9494
let length = usize::from_str_radix(input[length_index], 16)?;
95+
if length + offset_index > input.len() {
96+
return Err(DnsParserError::InvalidDnsResponse(
97+
"replacement data too short for declared frame length".to_owned(),
98+
));
99+
}
95100
let subset = input[offset_index..length + offset_index].to_vec();
96101
let decoded = decode_hex(&subset)?.join("");
97102

@@ -144,10 +149,26 @@ impl DnsUtil {
144149
#[jsg_method]
145150
pub fn parse_caa_record(&self, record: String) -> Result<CaaRecord, DnsParserError> {
146151
// Let's remove "\\#" and the length of data from the beginning of the record
147-
let data = record.split_ascii_whitespace().collect::<Vec<_>>()[2..].to_vec();
152+
let parts: Vec<_> = record.split_ascii_whitespace().collect();
153+
if parts.len() < 3 {
154+
return Err(DnsParserError::InvalidDnsResponse(
155+
"CAA record too short: expected at least 3 fields".to_owned(),
156+
));
157+
}
158+
let data = parts[2..].to_vec();
159+
if data.len() < 2 {
160+
return Err(DnsParserError::InvalidDnsResponse(
161+
"CAA record data too short: expected critical and prefix length fields".to_owned(),
162+
));
163+
}
148164
let critical = data[0].parse::<u8>()?;
149165
let prefix_length = data[1].parse::<usize>()?;
150166

167+
if data.len() < 2 + prefix_length {
168+
return Err(DnsParserError::InvalidDnsResponse(format!(
169+
"CAA record data too short for prefix_length {prefix_length}"
170+
)));
171+
}
151172
let field = decode_hex(&data[2..prefix_length + 2])?.join("");
152173
let value = decode_hex(&data[(prefix_length + 2)..])?.join("");
153174

@@ -198,7 +219,20 @@ impl DnsUtil {
198219
/// `DnsParserError::ParseIntError`
199220
#[jsg_method]
200221
pub fn parse_naptr_record(&self, record: String) -> jsg::Result<NaptrRecord, DnsParserError> {
201-
let data = record.split_ascii_whitespace().collect::<Vec<_>>()[1..].to_vec();
222+
let parts: Vec<_> = record.split_ascii_whitespace().collect();
223+
if parts.len() < 2 {
224+
return Err(DnsParserError::InvalidDnsResponse(
225+
"NAPTR record too short".to_owned(),
226+
));
227+
}
228+
let data = parts[1..].to_vec();
229+
230+
// Need at least: length(1) + order(2) + preference(2) + flag_length(1) = 6 fields
231+
if data.len() < 6 {
232+
return Err(DnsParserError::InvalidDnsResponse(
233+
"NAPTR record data too short: expected at least 6 fields".to_owned(),
234+
));
235+
}
202236

203237
let order_str = data[1..3].to_vec();
204238
let order = u32::from_str_radix(&order_str.join(""), 16)?;
@@ -207,14 +241,29 @@ impl DnsUtil {
207241

208242
let flag_length = usize::from_str_radix(data[5], 16)?;
209243
let flag_offset = 6;
244+
if data.len() < flag_offset + flag_length + 1 {
245+
return Err(DnsParserError::InvalidDnsResponse(
246+
"NAPTR record too short for flags field".to_owned(),
247+
));
248+
}
210249
let flags = decode_hex(&data[flag_offset..flag_length + flag_offset])?.join("");
211250

212251
let service_length = usize::from_str_radix(data[flag_offset + flag_length], 16)?;
213252
let service_offset = flag_offset + flag_length + 1;
253+
if data.len() < service_offset + service_length + 1 {
254+
return Err(DnsParserError::InvalidDnsResponse(
255+
"NAPTR record too short for service field".to_owned(),
256+
));
257+
}
214258
let service = decode_hex(&data[service_offset..service_length + service_offset])?.join("");
215259

216260
let regexp_length = usize::from_str_radix(data[service_offset + service_length], 16)?;
217261
let regexp_offset = service_offset + service_length + 1;
262+
if data.len() < regexp_offset + regexp_length {
263+
return Err(DnsParserError::InvalidDnsResponse(
264+
"NAPTR record too short for regexp field".to_owned(),
265+
));
266+
}
218267
let regexp = decode_hex(&data[regexp_offset..regexp_length + regexp_offset])?.join("");
219268

220269
let replacement = parse_replacement(&data[regexp_offset + regexp_length..])?;
@@ -321,4 +370,94 @@ mod tests {
321370
assert_eq!(record.order, 5555);
322371
assert_eq!(record.preference, 2222);
323372
}
373+
374+
// =========================================================================
375+
// Malformed input tests — these previously caused panics (index out of bounds)
376+
// which would abort the process via CXX. They must return Err, not panic.
377+
// =========================================================================
378+
379+
#[test]
380+
fn test_parse_caa_record_empty_string() {
381+
let dns_util = DnsUtil {
382+
_state: ResourceState::default(),
383+
};
384+
assert!(dns_util.parse_caa_record(String::new()).is_err());
385+
}
386+
387+
#[test]
388+
fn test_parse_caa_record_single_token() {
389+
let dns_util = DnsUtil {
390+
_state: ResourceState::default(),
391+
};
392+
assert!(dns_util.parse_caa_record("\\#".to_owned()).is_err());
393+
}
394+
395+
#[test]
396+
fn test_parse_caa_record_two_tokens() {
397+
let dns_util = DnsUtil {
398+
_state: ResourceState::default(),
399+
};
400+
assert!(dns_util.parse_caa_record("\\# 15".to_owned()).is_err());
401+
}
402+
403+
#[test]
404+
fn test_parse_caa_record_data_too_short_for_prefix() {
405+
let dns_util = DnsUtil {
406+
_state: ResourceState::default(),
407+
};
408+
// critical=00, prefix_length=FF (255) but no data follows
409+
assert!(
410+
dns_util
411+
.parse_caa_record("\\# 02 00 FF".to_owned())
412+
.is_err()
413+
);
414+
}
415+
416+
#[test]
417+
fn test_parse_naptr_record_empty_string() {
418+
let dns_util = DnsUtil {
419+
_state: ResourceState::default(),
420+
};
421+
assert!(dns_util.parse_naptr_record(String::new()).is_err());
422+
}
423+
424+
#[test]
425+
fn test_parse_naptr_record_single_token() {
426+
let dns_util = DnsUtil {
427+
_state: ResourceState::default(),
428+
};
429+
assert!(dns_util.parse_naptr_record("\\#".to_owned()).is_err());
430+
}
431+
432+
#[test]
433+
fn test_parse_naptr_record_too_few_fields() {
434+
let dns_util = DnsUtil {
435+
_state: ResourceState::default(),
436+
};
437+
assert!(
438+
dns_util
439+
.parse_naptr_record("\\# 37 15 b3".to_owned())
440+
.is_err()
441+
);
442+
}
443+
444+
#[test]
445+
fn test_parse_replacement_length_exceeds_input() {
446+
// First element says frame is FF (255) bytes but only 2 bytes follow
447+
let input = vec!["FF", "73", "69"];
448+
assert!(parse_replacement(&input).is_err());
449+
}
450+
451+
#[test]
452+
fn test_parse_naptr_record_truncated_at_flags() {
453+
let dns_util = DnsUtil {
454+
_state: ResourceState::default(),
455+
};
456+
// Has order+preference+flag_length but no flag data
457+
assert!(
458+
dns_util
459+
.parse_naptr_record("\\# 06 15 b3 08 ae 05".to_owned())
460+
.is_err()
461+
);
462+
}
324463
}

0 commit comments

Comments
 (0)