From 82b7c9c35aaebf8a2811bdb703dd51c2fa0693c2 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Wed, 18 Feb 2026 16:40:23 +0100 Subject: [PATCH] http2: bound number of http2 frames per tx Ticket: 8289 If stream.reassembly.depth is unlimited, an attacker controlling the 2 sides of a communication going through Suricata can send a transition with an infinite number of headers, until suricata OOMs Solution is to offer a configuration option to bound the number of HTTP2 frames we store in a HTTP2 transaction, and produce an anomaly if this bound is crossed (cherry picked from commit 784e173278944c3596ea9cb219afcfafece6d156) --- rules/http2-events.rules | 2 ++ rust/src/http2/http2.rs | 23 +++++++++++++++++------ suricata.yaml.in | 2 ++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/rules/http2-events.rules b/rules/http2-events.rules index 8242e2f79e..ff170e8ed6 100644 --- a/rules/http2-events.rules +++ b/rules/http2-events.rules @@ -22,3 +22,5 @@ alert http2 any any -> any any (msg:"SURICATA HTTP2 authority host mismatch"; fl alert http2 any any -> any any (msg:"SURICATA HTTP2 user info in uri"; flow:established,to_server; app-layer-event:http2.userinfo_in_uri; classtype:protocol-command-decode; sid:2290014; rev:1;) alert http2 any any -> any any (msg:"SURICATA HTTP2 reassembly limit reached"; flow:established; app-layer-event:http2.reassembly_limit_reached; classtype:protocol-command-decode; sid:2290015; rev:1;) alert http2 any any -> any any (msg:"SURICATA HTTP2 data on stream zero"; flow:established; app-layer-event:http2.data_stream_zero; classtype:protocol-command-decode; sid:2290018; rev:1;) +# disabled by default, as it can happen in legit cases depending on the max-frames config value +# alert http2 any any -> any any (msg:"SURICATA HTTP2 too many frames"; flow:established; app-layer-event:http2.too_many_frames; classtype:protocol-command-decode; sid:2290019; rev:1;) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 008bfeb6d3..49647bc0ba 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -64,6 +64,7 @@ pub static mut HTTP2_MAX_TABLESIZE: u32 = 65536; // 0x10000 // maximum size of reassembly for header + continuation static mut HTTP2_MAX_REASS: usize = 102400; static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000 +static mut HTTP2_MAX_FRAMES: usize = 65536; #[repr(u8)] #[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] @@ -410,6 +411,7 @@ pub enum HTTP2Event { UserinfoInUri, ReassemblyLimitReached, DataStreamZero, + TooManyFrames, } pub struct HTTP2DynTable { @@ -1067,16 +1069,18 @@ impl HTTP2State { let ftype = head.ftype; let sid = head.stream_id; let padded = head.flags & parser::HTTP2_FLAG_HEADER_PADDED != 0; - if dir == Direction::ToServer { - tx.frames_ts.push(HTTP2Frame { - header: head, - data: txdata, - }); + let h2frames = if dir == Direction::ToServer { + &mut tx.frames_ts } else { - tx.frames_tc.push(HTTP2Frame { + &mut tx.frames_tc + }; + if h2frames.len() < unsafe { HTTP2_MAX_FRAMES } { + h2frames.push(HTTP2Frame { header: head, data: txdata, }); + } else { + tx.tx_data.set_event(HTTP2Event::TooManyFrames as u8); } if ftype == parser::HTTP2FrameType::Data as u8 && sid == 0 { tx.tx_data.set_event(HTTP2Event::DataStreamZero as u8); @@ -1393,6 +1397,13 @@ pub unsafe extern "C" fn rs_http2_register_parser() { SCLogError!("Invalid value for http2.max-streams"); } } + if let Some(val) = conf_get("app-layer.protocols.http2.max-frames") { + if let Ok(v) = val.parse::() { + HTTP2_MAX_FRAMES = v; + } else { + SCLogError!("Invalid value for http2.max-frames"); + } + } if let Some(val) = conf_get("app-layer.protocols.http2.max-table-size") { if let Ok(v) = val.parse::() { HTTP2_MAX_TABLESIZE = v; diff --git a/suricata.yaml.in b/suricata.yaml.in index 3cc45a461f..b891e76461 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -969,6 +969,8 @@ app-layer: #max-table-size: 65536 # Maximum reassembly size for header + continuation frames #max-reassembly-size: 102400 + # Maximum number of frames per tx + #max-frames: 65536 smtp: enabled: yes raw-extraction: no