Skip to content

Commit 37dd8d9

Browse files
davidv1992rnijveld
authored andcommitted
Fix crash on oversized nts cookies.
1 parent c26f2e2 commit 37dd8d9

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

ntp-proto/src/nts_record.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ pub enum KeyExchangeError {
675675
NoValidAlgorithm,
676676
InvalidFixedKeyLength,
677677
NoCookies,
678+
CookiesTooBig,
678679
Io(std::io::Error),
679680
Tls(tls_utils::Error),
680681
Certificate(tls_utils::Error),
@@ -704,6 +705,7 @@ impl Display for KeyExchangeError {
704705
"The length of a fixed key does not match the algorithm used"
705706
),
706707
Self::NoCookies => write!(f, "Missing cookies"),
708+
Self::CookiesTooBig => write!(f, "Server returned cookies that are too large"),
707709
Self::Io(e) => write!(f, "{e}"),
708710
Self::Tls(e) => write!(f, "{e}"),
709711
Self::Certificate(e) => write!(f, "{e}"),
@@ -756,6 +758,7 @@ impl KeyExchangeError {
756758
| NoValidAlgorithm
757759
| InvalidFixedKeyLength
758760
| NoCookies
761+
| CookiesTooBig
759762
| Tls(_)
760763
| Certificate(_)
761764
| DnsName(_)
@@ -964,6 +967,9 @@ pub struct KeyExchangeResultDecoder {
964967
}
965968

966969
impl KeyExchangeResultDecoder {
970+
// Chosen such that we can get new cookies if the need arises. In practice, the cookie should never be this big.
971+
const MAX_COOKIE_SIZE: usize = 350;
972+
967973
pub fn step_with_slice(
968974
mut self,
969975
bytes: &[u8],
@@ -1027,6 +1033,9 @@ impl KeyExchangeResultDecoder {
10271033
Continue(state)
10281034
}
10291035
NewCookie { cookie_data } => {
1036+
if cookie_data.len() > Self::MAX_COOKIE_SIZE {
1037+
return ControlFlow::Break(Err(KeyExchangeError::CookiesTooBig));
1038+
}
10301039
state.cookies.store(cookie_data);
10311040
Continue(state)
10321041
}
@@ -2507,6 +2516,22 @@ mod test {
25072516
]
25082517
}
25092518

2519+
fn nts_oversized_cookie() -> [NtsRecord; 4] {
2520+
[
2521+
NtsRecord::NextProtocol {
2522+
protocol_ids: vec![0],
2523+
},
2524+
NtsRecord::AeadAlgorithm {
2525+
critical: false,
2526+
algorithm_ids: vec![15],
2527+
},
2528+
NtsRecord::NewCookie {
2529+
cookie_data: vec![0; 2048],
2530+
},
2531+
NtsRecord::EndOfMessage,
2532+
]
2533+
}
2534+
25102535
#[test]
25112536
fn test_nts_time_nl_response() {
25122537
let state = client_decode_records(nts_time_nl_records().as_slice()).unwrap();
@@ -2516,6 +2541,12 @@ mod test {
25162541
assert_eq!(state.cookies.gap(), 0);
25172542
}
25182543

2544+
#[test]
2545+
fn reject_oversized_cookie() {
2546+
let result = client_decode_records(nts_oversized_cookie().as_slice());
2547+
assert!(matches!(result, Err(KeyExchangeError::CookiesTooBig)));
2548+
}
2549+
25192550
#[test]
25202551
fn test_decode_nts_time_nl_response() {
25212552
let mut decoder = NtsRecord::decoder();

ntp-proto/src/source.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,11 @@ impl<Controller: SourceController<MeasurementDelay = NtpDuration>> NtpSource<Con
571571
.cookies
572572
.gap()
573573
.min(((self.buffer.len() - 300) / cookie.len()).min(u8::MAX as usize) as u8);
574+
// Defence in depth, ensure we can get at least 1 new cookie.
575+
if new_cookies == 0 {
576+
warn!("NTS Cookie too large, resetting source. This may be a problem with the source");
577+
return actions![NtpSourceAction::Reset];
578+
}
574579
match self.protocol_version {
575580
ProtocolVersion::V4 => {
576581
NtpPacket::nts_poll_message(&cookie, new_cookies, poll_interval)
@@ -861,7 +866,11 @@ impl<Controller: SourceController<MeasurementDelay = NtpDuration>> NtpSource<Con
861866

862867
#[cfg(test)]
863868
mod test {
864-
use crate::{packet::NoCipher, time_types::PollIntervalLimits, NtpClock};
869+
use crate::{
870+
packet::{AesSivCmac256, NoCipher},
871+
time_types::PollIntervalLimits,
872+
NtpClock,
873+
};
865874

866875
use super::*;
867876
#[cfg(feature = "ntpv5")]
@@ -1089,6 +1098,23 @@ mod test {
10891098
assert!(source.current_poll_interval() >= source.controller.0);
10901099
}
10911100

1101+
#[test]
1102+
fn test_oversize_cookie_doesnt_crash() {
1103+
let mut source = NtpSource::test_ntp_source(NoopController);
1104+
let mut ntsdata = SourceNtsData {
1105+
cookies: CookieStash::default(),
1106+
c2s: Box::new(AesSivCmac256::new([0; 32].into())),
1107+
s2c: Box::new(AesSivCmac256::new([0; 32].into())),
1108+
};
1109+
ntsdata.cookies.store(vec![0; 2048]);
1110+
ntsdata.cookies.store(vec![0; 2048]);
1111+
source.nts = Some(Box::new(ntsdata));
1112+
let actions = source.handle_timer();
1113+
for action in actions {
1114+
assert!(matches!(action, NtpSourceAction::Reset))
1115+
}
1116+
}
1117+
10921118
#[test]
10931119
fn test_handle_incoming() {
10941120
let base = NtpInstant::now();

0 commit comments

Comments
 (0)