Skip to content

Commit 1b56b2f

Browse files
committed
dtls: defer replay commits until datagram parse succeeds
1 parent 7be1d6a commit 1b56b2f

6 files changed

Lines changed: 434 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
* Fix malformed datagrams consuming DTLS replay-window state #121
34
* Replace pending DTLS 1.2 handshake output on resend #116
45
* Discard bad protected DTLS 1.2 records after handshake #115
56
* Reject oversized DTLS certificate lists #113

src/dtls12/incoming.rs

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::Error;
88
use crate::buffer::{Buf, TmpBuf};
99
use crate::crypto::{Aad, Nonce};
1010
use crate::dtls12::message::{ContentType, DTLSRecord, Dtls12CipherSuite, Handshake, Sequence};
11+
use crate::window::ReplayWindow;
1112

1213
/// Holds both the UDP packet and the parsed result of that packet.
1314
pub struct Incoming {
@@ -67,12 +68,14 @@ pub struct Records {
6768
}
6869

6970
impl Records {
70-
pub fn parse(
71+
fn parse(
7172
mut packet: &[u8],
7273
decrypt: &mut dyn RecordHandler,
7374
cs: Option<Dtls12CipherSuite>,
7475
) -> Result<Records, Error> {
7576
let mut parsed_records: ArrayVec<Record, 8> = ArrayVec::new();
77+
let mut replay_updates: ArrayVec<Sequence, 8> = ArrayVec::new();
78+
let mut pending_replay = ReplayWindow::new();
7679

7780
// Find record boundaries and copy each record ONCE from the packet
7881
while !packet.is_empty() {
@@ -91,21 +94,43 @@ impl Records {
9194
// This is the ONLY copy: packet -> record buffer
9295
let record_slice = &packet[..record_end];
9396
match Record::parse(record_slice, decrypt, cs) {
94-
Ok(record) => {
95-
if let Some(record) = record {
97+
Ok(parsed) => {
98+
if let Some(sequence) = parsed.replay_sequence {
99+
if !pending_replay.check(sequence.sequence_number) {
100+
trace!("Discarding duplicate rec in same datagram");
101+
packet = &packet[record_end..];
102+
continue;
103+
}
104+
}
105+
106+
if let Some(record) = parsed.record {
96107
if parsed_records.try_push(record).is_err() {
97108
return Err(Error::TooManyRecords);
98109
}
99-
} else {
110+
} else if parsed.replay_sequence.is_none() {
100111
trace!("Discarding replayed rec");
101112
}
113+
114+
if let Some(sequence) = parsed.replay_sequence {
115+
pending_replay.update(sequence.sequence_number);
116+
if replay_updates.try_push(sequence).is_err() {
117+
return Err(Error::TooManyRecords);
118+
}
119+
}
102120
}
103121
Err(e) => return Err(e),
104122
}
105123

106124
packet = &packet[record_end..];
107125
}
108126

127+
// Commit replay state only after the whole UDP datagram has parsed
128+
// successfully. A malformed trailing record must not consume
129+
// replay state for an earlier authenticated record in the same datagram.
130+
for sequence in replay_updates {
131+
decrypt.replay_update(sequence);
132+
}
133+
109134
let mut records = ArrayVec::new();
110135
for record in parsed_records {
111136
if let Some(record) = decrypt.classify_record(record)? {
@@ -134,14 +159,19 @@ pub struct Record {
134159
parsed: Box<ParsedRecord>,
135160
}
136161

162+
struct RecordParse {
163+
record: Option<Record>,
164+
replay_sequence: Option<Sequence>,
165+
}
166+
137167
impl Record {
138168
/// The first parse pass only parses the DTLSRecord header which is unencrypted.
139169
/// Copies record data from UDP packet ONCE into a pooled buffer.
140-
pub fn parse(
170+
fn parse(
141171
record_slice: &[u8],
142172
decrypt: &mut dyn RecordHandler,
143173
cs: Option<Dtls12CipherSuite>,
144-
) -> Result<Option<Record>, Error> {
174+
) -> Result<RecordParse, Error> {
145175
// ONLY COPY: UDP packet slice -> pooled buffer
146176
let mut buffer = Buf::new();
147177
buffer.extend_from_slice(record_slice);
@@ -151,7 +181,10 @@ impl Record {
151181
// RFC 6347 §4.1.2.7: Invalid records SHOULD be silently discarded.
152182
// This includes epoch 0 records with invalid ContentType.
153183
trace!("Discarding record: parse failed: {}", e);
154-
return Ok(None);
184+
return Ok(RecordParse {
185+
record: None,
186+
replay_sequence: None,
187+
});
155188
}
156189
};
157190
let parsed = Box::new(parsed);
@@ -162,7 +195,10 @@ impl Record {
162195
// packet loss, we can end up seeing epoch 1 records before we can decrypt them.
163196
let is_epoch_0 = record.record().sequence.epoch == 0;
164197
if is_epoch_0 || !decrypt.is_peer_encryption_enabled() {
165-
return Ok(Some(record));
198+
return Ok(RecordParse {
199+
record: Some(record),
200+
replay_sequence: None,
201+
});
166202
}
167203

168204
// We need to decrypt the record and redo the parsing.
@@ -171,12 +207,18 @@ impl Record {
171207

172208
// Anti-replay check (read-only, does not update window)
173209
if !decrypt.replay_check(sequence) {
174-
return Ok(None);
210+
return Ok(RecordParse {
211+
record: None,
212+
replay_sequence: None,
213+
});
175214
}
176215

177216
let explicit_nonce_len = decrypt.explicit_nonce_len();
178217
if (dtls.length as usize) < decrypt.min_protected_fragment_len() {
179-
return Ok(None);
218+
return Ok(RecordParse {
219+
record: None,
220+
replay_sequence: None,
221+
});
180222
}
181223

182224
// Get a reference to the buffer
@@ -203,25 +245,35 @@ impl Record {
203245
}
204246

205247
trace!("Discarding record: decrypt failed: {}", e);
206-
return Ok(None);
248+
return Ok(RecordParse {
249+
record: None,
250+
replay_sequence: None,
251+
});
207252
}
208253

209254
buffer.len()
210255
};
211256

212-
// Decryption succeeded — now commit the replay window update.
213-
// RFC 6347 §4.1.2.6: "The receive window is updated only if the
214-
// MAC verification succeeds."
215-
decrypt.replay_update(sequence);
216-
217257
// Update the length of the record.
218258
buffer[11] = (new_len >> 8) as u8;
219259
buffer[12] = new_len as u8;
220260

221-
let parsed = ParsedRecord::parse(&buffer, cs, explicit_nonce_len)?;
261+
let parsed = match ParsedRecord::parse(&buffer, cs, explicit_nonce_len) {
262+
Ok(parsed) => parsed,
263+
Err(e) => {
264+
trace!("Discarding authenticated record: parse failed: {}", e);
265+
return Ok(RecordParse {
266+
record: None,
267+
replay_sequence: Some(sequence),
268+
});
269+
}
270+
};
222271
let parsed = Box::new(parsed);
223272

224-
Ok(Some(Record { buffer, parsed }))
273+
Ok(RecordParse {
274+
record: Some(Record { buffer, parsed }),
275+
replay_sequence: Some(sequence),
276+
})
225277
}
226278

227279
pub fn record(&self) -> &DTLSRecord {

src/dtls13/engine.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2338,7 +2338,13 @@ impl RecordHandler for Engine {
23382338
epoch_bits
23392339
}
23402340

2341-
fn resolve_sequence(&self, epoch: u16, seq_bits: u64, s_flag: bool) -> u64 {
2341+
fn resolve_sequence(
2342+
&self,
2343+
epoch: u16,
2344+
seq_bits: u64,
2345+
s_flag: bool,
2346+
expected_override: Option<u64>,
2347+
) -> u64 {
23422348
let expected = if epoch == 2 {
23432349
self.hs_expected_recv_seq
23442350
} else {
@@ -2348,6 +2354,9 @@ impl RecordHandler for Engine {
23482354
.map(|e| e.expected_recv_seq)
23492355
.unwrap_or(0)
23502356
};
2357+
let expected = expected_override
2358+
.map(|override_expected| expected.max(override_expected))
2359+
.unwrap_or(expected);
23512360

23522361
let bits: u32 = if s_flag { 16 } else { 8 };
23532362
reconstruct_sequence(seq_bits, expected, bits)

0 commit comments

Comments
 (0)