diff --git a/rules/http2-events.rules b/rules/http2-events.rules index 7cceaf24c3..413fdd652c 100644 --- a/rules/http2-events.rules +++ b/rules/http2-events.rules @@ -20,3 +20,4 @@ alert http2 any any -> any any (msg:"SURICATA HTTP2 variable-length integer over alert http2 any any -> any any (msg:"SURICATA HTTP2 too many streams"; flow:established; app-layer-event:http2.too_many_streams; classtype:protocol-command-decode; sid:2290012; rev:1;) alert http2 any any -> any any (msg:"SURICATA HTTP2 authority host mismatch"; flow:established,to_server; app-layer-event:http2.authority_host_mismatch; classtype:protocol-command-decode; sid:2290013; rev:1;) 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;) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 14d7b47dfb..047b41402e 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -61,6 +61,8 @@ const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4; const HTTP2_FRAME_PRIORITY_LEN: usize = 5; const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4; 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 #[repr(u8)] @@ -406,6 +408,7 @@ pub enum HTTP2Event { TooManyStreams, AuthorityHostMismatch, UserinfoInUri, + ReassemblyLimitReached, } pub struct HTTP2DynTable { @@ -432,6 +435,12 @@ impl HTTP2DynTable { } } +#[derive(Default)] +struct HTTP2HeaderReassemblyBuffer { + data: Vec, + stream_id: u32, +} + pub struct HTTP2State { state_data: AppLayerStateData, tx_id: u64, @@ -441,6 +450,9 @@ pub struct HTTP2State { dynamic_headers_tc: HTTP2DynTable, transactions: VecDeque, progress: HTTP2ConnectionState, + + c2s_buf: HTTP2HeaderReassemblyBuffer, + s2c_buf: HTTP2HeaderReassemblyBuffer, } impl State for HTTP2State { @@ -473,6 +485,8 @@ impl HTTP2State { dynamic_headers_tc: HTTP2DynTable::new(), transactions: VecDeque::new(), progress: HTTP2ConnectionState::Http2StateInit, + c2s_buf: HTTP2HeaderReassemblyBuffer::default(), + s2c_buf: HTTP2HeaderReassemblyBuffer::default(), } } @@ -686,8 +700,11 @@ impl HTTP2State { } fn parse_frame_data( - &mut self, ftype: u8, input: &[u8], complete: bool, hflags: u8, dir: Direction, + &mut self, head: &parser::HTTP2FrameHeader, input: &[u8], complete: bool, dir: Direction, + reass_limit_reached: &mut bool, ) -> HTTP2FrameTypeData { + let ftype = head.ftype; + let hflags = head.flags; match num::FromPrimitive::from_u8(ftype) { Some(parser::HTTP2FrameType::GoAway) => { if input.len() < HTTP2_FRAME_GOAWAY_LEN { @@ -847,17 +864,47 @@ impl HTTP2State { return HTTP2FrameTypeData::DATA; } Some(parser::HTTP2FrameType::Continuation) => { + let buf = if dir == Direction::ToClient { + &mut self.s2c_buf + } else { + &mut self.c2s_buf + }; + if head.stream_id == buf.stream_id { + let max_reass = unsafe { HTTP2_MAX_REASS }; + if buf.data.len() + input.len() < max_reass { + buf.data.extend(input); + } else if buf.data.len() < max_reass { + buf.data.extend(&input[..max_reass - buf.data.len()]); + *reass_limit_reached = true; + } + if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { + let hs = parser::HTTP2FrameContinuation { + blocks: Vec::new(), + }; + return HTTP2FrameTypeData::CONTINUATION(hs); + } + } // else try to parse anyways + let input_reass = if head.stream_id == buf.stream_id { &buf.data } else { input }; + let dyn_headers = if dir == Direction::ToClient { &mut self.dynamic_headers_tc } else { &mut self.dynamic_headers_ts }; - match parser::http2_parse_frame_continuation(input, dyn_headers) { + match parser::http2_parse_frame_continuation(input_reass, dyn_headers) { Ok((_, hs)) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } self.process_headers(&hs.blocks, dir); return HTTP2FrameTypeData::CONTINUATION(hs); } Err(Err::Incomplete(_)) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } if complete { self.set_event(HTTP2Event::InvalidFrameData); return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { @@ -870,6 +917,10 @@ impl HTTP2State { } } Err(_) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } self.set_event(HTTP2Event::InvalidFrameData); return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { reason: HTTP2FrameUnhandledReason::ParsingError, @@ -878,6 +929,22 @@ impl HTTP2State { } } Some(parser::HTTP2FrameType::Headers) => { + if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { + let buf = if dir == Direction::ToClient { + &mut self.s2c_buf + } else { + &mut self.c2s_buf + }; + buf.data.clear(); + buf.data.extend(input); + buf.stream_id = head.stream_id; + let hs = parser::HTTP2FrameHeaders { + padlength: None, + priority: None, + blocks: Vec::new(), + }; + return HTTP2FrameTypeData::HEADERS(hs); + } let dyn_headers = if dir == Direction::ToClient { &mut self.dynamic_headers_tc } else { @@ -961,15 +1028,19 @@ impl HTTP2State { input = &rem[hlsafe..]; continue; } + let mut reass_limit_reached = false; let txdata = self.parse_frame_data( - head.ftype, + &head, &rem[..hlsafe], complete, - head.flags, dir, + &mut reass_limit_reached, ); let tx = self.find_or_create_tx(&head, &txdata, dir); + if reass_limit_reached { + tx.tx_data.set_event(HTTP2Event::ReassemblyLimitReached as u8); + } tx.handle_frame(&head, &txdata, dir); let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; let ftype = head.ftype; @@ -1306,6 +1377,13 @@ pub unsafe extern "C" fn rs_http2_register_parser() { SCLogError!("Invalid value for http2.max-table-size"); } } + if let Some(val) = conf_get("app-layer.protocols.http2.max-reassembly-size") { + if let Ok(v) = val.parse::() { + HTTP2_MAX_REASS = v as usize; + } else { + SCLogError!("Invalid value for http2.max-reassembly-size"); + } + } SCLogDebug!("Rust http2 parser registered."); } else { SCLogNotice!("Protocol detector and parser disabled for HTTP2."); diff --git a/suricata.yaml.in b/suricata.yaml.in index 630399126d..8914a0daf5 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -934,6 +934,8 @@ app-layer: #max-streams: 4096 # Maximum headers table size #max-table-size: 65536 + # Maximum reassembly size for header + continuation frames + #max-reassembly-size: 102400 smtp: enabled: yes raw-extraction: no