From 2d5172aaf3e2802fed087bfeeaea2477e2f6665d 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 019e3feff1..74da042e97 100644 --- a/rules/http2-events.rules +++ b/rules/http2-events.rules @@ -24,3 +24,5 @@ alert http2 any any -> any any (msg:"SURICATA HTTP2 reassembly limit reached"; f alert http2 any any -> any any (msg:"SURICATA HTTP2 dns request too long"; flow:established,to_server; app-layer-event:http2.dns_request_too_long; classtype:protocol-command-decode; sid:2290016; rev:1;) alert http2 any any -> any any (msg:"SURICATA HTTP2 dns response too long"; flow:established,to_client; app-layer-event:http2.dns_response_too_long; classtype:protocol-command-decode; sid:2290017; 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 ed8a2abc82..4bd22ce06a 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -77,6 +77,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; #[derive(AppLayerFrameType)] pub enum Http2FrameType { @@ -529,6 +530,7 @@ pub enum HTTP2Event { DnsRequestTooLong, DnsResponseTooLong, DataStreamZero, + TooManyFrames, } pub struct HTTP2DynTable { @@ -1237,16 +1239,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); @@ -1587,6 +1591,13 @@ pub unsafe extern "C" fn SCRegisterHttp2Parser() { 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 1b4df5709e..c40013f9c0 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1013,6 +1013,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