Skip to content

Fix panic in raw socket fragmentation when payload buffer exceeds packet size #1077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

maxslarsson
Copy link

@maxslarsson maxslarsson commented Jul 19, 2025

Summary

This PR fixes a panic that occurs when fragmenting raw socket packets. The panic happens when the packet that is being sent is larger than the MTU, and is not exactly the same size as the fragmentation buffer, causing copy_from_slice to fail with mismatched slice lengths.

Bug Details

When a raw socket packet requires fragmentation (packet size > MTU), the following sequence occurs:

  1. In InterfaceInner::dispatch_ip(), the fragmentation path is entered when total_ip_len > self.caps.ip_mtu()

// If we have an IPv4 packet, then we need to check if we need to fragment it.
if total_ip_len > self.caps.ip_mtu() {
#[cfg(feature = "proto-ipv4-fragmentation")]
{
net_debug!("start fragmentation");
// Calculate how much we will send now (including the Ethernet header).
let tx_len = self.caps.max_transmission_unit;
let ip_header_len = repr.buffer_len();
let first_frag_ip_len = self.caps.ip_mtu();
if frag.buffer.len() < total_ip_len {
net_debug!(
"Fragmentation buffer is too small, at least {} needed. Dropping",
total_ip_len
);
return Ok(());
}
#[cfg(feature = "medium-ethernet")]
{
frag.ipv4.dst_hardware_addr = dst_hardware_addr;
}
// Save the total packet len (without the Ethernet header, but with the first
// IP header).
frag.packet_len = total_ip_len;
// Save the IP header for other fragments.
frag.ipv4.repr = *repr;
// Save how much bytes we will send now.
frag.sent_bytes = first_frag_ip_len;
// Modify the IP header
repr.payload_len = first_frag_ip_len - repr.buffer_len();
// Emit the IP header to the buffer.
emit_ip(&ip_repr, &mut frag.buffer);
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut frag.buffer[..]);
frag.ipv4.ident = ipv4_id;
ipv4_packet.set_ident(ipv4_id);
ipv4_packet.set_more_frags(true);
ipv4_packet.set_dont_frag(false);
ipv4_packet.set_frag_offset(0);
if caps.checksum.ipv4.tx() {
ipv4_packet.fill_checksum();
}
// Transmit the first packet.
tx_token.consume(tx_len, |mut tx_buffer| {
#[cfg(feature = "medium-ethernet")]
if matches!(self.caps.medium, Medium::Ethernet) {
emit_ethernet(&ip_repr, tx_buffer)?;
tx_buffer = &mut tx_buffer[EthernetFrame::<&[u8]>::header_len()..];
}
// Change the offset for the next packet.
frag.ipv4.frag_offset = (first_frag_ip_len - ip_header_len) as u16;
// Copy the IP header and the payload.
tx_buffer[..first_frag_ip_len]
.copy_from_slice(&frag.buffer[..first_frag_ip_len]);
Ok(())
})
}

  1. The code calls emit_ip(&ip_repr, &mut frag.buffer) to emit the packet into the fragmentation buffer

emit_ip(&ip_repr, &mut frag.buffer);

  1. Inside emit_ip, after emitting the IP header, it passes the remaining fragmentation buffer to emit_payload:

let emit_ip = |repr: &IpRepr, tx_buffer: &mut [u8]| {
repr.emit(&mut *tx_buffer, &self.caps.checksum);
let payload = &mut tx_buffer[repr.header_len()..];
packet.emit_payload(repr, payload, &caps)
};

  1. For raw sockets, emit_payload attempts to copy the entire raw packet into the fragmentation buffer "payload":

smoltcp/src/iface/packet.rs

Lines 132 to 133 in a54589c

#[cfg(feature = "socket-raw")]
IpPayload::Raw(raw_packet) => payload.copy_from_slice(raw_packet),

The issue is that raw_packet can be much smaller than payload, since the payload is the fragmentation buffer . For example:

  • Fragmentation buffer size: 8192 bytes
  • IP header size: 20 bytes
  • Resulting payload buffer: 8172 bytes
  • Packet size: 4136 bytes (or any amount greater than the MTU and not equal to the fragmentation buffer size)

This mismatch causes copy_from_slice to panic with:

copy_from_slice: source slice length (4136) does not match destination slice length (8172)

Fix

The fix ensures we only write to the portion of the destination buffer that matches the source size:

IpPayload::Raw(raw_packet) => {
    let len = raw_packet.len();
    payload[..len].copy_from_slice(raw_packet)
}

This prevents the panic while maintaining the same behavior - the raw packet data is copied to the beginning of the payload buffer, and any remaining buffer space is left untouched.

Testing

This fix has been tested with raw socket packets that require fragmentation and no longer causes panics when the fragmentation buffer is larger than the packet payload.

Copy link

codecov bot commented Jul 19, 2025

Codecov Report

Attention: Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.

Project coverage is 79.62%. Comparing base (a54589c) to head (582cdc7).

Files with missing lines Patch % Lines
src/iface/packet.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1077      +/-   ##
==========================================
- Coverage   79.62%   79.62%   -0.01%     
==========================================
  Files          81       81              
  Lines       24218    24220       +2     
==========================================
  Hits        19284    19284              
- Misses       4934     4936       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@whitequark
Copy link
Contributor

Please add a test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants