]> git.ipfire.org Git - people/ms/suricata.git/blobdiff - rust/src/http2/http2.rs
app-layer: include DetectEngineState in AppLayerTxData
[people/ms/suricata.git] / rust / src / http2 / http2.rs
index efb60937dea95220fcd1971790cf87b831d84d4d..b0545f0a7550bb888720a84c9e4bfbd8ffaeb1c8 100644 (file)
  */
 
 use super::decompression;
+use super::detect;
 use super::parser;
+use super::range;
+
 use crate::applayer::{self, *};
-use crate::core::{
-    self, AppProto, Flow, SuricataFileContext, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP,
-    STREAM_TOCLIENT, STREAM_TOSERVER,
-};
+use crate::core::{self, *};
 use crate::filecontainer::*;
 use crate::filetracker::*;
 use nom;
@@ -129,11 +129,11 @@ pub struct HTTP2Transaction {
     pub frames_ts: Vec<HTTP2Frame>,
 
     decoder: decompression::HTTP2Decoder,
+    pub file_range: *mut HttpRangeContainerBlock,
 
-    de_state: Option<*mut core::DetectEngineState>,
     events: *mut core::AppLayerDecoderEvents,
     tx_data: AppLayerTxData,
-    ft_tc: FileTransferTracker,
+    pub ft_tc: FileTransferTracker,
     ft_ts: FileTransferTracker,
 
     //temporary escaped header for detection
@@ -141,6 +141,12 @@ pub struct HTTP2Transaction {
     pub escaped: Vec<Vec<u8>>,
 }
 
+impl Transaction for HTTP2Transaction {
+    fn id(&self) -> u64 {
+        self.tx_id
+    }
+}
+
 impl HTTP2Transaction {
     pub fn new() -> HTTP2Transaction {
         HTTP2Transaction {
@@ -151,7 +157,7 @@ impl HTTP2Transaction {
             frames_tc: Vec::new(),
             frames_ts: Vec::new(),
             decoder: decompression::HTTP2Decoder::new(),
-            de_state: None,
+            file_range: std::ptr::null_mut(),
             events: std::ptr::null_mut(),
             tx_data: AppLayerTxData::new(),
             ft_tc: FileTransferTracker::new(),
@@ -161,15 +167,33 @@ impl HTTP2Transaction {
     }
 
     pub fn free(&mut self) {
-        if self.events != std::ptr::null_mut() {
+        if !self.events.is_null() {
             core::sc_app_layer_decoder_events_free_events(&mut self.events);
         }
-        if let Some(state) = self.de_state {
-            core::sc_detect_engine_state_free(state);
+        if !self.file_range.is_null() {
+            match unsafe { SC } {
+                None => panic!("BUG no suricata_config"),
+                Some(c) => {
+                    //TODO get a file container instead of NULL
+                    (c.HTPFileCloseHandleRange)(
+                        std::ptr::null_mut(),
+                        0,
+                        self.file_range,
+                        std::ptr::null_mut(),
+                        0,
+                    );
+                    (c.HttpRangeFreeBlock)(self.file_range);
+                }
+            }
         }
     }
 
-    fn handle_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: u8) {
+    pub fn set_event(&mut self, event: HTTP2Event) {
+        let ev = event as u8;
+        core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, ev);
+    }
+
+    fn handle_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: Direction) {
         for i in 0..blocks.len() {
             if blocks[i].name == "content-encoding".as_bytes().to_vec() {
                 self.decoder.http2_encoding_fromvec(&blocks[i].value, dir);
@@ -178,13 +202,44 @@ impl HTTP2Transaction {
     }
 
     fn decompress<'a>(
-        &'a mut self, input: &'a [u8], dir: u8, sfcm: &'static SuricataFileContext, over: bool,
-        files: &mut FileContainer, flags: u16,
+        &'a mut self, input: &'a [u8], dir: Direction, sfcm: &'static SuricataFileContext,
+        over: bool, files: &mut FileContainer, flags: u16, flow: *const Flow,
     ) -> io::Result<()> {
         let mut output = Vec::with_capacity(decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE);
         let decompressed = self.decoder.decompress(input, &mut output, dir)?;
         let xid: u32 = self.tx_id as u32;
-        if dir == STREAM_TOCLIENT {
+        if dir == Direction::ToClient {
+            self.ft_tc.tx_id = self.tx_id - 1;
+            if !self.ft_tc.file_open {
+                // we are now sure that new_chunk will open a file
+                // even if it may close it right afterwards
+                self.tx_data.incr_files_opened();
+                if let Ok(value) = detect::http2_frames_get_header_value_vec(
+                    self,
+                    Direction::ToClient,
+                    "content-range",
+                ) {
+                    match range::http2_parse_check_content_range(&value) {
+                        Ok((_, v)) => {
+                            range::http2_range_open(self, &v, flow, sfcm, flags, decompressed);
+                            if over && !self.file_range.is_null() {
+                                range::http2_range_close(self, files, flags, &[])
+                            }
+                        }
+                        _ => {
+                            self.set_event(HTTP2Event::InvalidRange);
+                        }
+                    }
+                }
+            } else {
+                if !self.file_range.is_null() {
+                    if over {
+                        range::http2_range_close(self, files, flags, decompressed)
+                    } else {
+                        range::http2_range_append(self.file_range, decompressed)
+                    }
+                }
+            }
             self.ft_tc.new_chunk(
                 sfcm,
                 files,
@@ -198,6 +253,10 @@ impl HTTP2Transaction {
                 &xid,
             );
         } else {
+            self.ft_ts.tx_id = self.tx_id - 1;
+            if !self.ft_ts.file_open {
+                self.tx_data.incr_files_opened();
+            }
             self.ft_ts.new_chunk(
                 sfcm,
                 files,
@@ -215,12 +274,12 @@ impl HTTP2Transaction {
     }
 
     fn handle_frame(
-        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8,
+        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction,
     ) {
         //handle child_stream_id changes
         match data {
             HTTP2FrameTypeData::PUSHPROMISE(hs) => {
-                if dir == STREAM_TOCLIENT {
+                if dir == Direction::ToClient {
                     //we could set an event if self.child_stream_id != 0
                     if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 {
                         self.child_stream_id = hs.stream_id;
@@ -230,7 +289,7 @@ impl HTTP2Transaction {
                 self.handle_headers(&hs.blocks, dir);
             }
             HTTP2FrameTypeData::CONTINUATION(hs) => {
-                if dir == STREAM_TOCLIENT
+                if dir == Direction::ToClient
                     && header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0
                 {
                     self.child_stream_id = 0;
@@ -238,7 +297,7 @@ impl HTTP2Transaction {
                 self.handle_headers(&hs.blocks, dir);
             }
             HTTP2FrameTypeData::HEADERS(hs) => {
-                if dir == STREAM_TOCLIENT {
+                if dir == Direction::ToClient {
                     self.child_stream_id = 0;
                 }
                 self.handle_headers(&hs.blocks, dir);
@@ -255,12 +314,12 @@ impl HTTP2Transaction {
                     match self.state {
                         HTTP2TransactionState::HTTP2StateHalfClosedClient
                         | HTTP2TransactionState::HTTP2StateDataServer => {
-                            if dir == STREAM_TOCLIENT {
+                            if dir == Direction::ToClient {
                                 self.state = HTTP2TransactionState::HTTP2StateClosed;
                             }
                         }
                         HTTP2TransactionState::HTTP2StateHalfClosedServer => {
-                            if dir == STREAM_TOSERVER {
+                            if dir == Direction::ToServer {
                                 self.state = HTTP2TransactionState::HTTP2StateClosed;
                             }
                         }
@@ -268,7 +327,7 @@ impl HTTP2Transaction {
                         HTTP2TransactionState::HTTP2StateClosed => {}
                         HTTP2TransactionState::HTTP2StateGlobal => {}
                         _ => {
-                            if dir == STREAM_TOCLIENT {
+                            if dir == Direction::ToClient {
                                 self.state = HTTP2TransactionState::HTTP2StateHalfClosedServer;
                             } else {
                                 self.state = HTTP2TransactionState::HTTP2StateHalfClosedClient;
@@ -277,7 +336,7 @@ impl HTTP2Transaction {
                     }
                 } else if header.ftype == parser::HTTP2FrameType::DATA as u8 {
                     //not end of stream
-                    if dir == STREAM_TOSERVER {
+                    if dir == Direction::ToServer {
                         if self.state < HTTP2TransactionState::HTTP2StateDataClient {
                             self.state = HTTP2TransactionState::HTTP2StateDataClient;
                         }
@@ -311,6 +370,7 @@ pub enum HTTP2Event {
     StreamIdReuse,
     InvalidHTTP1Settings,
     FailedDecompression,
+    InvalidRange,
 }
 
 pub struct HTTP2DynTable {
@@ -342,6 +402,12 @@ pub struct HTTP2State {
     pub files: Files,
 }
 
+impl State<HTTP2Transaction> for HTTP2State {
+    fn get_transactions(&self) -> &[HTTP2Transaction] {
+        &self.transactions
+    }
+}
+
 impl HTTP2State {
     pub fn new() -> Self {
         Self {
@@ -436,7 +502,7 @@ impl HTTP2State {
     }
 
     pub fn find_or_create_tx(
-        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8,
+        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction,
     ) -> &mut HTTP2Transaction {
         if header.stream_id == 0 {
             return self.create_global_tx();
@@ -445,7 +511,7 @@ impl HTTP2State {
             //yes, the right stream_id for Suricata is not the header one
             HTTP2FrameTypeData::PUSHPROMISE(hs) => hs.stream_id,
             HTTP2FrameTypeData::CONTINUATION(_) => {
-                if dir == STREAM_TOCLIENT {
+                if dir == Direction::ToClient {
                     //continuation of a push promise
                     self.find_child_stream_id(header.stream_id)
                 } else {
@@ -477,7 +543,7 @@ impl HTTP2State {
         }
     }
 
-    fn process_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: u8) {
+    fn process_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: Direction) {
         let (mut update, mut sizeup) = (false, 0);
         for i in 0..blocks.len() {
             if blocks[i].error >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError {
@@ -493,7 +559,7 @@ impl HTTP2State {
         }
         if update {
             //borrow checker forbids to pass directly dyn_headers
-            let dyn_headers = if dir == STREAM_TOCLIENT {
+            let dyn_headers = if dir == Direction::ToClient {
                 &mut self.dynamic_headers_tc
             } else {
                 &mut self.dynamic_headers_ts
@@ -503,7 +569,7 @@ impl HTTP2State {
     }
 
     fn parse_frame_data(
-        &mut self, ftype: u8, input: &[u8], complete: bool, hflags: u8, dir: u8,
+        &mut self, ftype: u8, input: &[u8], complete: bool, hflags: u8, dir: Direction,
     ) -> HTTP2FrameTypeData {
         match num::FromPrimitive::from_u8(ftype) {
             Some(parser::HTTP2FrameType::GOAWAY) => {
@@ -531,7 +597,7 @@ impl HTTP2State {
                         for i in 0..set.len() {
                             if set[i].id == parser::HTTP2SettingsId::SETTINGSHEADERTABLESIZE {
                                 //reverse order as this is what we accept from the other endpoint
-                                let dyn_headers = if dir == STREAM_TOCLIENT {
+                                let dyn_headers = if dir == Direction::ToClient {
                                     &mut self.dynamic_headers_ts
                                 } else {
                                     &mut self.dynamic_headers_tc
@@ -630,7 +696,7 @@ impl HTTP2State {
                 }
             }
             Some(parser::HTTP2FrameType::PUSHPROMISE) => {
-                let dyn_headers = if dir == STREAM_TOCLIENT {
+                let dyn_headers = if dir == Direction::ToClient {
                     &mut self.dynamic_headers_tc
                 } else {
                     &mut self.dynamic_headers_ts
@@ -664,7 +730,7 @@ impl HTTP2State {
                 return HTTP2FrameTypeData::DATA;
             }
             Some(parser::HTTP2FrameType::CONTINUATION) => {
-                let dyn_headers = if dir == STREAM_TOCLIENT {
+                let dyn_headers = if dir == Direction::ToClient {
                     &mut self.dynamic_headers_tc
                 } else {
                     &mut self.dynamic_headers_ts
@@ -695,7 +761,7 @@ impl HTTP2State {
                 }
             }
             Some(parser::HTTP2FrameType::HEADERS) => {
-                let dyn_headers = if dir == STREAM_TOCLIENT {
+                let dyn_headers = if dir == Direction::ToClient {
                     &mut self.dynamic_headers_tc
                 } else {
                     &mut self.dynamic_headers_ts
@@ -740,7 +806,9 @@ impl HTTP2State {
         }
     }
 
-    fn parse_frames(&mut self, mut input: &[u8], il: usize, dir: u8) -> AppLayerResult {
+    fn parse_frames(
+        &mut self, mut input: &[u8], il: usize, dir: Direction, flow: *const Flow,
+    ) -> AppLayerResult {
         while input.len() > 0 {
             match parser::http2_parse_frame_header(input) {
                 Ok((rem, head)) => {
@@ -789,7 +857,7 @@ impl HTTP2State {
                     let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0;
                     let ftype = head.ftype;
                     let sid = head.stream_id;
-                    if dir == STREAM_TOSERVER {
+                    if dir == Direction::ToServer {
                         tx.frames_ts.push(HTTP2Frame {
                             header: head,
                             data: txdata,
@@ -806,12 +874,7 @@ impl HTTP2State {
                                 //borrow checker forbids to reuse directly tx
                                 let index = self.find_tx_index(sid);
                                 if index > 0 {
-                                    let mut tx_same = &mut self.transactions[index - 1];
-                                    if dir == STREAM_TOCLIENT {
-                                        tx_same.ft_tc.tx_id = tx_same.tx_id - 1;
-                                    } else {
-                                        tx_same.ft_ts.tx_id = tx_same.tx_id - 1;
-                                    }
+                                    let tx_same = &mut self.transactions[index - 1];
                                     let (files, flags) = self.files.get(dir);
                                     match tx_same.decompress(
                                         &rem[..hlsafe],
@@ -820,6 +883,7 @@ impl HTTP2State {
                                         over,
                                         files,
                                         flags,
+                                        flow,
                                     ) {
                                         Err(_e) => {
                                             self.set_event(HTTP2Event::FailedDecompression);
@@ -849,7 +913,7 @@ impl HTTP2State {
         return AppLayerResult::ok();
     }
 
-    fn parse_ts(&mut self, mut input: &[u8]) -> AppLayerResult {
+    fn parse_ts(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult {
         //very first : skip magic
         let mut magic_consumed = 0;
         if self.progress < HTTP2ConnectionState::Http2StateMagicDone {
@@ -889,7 +953,7 @@ impl HTTP2State {
         }
 
         //then parse all we can
-        let r = self.parse_frames(input, il, STREAM_TOSERVER);
+        let r = self.parse_frames(input, il, Direction::ToServer, flow);
         if r.status == 1 {
             //adds bytes consumed by banner to incomplete result
             return AppLayerResult::incomplete(r.consumed + magic_consumed as u32, r.needed);
@@ -898,7 +962,7 @@ impl HTTP2State {
         }
     }
 
-    fn parse_tc(&mut self, mut input: &[u8]) -> AppLayerResult {
+    fn parse_tc(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult {
         //first consume frame bytes
         let il = input.len();
         if self.response_frame_size > 0 {
@@ -913,34 +977,12 @@ impl HTTP2State {
             }
         }
         //then parse all we can
-        return self.parse_frames(input, il, STREAM_TOCLIENT);
-    }
-
-    fn tx_iterator(
-        &mut self, min_tx_id: u64, state: &mut u64,
-    ) -> Option<(&HTTP2Transaction, u64, bool)> {
-        let mut index = *state as usize;
-        let len = self.transactions.len();
-
-        while index < len {
-            let tx = &self.transactions[index];
-            if tx.tx_id < min_tx_id + 1 {
-                index += 1;
-                continue;
-            }
-            *state = index as u64;
-            return Some((tx, tx.tx_id - 1, (len - index) > 1));
-        }
-
-        return None;
+        return self.parse_frames(input, il, Direction::ToClient, flow);
     }
 }
 
 // C exports.
 
-export_tx_get_detect_state!(rs_http2_tx_get_detect_state, HTTP2Transaction);
-export_tx_set_detect_state!(rs_http2_tx_set_detect_state, HTTP2Transaction);
-
 export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction);
 
 /// C entry point for a probing parser.
@@ -948,7 +990,7 @@ export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction);
 pub unsafe extern "C" fn rs_http2_probing_parser_tc(
     _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
 ) -> AppProto {
-    if input != std::ptr::null_mut() {
+    if !input.is_null() {
         let slice = build_slice!(input, input_len as usize);
         match parser::http2_parse_frame_header(slice) {
             Ok((_, header)) => {
@@ -965,7 +1007,7 @@ pub unsafe extern "C" fn rs_http2_probing_parser_tc(
                 return ALPROTO_UNKNOWN;
             }
             Err(_) => {
-                return ALPROTO_FAILED ;
+                return ALPROTO_FAILED;
             }
         }
     }
@@ -989,7 +1031,7 @@ pub extern "C" fn rs_http2_state_new(
     let state = HTTP2State::new();
     let boxed = Box::new(state);
     let r = Box::into_raw(boxed) as *mut _;
-    if orig_state != std::ptr::null_mut() {
+    if !orig_state.is_null() {
         //we could check ALPROTO_HTTP1 == orig_proto
         unsafe {
             HTTP2MimicHttp1Request(orig_state, r);
@@ -1018,9 +1060,9 @@ pub unsafe extern "C" fn rs_http2_parse_ts(
     let state = cast_pointer!(state, HTTP2State);
     let buf = build_slice!(input, input_len as usize);
 
-    state.files.flags_ts = FileFlowToFlags(flow, STREAM_TOSERVER);
+    state.files.flags_ts = FileFlowToFlags(flow, Direction::ToServer.into());
     state.files.flags_ts = state.files.flags_ts | FILE_USE_DETECT;
-    return state.parse_ts(buf);
+    return state.parse_ts(buf, flow);
 }
 
 #[no_mangle]
@@ -1030,9 +1072,9 @@ pub unsafe extern "C" fn rs_http2_parse_tc(
 ) -> AppLayerResult {
     let state = cast_pointer!(state, HTTP2State);
     let buf = build_slice!(input, input_len as usize);
-    state.files.flags_tc = FileFlowToFlags(flow, STREAM_TOCLIENT);
+    state.files.flags_tc = FileFlowToFlags(flow, Direction::ToClient.into());
     state.files.flags_tc = state.files.flags_tc | FILE_USE_DETECT;
-    return state.parse_tc(buf);
+    return state.parse_tc(buf, flow);
 }
 
 #[no_mangle]
@@ -1057,7 +1099,9 @@ pub unsafe extern "C" fn rs_http2_state_get_tx_count(state: *mut std::os::raw::c
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn rs_http2_tx_get_state(tx: *mut std::os::raw::c_void) -> HTTP2TransactionState {
+pub unsafe extern "C" fn rs_http2_tx_get_state(
+    tx: *mut std::os::raw::c_void,
+) -> HTTP2TransactionState {
     let tx = cast_pointer!(tx, HTTP2Transaction);
     return tx.state;
 }
@@ -1077,30 +1121,12 @@ pub unsafe extern "C" fn rs_http2_state_get_events(
     return tx.events;
 }
 
-#[no_mangle]
-pub unsafe extern "C" fn rs_http2_state_get_tx_iterator(
-    _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64,
-    _max_tx_id: u64, istate: &mut u64,
-) -> applayer::AppLayerGetTxIterTuple {
-    let state = cast_pointer!(state, HTTP2State);
-    match state.tx_iterator(min_tx_id, istate) {
-        Some((tx, out_tx_id, has_next)) => {
-            let c_tx = tx as *const _ as *mut _;
-            let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next);
-            return ires;
-        }
-        None => {
-            return applayer::AppLayerGetTxIterTuple::not_found();
-        }
-    }
-}
-
 #[no_mangle]
 pub unsafe extern "C" fn rs_http2_getfiles(
     state: *mut std::os::raw::c_void, direction: u8,
 ) -> *mut FileContainer {
     let state = cast_pointer!(state, HTTP2State);
-    if direction == STREAM_TOCLIENT {
+    if direction == Direction::ToClient.into() {
         &mut state.files.files_tc as *mut FileContainer
     } else {
         &mut state.files.files_ts as *mut FileContainer
@@ -1131,15 +1157,13 @@ pub unsafe extern "C" fn rs_http2_register_parser() {
         tx_comp_st_ts: HTTP2TransactionState::HTTP2StateClosed as i32,
         tx_comp_st_tc: HTTP2TransactionState::HTTP2StateClosed as i32,
         tx_get_progress: rs_http2_tx_get_alstate_progress,
-        get_de_state: rs_http2_tx_get_detect_state,
-        set_de_state: rs_http2_tx_set_detect_state,
         get_events: Some(rs_http2_state_get_events),
         get_eventinfo: Some(HTTP2Event::get_event_info),
         get_eventinfo_byid: Some(HTTP2Event::get_event_info_by_id),
         localstorage_new: None,
         localstorage_free: None,
         get_files: Some(rs_http2_getfiles),
-        get_tx_iterator: Some(rs_http2_state_get_tx_iterator),
+        get_tx_iterator: Some(applayer::state_get_tx_iterator::<HTTP2State, HTTP2Transaction>),
         get_tx_data: rs_http2_get_tx_data,
         apply_tx_config: None,
         flags: 0,