Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions boringtun/src/noise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,21 +249,27 @@ impl Tunn {
/// Size of dst should be at least src.len() + 32, and no less than 148 bytes.
pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> {
let current = self.current;
if let Some(ref session) = self.sessions[current % N_SESSIONS] {
// Send the packet using an established session
let packet = session.format_packet_data(src, dst);
// Encrypt into dst using the current session, if any. We keep only
// the resulting length so the borrow on dst is released here, which
// lets us reuse dst for a fresh handshake on the exhausted-session
// path below.
let written = self.sessions[current % N_SESSIONS]
.as_ref()
.and_then(|session| session.format_packet_data(src, dst).map(|p| p.len()));
if let Some(written) = written {
self.timer_tick(TimerName::TimeLastPacketSent);
// Exclude Keepalive packets from timer update.
if !src.is_empty() {
self.timer_tick(TimerName::TimeLastDataPacketSent);
}
self.tx_bytes += src.len();
return TunnResult::WriteToNetwork(packet);
return TunnResult::WriteToNetwork(&mut dst[..written]);
}

// If there is no session, queue the packet for future retry
// Either no session is established or the current session has
// exhausted its REJECT_AFTER_MESSAGES nonce budget. Either way,
// queue the packet and trigger a fresh handshake.
self.queue_packet(src);
// Initiate a new handshake if none is in progress
self.format_handshake_initiation(dst, false)
}

Expand Down Expand Up @@ -353,7 +359,11 @@ impl Tunn {

let session = self.handshake.receive_handshake_response(p)?;

let keepalive_packet = session.format_packet_data(&[], dst);
// A freshly established session always has counter 0, well below
// REJECT_AFTER_MESSAGES, so format_packet_data must succeed here.
let keepalive_packet = session
.format_packet_data(&[], dst)
.expect("freshly established session must accept the keepalive");
// Store new session in ring buffer
let l_idx = session.local_index();
let index = l_idx % N_SESSIONS;
Expand Down
47 changes: 43 additions & 4 deletions boringtun/src/noise/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ const DATA_OFFSET: usize = 16;
/// The overhead of the AEAD
const AEAD_SIZE: usize = 16;

/// Initiator should rekey after sending this many messages on a session.
/// Per the WireGuard whitepaper section 5.2.
#[allow(dead_code)]
pub(crate) const REKEY_AFTER_MESSAGES: u64 = 1 << 60;
/// Both initiator and responder must refuse to send or receive on a session
/// once this many messages have been processed. Per the WireGuard whitepaper
/// section 5.2.
pub(crate) const REJECT_AFTER_MESSAGES: u64 = u64::MAX - (1 << 16) - 1;

// Receiving buffer constants
const WORD_SIZE: u64 = 64;
const N_WORDS: u64 = 16; // Suffice to reorder 64*16 = 1024 packets; can be increased at will
Expand Down Expand Up @@ -192,13 +201,24 @@ impl Session {

/// src - an IP packet from the interface
/// dst - pre-allocated space to hold the encapsulating UDP packet to send over the network
/// returns the size of the formatted packet
pub(super) fn format_packet_data<'a>(&self, src: &[u8], dst: &'a mut [u8]) -> &'a mut [u8] {
/// returns the formatted packet, or None if the session has reached
/// REJECT_AFTER_MESSAGES and must not be used to send any more data.
pub(super) fn format_packet_data<'a>(
&self,
src: &[u8],
dst: &'a mut [u8],
) -> Option<&'a mut [u8]> {
if dst.len() < src.len() + super::DATA_OVERHEAD_SZ {
panic!("The destination buffer is too small");
}

let sending_key_counter = self.sending_key_counter.fetch_add(1, Ordering::Relaxed) as u64;
let sending_key_counter = self.sending_key_counter.fetch_add(1, Ordering::Relaxed);
if sending_key_counter >= REJECT_AFTER_MESSAGES {
// Per the WireGuard spec, this session has exhausted its nonce
// budget and a rekey is mandatory before further traffic. Refuse
// to encrypt; the caller will trigger a new handshake.
return None;
}

let (message_type, rest) = dst.split_at_mut(4);
let (receiver_index, rest) = rest.split_at_mut(4);
Expand Down Expand Up @@ -226,7 +246,7 @@ impl Session {
.unwrap()
};

&mut dst[..DATA_OFFSET + n]
Some(&mut dst[..DATA_OFFSET + n])
}

/// packet - a data packet we received from the network
Expand All @@ -246,6 +266,11 @@ impl Session {
if packet.receiver_idx != self.receiving_index {
return Err(WireGuardError::WrongIndex);
}
// Per the WireGuard spec, sessions must refuse to process any
// packets at counter values >= REJECT_AFTER_MESSAGES.
if packet.counter >= REJECT_AFTER_MESSAGES {
return Err(WireGuardError::InvalidCounter);
}
// Don't reuse counters, in case this is a replay attack we want to quickly check the counter without running expensive decryption
self.receiving_counter_quick_check(packet.counter)?;

Expand Down Expand Up @@ -326,4 +351,18 @@ mod tests {
assert!(c.mark_did_receive(N_BITS * 3 + 71).is_err());
assert!(c.mark_did_receive(N_BITS * 3 + 72).is_err());
}

#[test]
fn rejects_send_counter_at_or_past_limit() {
// Build a Session with arbitrary keys and force the sending counter
// to REJECT_AFTER_MESSAGES; format_packet_data must refuse to emit a
// packet rather than reusing a nonce past the spec limit.
let session = Session::new(0, 0, [0u8; 32], [1u8; 32]);
session
.sending_key_counter
.store(REJECT_AFTER_MESSAGES, Ordering::Relaxed);

let mut dst = vec![0u8; 256];
assert!(session.format_packet_data(&[0u8; 32], &mut dst).is_none());
}
}