diff --git a/tenderlink/src/bandwidth_test.rs b/tenderlink/src/bandwidth_test.rs index 751aa91d..d65cac43 100644 --- a/tenderlink/src/bandwidth_test.rs +++ b/tenderlink/src/bandwidth_test.rs @@ -894,8 +894,15 @@ impl ReassemblySlot { /// returns (success, is_complete) pub fn insert(&mut self, offset: usize, data: &[u8], is_fin: bool) -> (bool, bool) { + let Some(end_usize) = offset.checked_add(data.len()) else { + return (false, false); + }; + if end_usize > MAX_JUMBOGRAM_LEN { + return (false, false); + } + let start = offset as u32; - let end = (offset + data.len()) as u32; + let end = end_usize as u32; // If this is the final fragment, derive total_len from offset + data.len() if is_fin { @@ -906,7 +913,7 @@ impl ReassemblySlot { if last_end > end { return (false, false); } } self.total_len = Some(end); - self.buf.resize(end as usize, 0); + self.buf.resize(end_usize, 0); } } @@ -925,8 +932,8 @@ impl ReassemblySlot { } // Grow buf if needed (total_len not yet known) - if end as usize > self.buf.len() { - self.buf.resize(end as usize, 0); + if end_usize > self.buf.len() { + self.buf.resize(end_usize, 0); } // Find all ranges that overlap or are adjacent to [start, end) @@ -946,7 +953,7 @@ impl ReassemblySlot { } // Write new data (overlapping parts verified equal) - self.buf[offset..offset + data.len()].copy_from_slice(data); + self.buf[offset..end_usize].copy_from_slice(data); // Merge: union of [start, end) with all overlapping/adjacent ranges let merged_start = if first < last { self.received[first].0.min(start) } else { start }; @@ -1612,7 +1619,11 @@ pub fn new_network_thread(my_keypairs: Vec, my_port: u16, max_p if !success { existing_connection.unreliable_reassembly[slot_idx] = ReassemblySlot::new(); (success, complete) = existing_connection.unreliable_reassembly[slot_idx].insert(frag_offset as usize, frag_data, is_fin); - assert!(success); + if !success { + if OVERLY_VERBOSE { println!("Error, oversized or invalid jumbogram fragment from {connection_key:?}. Disconnecting..."); } + connections_map.remove(&connection_key); + break 'conn; + } } if complete { let completed = std::mem::replace(&mut existing_connection.unreliable_reassembly[slot_idx], ReassemblySlot::new()); diff --git a/tenderlink/src/lib.rs b/tenderlink/src/lib.rs index 882cd1e6..6d7f04ed 100644 --- a/tenderlink/src/lib.rs +++ b/tenderlink/src/lib.rs @@ -71,6 +71,7 @@ const STP_PACKLET_HDR: usize = 2; const PATH_MTU: usize = UDP_mMTU - STP_HEADER_SIZE - STP_PACKLET_HDR; +const MAX_BFT_PROPOSAL_SIZE: usize = 8 * 1024 * 1024; // Tweak this! const MAX_BANDWIDTH_BYTES_PER_SECOND: usize = 1_000_000; @@ -716,6 +717,17 @@ impl TMState { PACKET_TYPE_PROPOSAL_CHUNK => { let Some(hdr) = PacketProposalChunkHeader::read_from(&mut &signed_data[..]) else { return TMStatus::Fail; }; + let proposal_size = hdr.proposal_size as usize; + if proposal_size > MAX_BFT_PROPOSAL_SIZE { + eprintln!("{ctx_str} {ANSI_RED}BFT FAULT{ANSI_RST} at {}.{}.{}: proposal too large ({} bytes). Ignoring...", + height, round, chunk_i, hdr.proposal_size); + return TMStatus::Fail; + } + if chunk_i >= proposal_size.div_ceil(PROPOSAL_CHUNK_DATA_SIZE) { + eprintln!("{ctx_str} {ANSI_RED}BFT FAULT{ANSI_RST} at {}.{}.{}: proposal chunk out of range for {} bytes. Ignoring...", + height, round, chunk_i, hdr.proposal_size); + return TMStatus::Fail; + } // "have they previously proposed a different value?" if is_prev_seen_round && round_data.proposal_sigs_n > 0 { @@ -1067,7 +1079,7 @@ impl TMState { // new roster by the decided-block closure). self.vote_namespace = new_vote_namespace; self.recent_commit_round_cache.push(self.rounds_data[i].clone()); - self.rounds_data.retain(|r| r.height < self.height); + self.rounds_data.retain(|r| r.height >= self.height); self.locked_value_round = (None, -1); self.valid_value_round = (None, -1); self.start_round(roster, now, 0).await; @@ -2096,7 +2108,19 @@ pub async fn entry_point(my_root_private_key: SigningKey, }; let proposal_size = hdr.proposal_size as usize; let chunk_i = hdr.chunk_i as usize; - let chunk_size = usize::min(PROPOSAL_CHUNK_DATA_SIZE, proposal_size - chunk_i * PROPOSAL_CHUNK_DATA_SIZE); + if proposal_size > MAX_BFT_PROPOSAL_SIZE { + eprintln!("{:05}: couldn't read proposal chunk: proposal too large {}", my_port, proposal_size); + continue; + } + let Some(chunk_o) = chunk_i.checked_mul(PROPOSAL_CHUNK_DATA_SIZE) else { + eprintln!("{:05}: couldn't read proposal chunk: chunk index overflow {}", my_port, chunk_i); + continue; + }; + if chunk_o >= proposal_size { + eprintln!("{:05}: couldn't read proposal chunk: chunk index out of range {}", my_port, chunk_i); + continue; + } + let chunk_size = usize::min(PROPOSAL_CHUNK_DATA_SIZE, proposal_size - chunk_o); let packet_size = chunk_size + PROPOSAL_PACKET_EXTRA; // NOTE: assume for the moment that this is the valid height, we'll check in the subsequent call