fix: P0 bugs — IPv4 IHL parsing, TX frame leak, SSH whitelist logic

IPv4 IHL: L4 header offset was hardcoded to 34 (20-byte IPv4 header).
Now reads IHL field dynamically (20-60 bytes) so IP options don't
cause wrong port/flag parsing. Attackers could previously bypass
port-based rules by adding IP options.

TX frame leak: tx.produce() may submit fewer frames than provided.
Unsubmitted FrameDescs were lost, permanently shrinking frame_pool.
Now returns unsubmitted frames to pool.

SSH whitelist: Array<PlaceHolder>.get(0).is_some() always returns true
(zero-initialized entries exist). Changed to check actual value != 0,
matching userspace enable(1)/disable(0) semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
DaLaw2 2026-03-21 14:26:11 +08:00
parent ac56b7b480
commit a14812df94
3 changed files with 25 additions and 5 deletions

View File

@ -31,6 +31,16 @@ unsafe fn parse_ipv4_packet(start: usize, end: usize, target: *mut ParsedPacket)
let ipv4 = &*((start + IPV4_HEADER_START) as *const Ipv4Hdr);
let packet_length = (end - start) as u32;
// Compute actual IPv4 header length from the IHL field
let ihl = (*((start + IPV4_HEADER_START) as *const u8) & 0x0F) as usize * 4;
if ihl < 20 || ihl > 60 {
return Err(());
}
let l4_start = ETHER_HEADER_END + ihl;
if start + l4_start > end {
return Err(());
}
let t = &mut *target;
t.timestamp_ns = bpf_ktime_get_ns();
core::ptr::copy_nonoverlapping(ipv4.src_addr.as_ptr(), t.src_ip.as_mut_ptr(), 4);
@ -39,13 +49,16 @@ unsafe fn parse_ipv4_packet(start: usize, end: usize, target: *mut ParsedPacket)
t.ip_version = 4;
t.protocol = ipv4.proto;
let tcp_min_end = l4_start + core::mem::size_of::<TcpHdr>();
let udp_min_end = l4_start + core::mem::size_of::<UdpHdr>();
let (src_port, dst_port, tcp_flags, l4_header_len) = match ipv4.proto {
IpProto::Tcp => parse_tcp(start, end, IPV4_TCP_HEADER_START, IPV4_TCP_HEADER_END)?,
IpProto::Udp => parse_udp(start, end, IPV4_UDP_HEADER_START, IPV4_UDP_HEADER_END)?,
IpProto::Tcp => parse_tcp(start, end, l4_start, tcp_min_end)?,
IpProto::Udp => parse_udp(start, end, l4_start, udp_min_end)?,
_ => (0, 0, 0, 0),
};
t.payload_length = packet_length.saturating_sub((IPV4_HEADER_END + l4_header_len) as u32);
t.payload_length = packet_length.saturating_sub((l4_start + l4_header_len) as u32);
t.src_port = src_port;
t.dst_port = dst_port;
t.tcp_flags = tcp_flags;

View File

@ -109,7 +109,7 @@ fn get_http_request_method(start: usize, end: usize, offset: usize) -> Option<Ht
fn ipv4_ssh_service_violation(source: &AddrPortV4, destination: &AddrPortV4) -> bool {
unsafe {
if IPV4_SSH_SERVICE.get(destination).is_some() {
if SSH_WHITE_LIST_ENABLE.get(0).is_some() {
if matches!(SSH_WHITE_LIST_ENABLE.get(0), Some(&v) if v != 0) {
IPV4_SSH_WHITE_LIST.get(&source.ip()).is_none()
} else {
IPV4_SSH_BLACK_LIST.get(&source.ip()).is_some()
@ -124,7 +124,7 @@ fn ipv4_ssh_service_violation(source: &AddrPortV4, destination: &AddrPortV4) ->
fn ipv6_ssh_service_violation(source: &AddrPortV6, destination: &AddrPortV6) -> bool {
unsafe {
if IPV6_SSH_SERVICE.get(destination).is_some() {
if SSH_WHITE_LIST_ENABLE.get(0).is_some() {
if matches!(SSH_WHITE_LIST_ENABLE.get(0), Some(&v) if v != 0) {
IPV6_SSH_WHITE_LIST.get(&source.ip()).is_none()
} else {
IPV6_SSH_BLACK_LIST.get(&source.ip()).is_some()

View File

@ -422,6 +422,13 @@ impl XskPair {
let nb_submitted = unsafe { self.tx.produce(&frames) };
// Return unsubmitted frames to pool to prevent frame leak
if nb_submitted < frames.len() {
for frame in frames[nb_submitted..].iter() {
self.frame_pool.push(*frame);
}
}
if let Err(e) = self.tx.wakeup() {
if e.kind() != std::io::ErrorKind::WouldBlock {
log!(EbpfLog::TXWakeupFailed(e.to_string()));