]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
http2: keep track of dynamic headers table size
authorPhilippe Antoine <contact@catenacyber.fr>
Wed, 30 Sep 2020 21:29:36 +0000 (23:29 +0200)
committerVictor Julien <victor@inliniac.net>
Sat, 3 Oct 2020 17:52:53 +0000 (19:52 +0200)
And evict entries accordingly to maximum size

rust/src/http2/http2.rs
rust/src/http2/parser.rs

index 86218f923e4e5fa0ead834d9aa52127593cca793..c7b55c3f60f918629f0ae107f0965e4e15df2886 100644 (file)
@@ -58,6 +58,8 @@ const HTTP2_FRAME_GOAWAY_LEN: usize = 4;
 const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4;
 const HTTP2_FRAME_PRIORITY_LEN: usize = 1;
 const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4;
+//TODO make this configurable
+pub const HTTP2_MAX_TABLESIZE: u32 = 0x10000; // 65536
 
 #[repr(u8)]
 #[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
@@ -271,12 +273,30 @@ impl HTTP2Event {
     }
 }
 
+pub struct HTTP2DynTable {
+    pub table: Vec<parser::HTTP2FrameHeaderBlock>,
+    pub current_size: usize,
+    pub max_size: usize,
+    pub overflow: u8,
+}
+
+impl HTTP2DynTable {
+    pub fn new() -> Self {
+        Self {
+            table: Vec::with_capacity(64),
+            current_size: 0,
+            max_size: 4096, //default value
+            overflow: 0,
+        }
+    }
+}
+
 pub struct HTTP2State {
     tx_id: u64,
     request_frame_size: u32,
     response_frame_size: u32,
-    dynamic_headers_ts: Vec<parser::HTTP2FrameHeaderBlock>,
-    dynamic_headers_tc: Vec<parser::HTTP2FrameHeaderBlock>,
+    dynamic_headers_ts: HTTP2DynTable,
+    dynamic_headers_tc: HTTP2DynTable,
     transactions: Vec<HTTP2Transaction>,
     progress: HTTP2ConnectionState,
     pub files: HTTP2Files,
@@ -291,8 +311,8 @@ impl HTTP2State {
             // the headers are encoded on one byte
             // with a fixed number of static headers, and
             // a variable number of dynamic headers
-            dynamic_headers_ts: Vec::with_capacity(256 - parser::HTTP2_STATIC_HEADERS_NUMBER),
-            dynamic_headers_tc: Vec::with_capacity(256 - parser::HTTP2_STATIC_HEADERS_NUMBER),
+            dynamic_headers_ts: HTTP2DynTable::new(),
+            dynamic_headers_tc: HTTP2DynTable::new(),
             transactions: Vec::new(),
             progress: HTTP2ConnectionState::Http2StateInit,
             files: HTTP2Files::new(),
@@ -444,6 +464,22 @@ impl HTTP2State {
             Some(parser::HTTP2FrameType::SETTINGS) => {
                 match parser::http2_parse_frame_settings(input) {
                     Ok((_, set)) => {
+                        for i in 0..set.len() {
+                            if set[i].id == parser::HTTP2SettingsId::SETTINGSHEADERTABLESIZE {
+                                //set for both endpoints ? to be tested
+                                self.dynamic_headers_tc.max_size = set[i].value as usize;
+                                self.dynamic_headers_ts.max_size = set[i].value as usize;
+                                if set[i].value > HTTP2_MAX_TABLESIZE {
+                                    //mark potential overflow
+                                    self.dynamic_headers_tc.overflow = 1;
+                                    self.dynamic_headers_ts.overflow = 1;
+                                } else {
+                                    //reset in case peer set a lower value, to be tested
+                                    self.dynamic_headers_tc.overflow = 0;
+                                    self.dynamic_headers_ts.overflow = 0;
+                                }
+                            }
+                        }
                         //we could set an event on remaining data
                         return HTTP2FrameTypeData::SETTINGS(set);
                     }
index 2555fd63225c0c751eb4322f7097acd95cfcd596..d0e3b3aaa9eee9342587c117d0b99945cd74a658 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 use super::huffman;
+use crate::http2::http2::{HTTP2DynTable, HTTP2_MAX_TABLESIZE};
 use nom::character::complete::digit1;
 use nom::combinator::rest;
 use nom::error::ErrorKind;
@@ -209,9 +210,7 @@ named!(pub http2_parse_headers_priority<HTTP2FrameHeadersPriority>,
 
 pub const HTTP2_STATIC_HEADERS_NUMBER: usize = 61;
 
-fn http2_frame_header_static(
-    n: u64, dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
-) -> Option<HTTP2FrameHeaderBlock> {
+fn http2_frame_header_static(n: u64, dyn_headers: &HTTP2DynTable) -> Option<HTTP2FrameHeaderBlock> {
     let (name, value) = match n {
         1 => (":authority", ""),
         2 => (":method", "GET"),
@@ -292,7 +291,7 @@ fn http2_frame_header_static(
                 error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0,
                 sizeupdate: 0,
             });
-        } else if dyn_headers.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize {
+        } else if dyn_headers.table.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize {
             return Some(HTTP2FrameHeaderBlock {
                 name: Vec::new(),
                 value: Vec::new(),
@@ -300,10 +299,10 @@ fn http2_frame_header_static(
                 sizeupdate: 0,
             });
         } else {
-            let indyn = dyn_headers.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER);
+            let indyn = dyn_headers.table.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER);
             let headcopy = HTTP2FrameHeaderBlock {
-                name: dyn_headers[indyn].name.to_vec(),
-                value: dyn_headers[indyn].value.to_vec(),
+                name: dyn_headers.table[indyn].name.to_vec(),
+                value: dyn_headers.table[indyn].value.to_vec(),
                 error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
                 sizeupdate: 0,
             };
@@ -338,7 +337,7 @@ pub struct HTTP2FrameHeaderBlock {
 }
 
 fn http2_parse_headers_block_indexed<'a>(
-    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
         bits!(
@@ -379,7 +378,7 @@ fn http2_parse_headers_block_string(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
 }
 
 fn http2_parse_headers_block_literal_common<'a>(
-    input: &'a [u8], index: u64, dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], index: u64, dyn_headers: &HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     let (i3, name, error) = if index == 0 {
         match http2_parse_headers_block_string(input) {
@@ -413,7 +412,7 @@ fn http2_parse_headers_block_literal_common<'a>(
 }
 
 fn http2_parse_headers_block_literal_incindex<'a>(
-    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &mut HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
         bits!(
@@ -438,16 +437,28 @@ fn http2_parse_headers_block_literal_incindex<'a>(
                 error: head.error,
                 sizeupdate: 0,
             };
-            dyn_headers.push(headcopy);
-            let mut dynsize = 0;
-            //we may spend less CPU by storing in memory
-            for i in 0..dyn_headers.len() {
-                dynsize += 32 + dyn_headers[i].name.len() + dyn_headers[i].value.len();
-            }
-            //TODO keep track of settings + updates instead of magic default value
-            while dynsize > 4096 {
-                dynsize -= 32 + dyn_headers[0].name.len() + dyn_headers[0].value.len();
-                dyn_headers.remove(0);
+            if head.error == HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess {
+                dyn_headers.current_size += 32 + headcopy.name.len() + headcopy.value.len();
+                //in case of overflow, best effort is to keep first headers
+                if dyn_headers.overflow > 0 {
+                    if dyn_headers.overflow == 1 {
+                        if dyn_headers.current_size <= (HTTP2_MAX_TABLESIZE as usize) {
+                            //overflow had not yet happened
+                            dyn_headers.table.push(headcopy);
+                        } else if dyn_headers.current_size > dyn_headers.max_size {
+                            //overflow happens, we cannot replace evicted headers
+                            dyn_headers.overflow = 2;
+                        }
+                    }
+                } else {
+                    dyn_headers.table.push(headcopy);
+                }
+                while dyn_headers.current_size > dyn_headers.max_size && dyn_headers.table.len() > 0
+                {
+                    dyn_headers.current_size -=
+                        32 + dyn_headers.table[0].name.len() + dyn_headers.table[0].value.len();
+                    dyn_headers.table.remove(0);
+                }
             }
             return Ok((r, head));
         }
@@ -458,7 +469,7 @@ fn http2_parse_headers_block_literal_incindex<'a>(
 }
 
 fn http2_parse_headers_block_literal_noindex<'a>(
-    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
         bits!(
@@ -479,7 +490,7 @@ fn http2_parse_headers_block_literal_noindex<'a>(
 }
 
 fn http2_parse_headers_block_literal_neverindex<'a>(
-    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
         bits!(
@@ -521,7 +532,9 @@ fn http2_parse_var_uint(input: &[u8], value: u64, max: u64) -> IResult<&[u8], u6
     return Ok((i3, varval));
 }
 
-fn http2_parse_headers_block_dynamic_size(input: &[u8]) -> IResult<&[u8], HTTP2FrameHeaderBlock> {
+fn http2_parse_headers_block_dynamic_size<'a>(
+    input: &'a [u8], dyn_headers: &mut HTTP2DynTable,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
         bits!(
             input,
@@ -544,6 +557,15 @@ fn http2_parse_headers_block_dynamic_size(input: &[u8]) -> IResult<&[u8], HTTP2F
             },
         ));
     }
+    if (maxsize2 as usize) < dyn_headers.max_size {
+        dyn_headers.max_size = maxsize2 as usize;
+        //may evict entries
+        while dyn_headers.current_size > dyn_headers.max_size {
+            dyn_headers.current_size -=
+                32 + dyn_headers.table[0].name.len() + dyn_headers.table[0].value.len();
+            dyn_headers.table.remove(0);
+        }
+    }
     return Ok((
         i3,
         HTTP2FrameHeaderBlock {
@@ -556,7 +578,7 @@ fn http2_parse_headers_block_dynamic_size(input: &[u8]) -> IResult<&[u8], HTTP2F
 }
 
 fn http2_parse_headers_block<'a>(
-    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &mut HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
     //caller garantees o have at least one byte
     if input[0] & 0x80 != 0 {
@@ -564,7 +586,7 @@ fn http2_parse_headers_block<'a>(
     } else if input[0] & 0x40 != 0 {
         return http2_parse_headers_block_literal_incindex(input, dyn_headers);
     } else if input[0] & 0x20 != 0 {
-        return http2_parse_headers_block_dynamic_size(input);
+        return http2_parse_headers_block_dynamic_size(input, dyn_headers);
     } else if input[0] & 0x10 != 0 {
         return http2_parse_headers_block_literal_neverindex(input, dyn_headers);
     } else {
@@ -586,7 +608,7 @@ const HTTP2_FLAG_HEADER_PADDED: u8 = 0x8;
 const HTTP2_FLAG_HEADER_PRIORITY: u8 = 0x20;
 
 pub fn http2_parse_frame_headers<'a>(
-    input: &'a [u8], flags: u8, dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameHeaders> {
     let (i2, padlength) = cond!(input, flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)?;
     let (mut i3, priority) = cond!(
@@ -630,7 +652,7 @@ pub struct HTTP2FramePushPromise {
 }
 
 pub fn http2_parse_frame_push_promise<'a>(
-    input: &'a [u8], flags: u8, dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FramePushPromise> {
     let (i2, padlength) = cond!(input, flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)?;
     let (mut i3, stream_id) = bits!(i2, tuple!(take_bits!(1u8), take_bits!(31u32)))?;
@@ -668,7 +690,7 @@ pub struct HTTP2FrameContinuation {
 }
 
 pub fn http2_parse_frame_continuation<'a>(
-    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+    input: &'a [u8], dyn_headers: &mut HTTP2DynTable,
 ) -> IResult<&'a [u8], HTTP2FrameContinuation> {
     let mut i3 = input;
     let mut blocks = Vec::new();
@@ -897,8 +919,7 @@ mod tests {
     #[test]
     fn test_http2_parse_header() {
         let buf0: &[u8] = &[0x82];
-        let mut dynh: Vec<HTTP2FrameHeaderBlock> =
-            Vec::with_capacity(255 - HTTP2_STATIC_HEADERS_NUMBER);
+        let mut dynh = HTTP2DynTable::new();
         let r0 = http2_parse_headers_block(buf0, &mut dynh);
         match r0 {
             Ok((remainder, hd)) => {
@@ -924,7 +945,7 @@ mod tests {
                 assert_eq!(hd.value, "*/*".as_bytes().to_vec());
                 // And we should have no bytes left.
                 assert_eq!(remainder.len(), 0);
-                assert_eq!(dynh.len(), 1);
+                assert_eq!(dynh.table.len(), 1);
             }
             Err(Err::Incomplete(_)) => {
                 panic!("Result should not have been incomplete.");
@@ -944,7 +965,7 @@ mod tests {
                 assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec());
                 // And we should have no bytes left.
                 assert_eq!(remainder.len(), 0);
-                assert_eq!(dynh.len(), 2);
+                assert_eq!(dynh.table.len(), 2);
             }
             Err(Err::Incomplete(_)) => {
                 panic!("Result should not have been incomplete.");
@@ -962,7 +983,7 @@ mod tests {
                 assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec());
                 // And we should have no bytes left.
                 assert_eq!(remainder.len(), 0);
-                assert_eq!(dynh.len(), 2);
+                assert_eq!(dynh.table.len(), 2);
             }
             Err(Err::Incomplete(_)) => {
                 panic!("Result should not have been incomplete.");
@@ -977,7 +998,7 @@ mod tests {
             Ok((remainder, hd)) => {
                 assert_eq!(hd.error, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0);
                 assert_eq!(remainder.len(), 0);
-                assert_eq!(dynh.len(), 2);
+                assert_eq!(dynh.table.len(), 2);
             }
             Err(Err::Incomplete(_)) => {
                 panic!("Result should not have been incomplete.");
@@ -998,7 +1019,7 @@ mod tests {
                 assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec());
                 // And we should have no bytes left.
                 assert_eq!(remainder.len(), 0);
-                assert_eq!(dynh.len(), 2);
+                assert_eq!(dynh.table.len(), 2);
             }
             Err(Err::Incomplete(_)) => {
                 panic!("Result should not have been incomplete.");