From 1422b18a996e76aa67c08cf2c67b0b9b84d9c1b7 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Mon, 13 Jul 2020 17:07:20 +0200 Subject: [PATCH] http2: initial support --- rules/http2-events.rules | 15 + rust/src/filecontainer.rs | 5 + rust/src/filetracker.rs | 2 +- rust/src/http2/detect.rs | 542 ++++++++++++++++ rust/src/http2/files.rs | 51 ++ rust/src/http2/http2.rs | 1100 ++++++++++++++++++++++++++++++++ rust/src/http2/huffman.rs | 504 +++++++++++++++ rust/src/http2/logger.rs | 184 ++++++ rust/src/http2/mod.rs | 23 + rust/src/http2/parser.rs | 1155 ++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 1 + src/Makefile.am | 3 + src/alert-prelude.c | 23 + src/app-layer-http2.c | 71 +++ src/app-layer-http2.h | 29 + src/app-layer-parser.c | 2 + src/app-layer-protos.c | 4 + src/app-layer-protos.h | 1 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 9 + src/detect-file-data.c | 15 +- src/detect-filemagic.c | 4 +- src/detect-http2.c | 984 +++++++++++++++++++++++++++++ src/detect-http2.h | 29 + src/output-json-alert.c | 21 + src/output-json-file.c | 10 + src/output-json-http2.c | 251 ++++++++ src/output-json-http2.h | 30 + src/output.c | 2 + src/suricata-common.h | 1 + src/tests/detect-http2.c | 161 +++++ src/util-error.c | 1 + src/util-error.h | 1 + src/util-profiling.c | 1 + suricata.yaml.in | 3 + 35 files changed, 5236 insertions(+), 4 deletions(-) create mode 100644 rules/http2-events.rules create mode 100644 rust/src/http2/detect.rs create mode 100644 rust/src/http2/files.rs create mode 100644 rust/src/http2/http2.rs create mode 100644 rust/src/http2/huffman.rs create mode 100644 rust/src/http2/logger.rs create mode 100644 rust/src/http2/mod.rs create mode 100644 rust/src/http2/parser.rs create mode 100644 src/app-layer-http2.c create mode 100644 src/app-layer-http2.h create mode 100644 src/detect-http2.c create mode 100644 src/detect-http2.h create mode 100644 src/output-json-http2.c create mode 100644 src/output-json-http2.h create mode 100644 src/tests/detect-http2.c diff --git a/rules/http2-events.rules b/rules/http2-events.rules new file mode 100644 index 0000000000..9429edb139 --- /dev/null +++ b/rules/http2-events.rules @@ -0,0 +1,15 @@ +# HTTP2 app layer event rules +# +# SID's fall in the 2290000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer +# +# These sigs fire at most once per connection. +# + +alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame header"; flow:established; app-layer-event:http2.invalid_frame_header; classtype:protocol-command-decode; sid:2290000; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid client magic"; flow:established; app-layer-event:http2.invalid_client_magic; classtype:protocol-command-decode; sid:2290001; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame data"; flow:established; app-layer-event:http2.invalid_frame_data; classtype:protocol-command-decode; sid:2290002; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid header"; flow:established; app-layer-event:http2.invalid_header; classtype:protocol-command-decode; sid:2290003; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame length"; flow:established; app-layer-event:http2.invalid_frame_length; classtype:protocol-command-decode; sid:2290004; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 header frame with extra data"; flow:established; app-layer-event:http2.extra_header_data; classtype:protocol-command-decode; sid:2290005; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 too long frame data"; flow:established; app-layer-event:http2.long_frame_data; classtype:protocol-command-decode; sid:2290006; rev:1;) +alert http2 any any -> any any (msg:"SURICATA HTTP2 stream identifier reuse"; flow:established; app-layer-event:http2.stream_id_reuse; classtype:protocol-command-decode; sid:2290007; rev:1;) diff --git a/rust/src/filecontainer.rs b/rust/src/filecontainer.rs index 38520cfa9e..6f67d88b94 100644 --- a/rust/src/filecontainer.rs +++ b/rust/src/filecontainer.rs @@ -21,6 +21,11 @@ use std::os::raw::{c_void}; use crate::log::*; use crate::core::*; +// Defined in util-file.h +extern { + pub fn FileFlowToFlags(flow: *const Flow, flags: u8) -> u16; +} + pub struct File; #[repr(C)] #[derive(Debug)] diff --git a/rust/src/filetracker.rs b/rust/src/filetracker.rs index ad1402f66c..1f6534196f 100644 --- a/rust/src/filetracker.rs +++ b/rust/src/filetracker.rs @@ -52,7 +52,7 @@ impl FileChunk { #[derive(Debug)] pub struct FileTransferTracker { file_size: u64, - tracked: u64, + pub tracked: u64, cur_ooo: u64, // how many bytes do we have queued from ooo chunks track_id: u32, chunk_left: u32, diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs new file mode 100644 index 0000000000..28f100ddf5 --- /dev/null +++ b/rust/src/http2/detect.rs @@ -0,0 +1,542 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::http2::{HTTP2FrameTypeData, HTTP2Transaction}; +use super::parser; +use crate::core::STREAM_TOSERVER; +use std::ffi::CStr; +use std::mem::transmute; +use std::str::FromStr; + +fn http2_tx_has_frametype( + tx: &mut HTTP2Transaction, direction: u8, value: u8, +) -> std::os::raw::c_int { + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + if tx.frames_ts[i].header.ftype as u8 == value { + return 1; + } + } + } else { + for i in 0..tx.frames_tc.len() { + if tx.frames_tc[i].header.ftype as u8 == value { + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_has_frametype( + tx: *mut std::os::raw::c_void, direction: u8, value: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_has_frametype(tx, direction, value); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_frametype( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok(x) = parser::HTTP2FrameType::from_str(s) { + return x as i32; + } + } + return -1; +} + +fn http2_tx_has_errorcode( + tx: &mut HTTP2Transaction, direction: u8, code: u32, +) -> std::os::raw::c_int { + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match tx.frames_ts[i].data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if goaway.errorcode == code { + return 1; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if rst.errorcode == code { + return 1; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match tx.frames_tc[i].data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if goaway.errorcode as u32 == code { + return 1; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if rst.errorcode as u32 == code { + return 1; + } + } + _ => {} + } + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_has_errorcode( + tx: *mut std::os::raw::c_void, direction: u8, code: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_has_errorcode(tx, direction, code); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_errorcode( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok(x) = parser::HTTP2ErrorCode::from_str(s) { + return x as i32; + } + } + return -1; +} + +fn http2_tx_get_next_priority( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let mut pos = 0 as u32; + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::PRIORITY(prio) => { + if pos == nb { + return prio.weight as i32; + } else { + pos = pos + 1; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(prio) = hd.priority { + if pos == nb { + return prio.weight as i32; + } else { + pos = pos + 1; + } + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::PRIORITY(prio) => { + if pos == nb { + return prio.weight as i32; + } else { + pos = pos + 1; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(prio) = hd.priority { + if pos == nb { + return prio.weight as i32; + } else { + pos = pos + 1; + } + } + } + _ => {} + } + } + } + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_next_priority( + tx: *mut std::os::raw::c_void, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_get_next_priority(tx, direction, nb); +} + +fn http2_tx_get_next_window( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let mut pos = 0 as u32; + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match tx.frames_ts[i].data { + HTTP2FrameTypeData::WINDOWUPDATE(wu) => { + if pos == nb { + return wu.sizeinc as i32; + } else { + pos = pos + 1; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match tx.frames_tc[i].data { + HTTP2FrameTypeData::WINDOWUPDATE(wu) => { + if pos == nb { + return wu.sizeinc as i32; + } else { + pos = pos + 1; + } + } + _ => {} + } + } + } + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_next_window( + tx: *mut std::os::raw::c_void, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_get_next_window(tx, direction, nb); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_settingsid( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok(x) = parser::HTTP2SettingsId::from_str(s) { + return x as i32; + } + } + return -1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_settingsctx_parse( + str: *const std::os::raw::c_char, +) -> *mut std::os::raw::c_void { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = parser::http2_parse_settingsctx(s) { + let boxed = Box::new(ctx); + return transmute(boxed); //unsafe + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_settingsctx_free(ctx: *mut std::os::raw::c_void) { + // Just unbox... + let _ctx: Box = transmute(ctx); +} + +fn http2_detect_settings_match( + set: &Vec, ctx: &parser::DetectHTTP2settingsSigCtx, +) -> std::os::raw::c_int { + for i in 0..set.len() { + if set[i].id == ctx.id { + match &ctx.value { + None => { + return 1; + } + Some(x) => match x.mode { + parser::DetectUintMode::DetectUintModeEqual => { + if set[i].value == x.value { + return 1; + } + } + parser::DetectUintMode::DetectUintModeLt => { + if set[i].value <= x.value { + return 1; + } + } + parser::DetectUintMode::DetectUintModeGt => { + if set[i].value >= x.value { + return 1; + } + } + parser::DetectUintMode::DetectUintModeRange => { + if set[i].value <= x.value && set[i].value >= x.valrange { + return 1; + } + } + }, + } + } + } + return 0; +} + +fn http2_detect_settingsctx_match( + ctx: &mut parser::DetectHTTP2settingsSigCtx, tx: &mut HTTP2Transaction, direction: u8, +) -> std::os::raw::c_int { + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::SETTINGS(set) => { + if http2_detect_settings_match(&set, ctx) != 0 { + return 1; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::SETTINGS(set) => { + if http2_detect_settings_match(&set, ctx) != 0 { + return 1; + } + } + _ => {} + } + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_detect_settingsctx_match( + ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let ctx = cast_pointer!(ctx, parser::DetectHTTP2settingsSigCtx); + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_detect_settingsctx_match(ctx, tx, direction); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u64_parse( + str: *const std::os::raw::c_char, +) -> *mut std::os::raw::c_void { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = parser::detect_parse_u64(s) { + let boxed = Box::new(ctx); + return transmute(boxed); //unsafe + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u64_free(ctx: *mut std::os::raw::c_void) { + // Just unbox... + let _ctx: Box = transmute(ctx); +} + +fn http2_detect_sizeupdate_match( + hd: &parser::HTTP2FrameHeaders, ctx: &parser::DetectU64Data, +) -> std::os::raw::c_int { + for i in 0..hd.blocks.len() { + match ctx.mode { + parser::DetectUintMode::DetectUintModeEqual => { + if hd.blocks[i].sizeupdate == ctx.value + && hd.blocks[i].error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + { + return 1; + } + } + parser::DetectUintMode::DetectUintModeLt => { + if hd.blocks[i].sizeupdate <= ctx.value + && hd.blocks[i].error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + { + return 1; + } + } + parser::DetectUintMode::DetectUintModeGt => { + if hd.blocks[i].sizeupdate >= ctx.value + && hd.blocks[i].error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + { + return 1; + } + } + parser::DetectUintMode::DetectUintModeRange => { + if hd.blocks[i].sizeupdate <= ctx.value + && hd.blocks[i].sizeupdate >= ctx.valrange + && hd.blocks[i].error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + { + return 1; + } + } + } + } + return 0; +} + +fn http2_detect_sizeupdatectx_match( + ctx: &mut parser::DetectU64Data, tx: &mut HTTP2Transaction, direction: u8, +) -> std::os::raw::c_int { + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if http2_detect_sizeupdate_match(&hd, ctx) != 0 { + return 1; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if http2_detect_sizeupdate_match(&hd, ctx) != 0 { + return 1; + } + } + _ => {} + } + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_detect_sizeupdatectx_match( + ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let ctx = cast_pointer!(ctx, parser::DetectU64Data); + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_detect_sizeupdatectx_match(ctx, tx, direction); +} + +//TODOask better syntax between rs_http2_tx_get_header_name in argument +// and rs_http2_detect_sizeupdatectx_match explicitly casting +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header_name( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut pos = 0 as u32; + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if nb < pos + hd.blocks.len() as u32 { + let value = &hd.blocks[(nb - pos) as usize].name; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos = pos + hd.blocks.len() as u32; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if nb < pos + hd.blocks.len() as u32 { + let value = &hd.blocks[(nb - pos) as usize].name; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos = pos + hd.blocks.len() as u32; + } + } + _ => {} + } + } + } + return 0; +} + +fn http2_escape_header(hd: &parser::HTTP2FrameHeaders, i: u32) -> Vec { + //minimum size + 2 for escapes + let normalsize = hd.blocks[i as usize].value.len() + 2 + hd.blocks[i as usize].name.len() + 2; + let mut vec = Vec::with_capacity(normalsize); + for j in 0..hd.blocks[i as usize].name.len() { + vec.push(hd.blocks[i as usize].name[j]); + if hd.blocks[i as usize].name[j] == ':' as u8 { + vec.push(':' as u8); + } + } + vec.push(':' as u8); + vec.push(' ' as u8); + for j in 0..hd.blocks[i as usize].value.len() { + vec.push(hd.blocks[i as usize].value[j]); + if hd.blocks[i as usize].value[j] == ':' as u8 { + vec.push(':' as u8); + } + } + return vec; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut pos = 0 as u32; + if direction & STREAM_TOSERVER != 0 { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if nb < pos + hd.blocks.len() as u32 { + tx.escaped_tmp = http2_escape_header(&hd, nb - pos); + let value = &tx.escaped_tmp; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos = pos + hd.blocks.len() as u32; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if nb < pos + hd.blocks.len() as u32 { + tx.escaped_tmp = http2_escape_header(&hd, nb - pos); + let value = &tx.escaped_tmp; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos = pos + hd.blocks.len() as u32; + } + } + _ => {} + } + } + } + + return 0; +} diff --git a/rust/src/http2/files.rs b/rust/src/http2/files.rs new file mode 100644 index 0000000000..4063604edd --- /dev/null +++ b/rust/src/http2/files.rs @@ -0,0 +1,51 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::core::*; +use crate::filecontainer::*; + +/// Wrapper around Suricata's internal file container logic. +#[derive(Debug)] +pub struct HTTP2Files { + pub files_ts: FileContainer, + pub files_tc: FileContainer, + pub flags_ts: u16, + pub flags_tc: u16, +} + +impl HTTP2Files { + pub fn new() -> HTTP2Files { + HTTP2Files { + files_ts: FileContainer::default(), + files_tc: FileContainer::default(), + flags_ts: 0, + flags_tc: 0, + } + } + pub fn free(&mut self) { + self.files_ts.free(); + self.files_tc.free(); + } + + pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16) { + if direction == STREAM_TOSERVER { + (&mut self.files_ts, self.flags_ts) + } else { + (&mut self.files_tc, self.flags_tc) + } + } +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs new file mode 100644 index 0000000000..3bf4893d5a --- /dev/null +++ b/rust/src/http2/http2.rs @@ -0,0 +1,1100 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::files::*; +use super::parser; +use crate::applayer::{self, *}; +use crate::core::{ + self, AppProto, Flow, SuricataFileContext, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP, + STREAM_TOCLIENT, STREAM_TOSERVER, +}; +use crate::filecontainer::*; +use crate::filetracker::*; +use crate::log::*; +use nom; +use std; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::mem::transmute; + +static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN; + +const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384; +const HTTP2_MAX_HANDLED_FRAME_SIZE: usize = 65536; +const HTTP2_MIN_HANDLED_FRAME_SIZE: usize = 256; + +pub static mut SURICATA_HTTP2_FILE_CONFIG: Option<&'static SuricataFileContext> = None; + +#[no_mangle] +pub extern "C" fn rs_http2_init(context: &'static mut SuricataFileContext) { + unsafe { + SURICATA_HTTP2_FILE_CONFIG = Some(context); + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq)] +pub enum HTTP2ConnectionState { + Http2StateInit = 0, + Http2StateMagicDone = 1, +} + +const HTTP2_FRAME_HEADER_LEN: usize = 9; +const HTTP2_MAGIC_LEN: usize = 24; +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; + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] +pub enum HTTP2FrameUnhandledReason { + UnknownType = 0, + TooLong = 1, + ParsingError = 2, + Incomplete = 3, +} + +impl fmt::Display for HTTP2FrameUnhandledReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug)] +pub struct HTTP2FrameUnhandled { + pub reason: HTTP2FrameUnhandledReason, +} + +pub enum HTTP2FrameTypeData { + PRIORITY(parser::HTTP2FramePriority), + GOAWAY(parser::HTTP2FrameGoAway), + RSTSTREAM(parser::HTTP2FrameRstStream), + SETTINGS(Vec), + WINDOWUPDATE(parser::HTTP2FrameWindowUpdate), + HEADERS(parser::HTTP2FrameHeaders), + PUSHPROMISE(parser::HTTP2FramePushPromise), + CONTINUATION(parser::HTTP2FrameContinuation), + PING, + DATA, + //not a defined frame + UNHANDLED(HTTP2FrameUnhandled), +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] +pub enum HTTP2TransactionState { + HTTP2StateIdle = 0, + HTTP2StateOpen = 1, + HTTP2StateReserved = 2, + HTTP2StateDataClient = 3, + HTTP2StateDataServer = 4, + HTTP2StateHalfClosedClient = 5, + HTTP2StateHalfClosedServer = 6, + HTTP2StateClosed = 7, + //not a RFC-defined state, used for stream 0 frames appyling to the global connection + HTTP2StateGlobal = 8, +} + +pub struct HTTP2Frame { + pub header: parser::HTTP2FrameHeader, + pub data: HTTP2FrameTypeData, +} + +pub struct HTTP2Transaction { + tx_id: u64, + pub stream_id: u32, + state: HTTP2TransactionState, + child_stream_id: u32, + + pub frames_tc: Vec, + pub frames_ts: Vec, + + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, + tx_data: AppLayerTxData, + ft: FileTransferTracker, + + //temporary escaped header for detection + //must be attached to transaction for memory management (be freed at the right time) + pub escaped_tmp: Vec, +} + +impl HTTP2Transaction { + pub fn new() -> HTTP2Transaction { + HTTP2Transaction { + tx_id: 0, + stream_id: 0, + child_stream_id: 0, + state: HTTP2TransactionState::HTTP2StateIdle, + frames_tc: Vec::new(), + frames_ts: Vec::new(), + de_state: None, + events: std::ptr::null_mut(), + tx_data: AppLayerTxData::new(), + ft: FileTransferTracker::new(), + escaped_tmp: Vec::new(), + } + } + + pub fn free(&mut self) { + if self.events != std::ptr::null_mut() { + 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); + } + } + + fn handle_frame( + &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8, + ) { + //handle child_stream_id changes + match data { + HTTP2FrameTypeData::PUSHPROMISE(hs) => { + if dir == STREAM_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; + } + self.state = HTTP2TransactionState::HTTP2StateReserved; + } + } + HTTP2FrameTypeData::CONTINUATION(_) => { + if dir == STREAM_TOCLIENT + && header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0 + { + self.child_stream_id = 0; + } + } + HTTP2FrameTypeData::HEADERS(_) => { + if dir == STREAM_TOCLIENT { + self.child_stream_id = 0; + } + } + HTTP2FrameTypeData::RSTSTREAM(_) => { + self.child_stream_id = 0; + } + _ => {} + } + //handle closing state changes + match data { + HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => { + if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 { + match self.state { + HTTP2TransactionState::HTTP2StateHalfClosedClient => { + if dir == STREAM_TOCLIENT { + self.state = HTTP2TransactionState::HTTP2StateClosed; + } + } + HTTP2TransactionState::HTTP2StateHalfClosedServer => { + if dir == STREAM_TOSERVER { + self.state = HTTP2TransactionState::HTTP2StateClosed; + } + } + // do not revert back to a hald closed state + HTTP2TransactionState::HTTP2StateClosed => {} + HTTP2TransactionState::HTTP2StateGlobal => {} + _ => { + if dir == STREAM_TOCLIENT { + self.state = HTTP2TransactionState::HTTP2StateHalfClosedServer; + } else { + self.state = HTTP2TransactionState::HTTP2StateHalfClosedClient; + } + } + } + } else if header.ftype == parser::HTTP2FrameType::DATA as u8 { + //not end of stream + if dir == STREAM_TOSERVER { + self.state = HTTP2TransactionState::HTTP2StateDataClient; + } else { + self.state = HTTP2TransactionState::HTTP2StateDataServer; + } + } + } + _ => {} + } + } +} + +impl Drop for HTTP2Transaction { + fn drop(&mut self) { + self.free(); + } +} + +#[repr(u32)] +pub enum HTTP2Event { + InvalidFrameHeader = 0, + InvalidClientMagic, + InvalidFrameData, + InvalidHeader, + InvalidFrameLength, + ExtraHeaderData, + LongFrameData, + StreamIdReuse, +} + +impl HTTP2Event { + fn from_i32(value: i32) -> Option { + match value { + 0 => Some(HTTP2Event::InvalidFrameHeader), + 1 => Some(HTTP2Event::InvalidClientMagic), + 2 => Some(HTTP2Event::InvalidFrameData), + 3 => Some(HTTP2Event::InvalidHeader), + 4 => Some(HTTP2Event::InvalidFrameLength), + 5 => Some(HTTP2Event::ExtraHeaderData), + 6 => Some(HTTP2Event::LongFrameData), + 7 => Some(HTTP2Event::StreamIdReuse), + _ => None, + } + } +} + +pub struct HTTP2State { + tx_id: u64, + request_frame_size: u32, + response_frame_size: u32, + dynamic_headers_ts: Vec, + dynamic_headers_tc: Vec, + transactions: Vec, + progress: HTTP2ConnectionState, + pub files: HTTP2Files, +} + +impl HTTP2State { + pub fn new() -> Self { + Self { + tx_id: 0, + request_frame_size: 0, + response_frame_size: 0, + // 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), + transactions: Vec::new(), + progress: HTTP2ConnectionState::Http2StateInit, + files: HTTP2Files::new(), + } + } + + pub fn free(&mut self) { + self.transactions.clear(); + self.files.free(); + } + + fn set_event(&mut self, event: HTTP2Event) { + let len = self.transactions.len(); + if len == 0 { + return; + } + let tx = &mut self.transactions[len - 1]; + let ev = event as u8; + core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, ev); + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&HTTP2Transaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + return Some(tx); + } + } + return None; + } + + fn find_tx_index(&mut self, sid: u32) -> usize { + for i in 0..self.transactions.len() { + //reverse order should be faster + let idx = self.transactions.len() - 1 - i; + if sid == self.transactions[idx].stream_id { + if self.transactions[idx].state == HTTP2TransactionState::HTTP2StateClosed { + self.set_event(HTTP2Event::StreamIdReuse); + return 0; + } + return idx + 1; + } + } + return 0; + } + + fn find_child_stream_id(&mut self, sid: u32) -> u32 { + for i in 0..self.transactions.len() { + //reverse order should be faster + if sid == self.transactions[self.transactions.len() - 1 - i].stream_id { + if self.transactions[self.transactions.len() - 1 - i].child_stream_id > 0 { + return self.transactions[self.transactions.len() - 1 - i].child_stream_id; + } + return sid; + } + } + return sid; + } + + fn create_global_tx(&mut self) -> &mut HTTP2Transaction { + //special transaction with only one frame + //as it affects the global connection, there is no end to it + let mut tx = HTTP2Transaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + tx.state = HTTP2TransactionState::HTTP2StateGlobal; + self.transactions.push(tx); + return self.transactions.last_mut().unwrap(); + } + + fn find_or_create_tx( + &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8, + ) -> &mut HTTP2Transaction { + if header.stream_id == 0 { + return self.create_global_tx(); + } + let sid = match data { + //yes, the right stream_id for Suricata is not the header one + HTTP2FrameTypeData::PUSHPROMISE(hs) => hs.stream_id, + HTTP2FrameTypeData::CONTINUATION(_) => { + if dir == STREAM_TOCLIENT { + //continuation of a push promise + self.find_child_stream_id(header.stream_id) + } else { + header.stream_id + } + } + _ => header.stream_id, + }; + let index = self.find_tx_index(sid); + if index > 0 { + return &mut self.transactions[index - 1]; + } else { + let mut tx = HTTP2Transaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + tx.stream_id = sid; + tx.state = HTTP2TransactionState::HTTP2StateOpen; + self.transactions.push(tx); + return self.transactions.last_mut().unwrap(); + } + } + + fn parse_frame_data( + &mut self, ftype: u8, input: &[u8], complete: bool, hflags: u8, dir: u8, + ) -> HTTP2FrameTypeData { + match num::FromPrimitive::from_u8(ftype) { + Some(parser::HTTP2FrameType::GOAWAY) => { + if input.len() < HTTP2_FRAME_GOAWAY_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } + match parser::http2_parse_frame_goaway(input) { + Ok((_, goaway)) => { + return HTTP2FrameTypeData::GOAWAY(goaway); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::SETTINGS) => { + match parser::http2_parse_frame_settings(input) { + Ok((_, set)) => { + //we could set an event on remaining data + return HTTP2FrameTypeData::SETTINGS(set); + } + Err(nom::Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::RSTSTREAM) => { + if input.len() != HTTP2_FRAME_RSTSTREAM_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_rststream(input) { + Ok((_, rst)) => { + return HTTP2FrameTypeData::RSTSTREAM(rst); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::PRIORITY) => { + if input.len() != HTTP2_FRAME_PRIORITY_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_priority(input) { + Ok((_, priority)) => { + return HTTP2FrameTypeData::PRIORITY(priority); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::WINDOWUPDATE) => { + if input.len() != HTTP2_FRAME_WINDOWUPDATE_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_windowupdate(input) { + Ok((_, wu)) => { + return HTTP2FrameTypeData::WINDOWUPDATE(wu); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::PUSHPROMISE) => { + let dyn_headers = if dir == STREAM_TOCLIENT { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_push_promise(input, hflags, dyn_headers) { + Ok((_, hs)) => { + for i in 0..hs.blocks.len() { + if hs.blocks[i].error + >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError + { + self.set_event(HTTP2Event::InvalidHeader); + } + } + return HTTP2FrameTypeData::PUSHPROMISE(hs); + } + Err(nom::Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::DATA) => { + return HTTP2FrameTypeData::DATA; + } + Some(parser::HTTP2FrameType::CONTINUATION) => { + let dyn_headers = if dir == STREAM_TOCLIENT { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_continuation(input, dyn_headers) { + Ok((_, hs)) => { + for i in 0..hs.blocks.len() { + if hs.blocks[i].error + >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError + { + self.set_event(HTTP2Event::InvalidHeader); + } + } + return HTTP2FrameTypeData::CONTINUATION(hs); + } + Err(nom::Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::HEADERS) => { + let dyn_headers = if dir == STREAM_TOCLIENT { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_headers(input, hflags, dyn_headers) { + Ok((hrem, hs)) => { + for i in 0..hs.blocks.len() { + if hs.blocks[i].error + >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError + { + self.set_event(HTTP2Event::InvalidHeader); + } + } + if hrem.len() > 0 { + SCLogDebug!("Remaining data for HTTP2 headers"); + self.set_event(HTTP2Event::ExtraHeaderData); + } + return HTTP2FrameTypeData::HEADERS(hs); + } + Err(nom::Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::PING) => { + return HTTP2FrameTypeData::PING; + } + _ => { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::UnknownType, + }); + } + } + } + + fn stream_data(&mut self, dir: u8, input: &[u8], over: bool, txid: u64) { + match unsafe { SURICATA_HTTP2_FILE_CONFIG } { + Some(sfcm) => { + for tx in &mut self.transactions { + if tx.tx_id == txid { + let xid: u32 = tx.tx_id as u32; + let (files, flags) = self.files.get(dir); + tx.ft.tx_id = tx.tx_id; + tx.ft.new_chunk( + sfcm, + files, + flags, + b"", + input, + tx.ft.tracked, //offset = append + input.len() as u32, + 0, + over, + &xid, + ); + break; + } + } + } + None => panic!("BUG"), + } + } + + fn parse_frames(&mut self, mut input: &[u8], il: usize, dir: u8) -> AppLayerResult { + while input.len() > 0 { + match parser::http2_parse_frame_header(input) { + Ok((rem, head)) => { + let hl = head.length as usize; + + //we check for completeness first + if rem.len() < hl { + //but limit ourselves so as not to exhaust memory + if hl < HTTP2_MAX_HANDLED_FRAME_SIZE { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + hl) as u32, + ); + } else if rem.len() < HTTP2_MIN_HANDLED_FRAME_SIZE { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + HTTP2_MIN_HANDLED_FRAME_SIZE) as u32, + ); + } else { + self.set_event(HTTP2Event::LongFrameData); + self.request_frame_size = head.length - (rem.len() as u32); + } + } + + //get a safe length for the buffer + let (hlsafe, complete) = if rem.len() < hl { + (rem.len(), false) + } else { + (hl, true) + }; + + if head.length == 0 && head.ftype == parser::HTTP2FrameType::SETTINGS as u8 { + input = &rem[hlsafe..]; + continue; + } + let txdata = self.parse_frame_data( + head.ftype, + &rem[..hlsafe], + complete, + head.flags, + dir, + ); + + let tx = self.find_or_create_tx(&head, &txdata, dir); + tx.handle_frame(&head, &txdata, dir); + let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; + let txid = tx.tx_id; + let ftype = head.ftype; + if dir == STREAM_TOSERVER { + tx.frames_ts.push(HTTP2Frame { + header: head, + data: txdata, + }); + } else { + tx.frames_tc.push(HTTP2Frame { + header: head, + data: txdata, + }); + } + if ftype == parser::HTTP2FrameType::DATA as u8 { + self.stream_data(dir, &rem[..hlsafe], over, txid); + } + input = &rem[hlsafe..]; + } + Err(nom::Err::Incomplete(_)) => { + //we may have consumed data from previous records + return AppLayerResult::incomplete( + (il - input.len()) as u32, + HTTP2_FRAME_HEADER_LEN as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return AppLayerResult::err(); + } + } + } + return AppLayerResult::ok(); + } + + fn parse_ts(&mut self, mut input: &[u8]) -> AppLayerResult { + //very first : skip magic + if self.progress < HTTP2ConnectionState::Http2StateMagicDone { + //skip magic + if input.len() >= HTTP2_MAGIC_LEN { + //skip magic + match std::str::from_utf8(&input[..HTTP2_MAGIC_LEN]) { + Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => { + input = &input[HTTP2_MAGIC_LEN..]; + } + Ok(&_) => { + self.set_event(HTTP2Event::InvalidClientMagic); + } + Err(_) => { + return AppLayerResult::err(); + } + } + self.progress = HTTP2ConnectionState::Http2StateMagicDone; + } else { + //still more buffer + return AppLayerResult::incomplete(0 as u32, HTTP2_MAGIC_LEN as u32); + } + } + //first consume frame bytes + let il = input.len(); + if self.request_frame_size > 0 { + let ilen = input.len() as u32; + if self.request_frame_size >= ilen { + self.request_frame_size -= ilen; + return AppLayerResult::ok(); + } else { + let start = self.request_frame_size as usize; + input = &input[start..]; + self.request_frame_size = 0; + } + } + + //then parse all we can + return self.parse_frames(input, il, STREAM_TOSERVER); + } + + fn parse_tc(&mut self, mut input: &[u8]) -> AppLayerResult { + //first consume frame bytes + let il = input.len(); + if self.response_frame_size > 0 { + let ilen = input.len() as u32; + if self.response_frame_size >= ilen { + self.response_frame_size -= ilen; + return AppLayerResult::ok(); + } else { + let start = self.response_frame_size as usize; + input = &input[start..]; + self.response_frame_size = 0; + } + } + //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; + } +} + +// 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); + +//TODOnext connection upgrade from HTTP1 cf SMTP STARTTLS +/// C entry point for a probing parser. +#[no_mangle] +pub 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() { + let slice = build_slice!(input, input_len as usize); + match parser::http2_parse_frame_header(slice) { + Ok((_, header)) => { + if header.reserved != 0 + || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE + || header.flags & 0xFE != 0 + || header.ftype != parser::HTTP2FrameType::SETTINGS as u8 + { + return unsafe { ALPROTO_FAILED }; + } + return unsafe { ALPROTO_HTTP2 }; + } + Err(nom::Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + } + Err(_) => { + return unsafe { ALPROTO_FAILED }; + } + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_new() -> *mut std::os::raw::c_void { + let state = HTTP2State::new(); + let boxed = Box::new(state); + return unsafe { transmute(boxed) }; +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + let mut state: Box = unsafe { transmute(state) }; + state.free(); +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, HTTP2State); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_http2_parse_ts( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + let state = cast_pointer!(state, HTTP2State); + let buf = build_slice!(input, input_len as usize); + + state.files.flags_ts = unsafe { FileFlowToFlags(flow, STREAM_TOSERVER) }; + return state.parse_ts(buf); +} + +#[no_mangle] +pub extern "C" fn rs_http2_parse_tc( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, +) -> AppLayerResult { + let state = cast_pointer!(state, HTTP2State); + let buf = build_slice!(input, input_len as usize); + state.files.flags_tc = unsafe { FileFlowToFlags(flow, STREAM_TOCLIENT) }; + return state.parse_tc(buf); +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, HTTP2State); + match state.get_tx(tx_id) { + Some(tx) => { + return unsafe { transmute(tx) }; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, HTTP2State); + return state.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { + return HTTP2TransactionState::HTTP2StateClosed as i32; +} + +#[no_mangle] +pub 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; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + return rs_http2_tx_get_state(tx) as i32; +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_get_events( + tx: *mut std::os::raw::c_void, +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, HTTP2Transaction); + return tx.events; +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_get_event_info( + event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int, + event_type: *mut core::AppLayerEventType, +) -> std::os::raw::c_int { + if event_name == std::ptr::null() { + return -1; + } + let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) }; + let event = match c_event_name.to_str() { + Ok(s) => { + match s { + "invalid_frame_header" => HTTP2Event::InvalidFrameHeader as i32, + "invalid_client_magic" => HTTP2Event::InvalidClientMagic as i32, + "invalid_frame_data" => HTTP2Event::InvalidFrameData as i32, + "invalid_header" => HTTP2Event::InvalidHeader as i32, + "invalid_frame_length" => HTTP2Event::InvalidFrameLength as i32, + "extra_header_data" => HTTP2Event::ExtraHeaderData as i32, + "long_frame_data" => HTTP2Event::LongFrameData as i32, + "stream_id_reuse" => HTTP2Event::StreamIdReuse as i32, + _ => -1, // unknown event + } + } + Err(_) => -1, // UTF-8 conversion failed + }; + unsafe { + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + *event_id = event as std::os::raw::c_int; + }; + 0 +} + +#[no_mangle] +pub extern "C" fn rs_http2_state_get_event_info_by_id( + event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, +) -> i8 { + if let Some(e) = HTTP2Event::from_i32(event_id as i32) { + let estr = match e { + HTTP2Event::InvalidFrameHeader => "invalid_frame_header\0", + HTTP2Event::InvalidClientMagic => "invalid_client_magic\0", + HTTP2Event::InvalidFrameData => "invalid_frame_data\0", + HTTP2Event::InvalidHeader => "invalid_header\0", + HTTP2Event::InvalidFrameLength => "invalid_frame_length\0", + HTTP2Event::ExtraHeaderData => "extra_header_data\0", + HTTP2Event::LongFrameData => "long_frame_data\0", + HTTP2Event::StreamIdReuse => "stream_id_reuse\0", + }; + unsafe { + *event_name = estr.as_ptr() as *const std::os::raw::c_char; + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + }; + 0 + } else { + -1 + } +} +#[no_mangle] +pub 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 = unsafe { transmute(tx) }; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +#[no_mangle] +pub 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 { + &mut state.files.files_tc as *mut FileContainer + } else { + &mut state.files.files_ts as *mut FileContainer + } +} + +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"http2\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_register_parser() { + //TODOend default port + let default_port = CString::new("[3000]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: None, // big magic string should be enough + probe_tc: Some(rs_http2_probing_parser_tc), + min_depth: HTTP2_FRAME_HEADER_LEN as u16, + max_depth: HTTP2_MAGIC_LEN as u16, + state_new: rs_http2_state_new, + state_free: rs_http2_state_free, + tx_free: rs_http2_state_tx_free, + parse_ts: rs_http2_parse_ts, + parse_tc: rs_http2_parse_tc, + get_tx_count: rs_http2_state_get_tx_count, + get_tx: rs_http2_state_get_tx, + tx_get_comp_st: rs_http2_state_progress_completion_status, + 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(rs_http2_state_get_event_info), + get_eventinfo_byid: Some(rs_http2_state_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_data: rs_http2_get_tx_data, + apply_tx_config: None, + flags: 0, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_HTTP2 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust http2 parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for HTTP2."); + } +} diff --git a/rust/src/http2/huffman.rs b/rust/src/http2/huffman.rs new file mode 100644 index 0000000000..05ec03a5f0 --- /dev/null +++ b/rust/src/http2/huffman.rs @@ -0,0 +1,504 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom::error::ErrorKind; +use nom::Err; +use nom::IResult; + +fn http2_huffman_table_len5(n: u32) -> Option { + match n { + 0 => Some(48), + 1 => Some(49), + 2 => Some(50), + 3 => Some(97), + 4 => Some(99), + 5 => Some(101), + 6 => Some(105), + 7 => Some(111), + 8 => Some(115), + 9 => Some(116), + _ => None, + } +} + +named!(http2_decode_huffman_len5<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(5u32), http2_huffman_table_len5) ) +); + +fn http2_huffman_table_len6(n: u32) -> Option { + match n { + 0x14 => Some(32), + 0x15 => Some(37), + 0x16 => Some(45), + 0x17 => Some(46), + 0x18 => Some(47), + 0x19 => Some(51), + 0x1a => Some(52), + 0x1b => Some(53), + 0x1c => Some(54), + 0x1d => Some(55), + 0x1e => Some(56), + 0x1f => Some(57), + 0x20 => Some(61), + 0x21 => Some(65), + 0x22 => Some(95), + 0x23 => Some(98), + 0x24 => Some(100), + 0x25 => Some(102), + 0x26 => Some(103), + 0x27 => Some(104), + 0x28 => Some(108), + 0x29 => Some(109), + 0x2a => Some(110), + 0x2b => Some(112), + 0x2c => Some(114), + 0x2d => Some(117), + _ => None, + } +} + +named!(http2_decode_huffman_len6<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(6u32), http2_huffman_table_len6)) +); + +fn http2_huffman_table_len7(n: u32) -> Option { + match n { + 0x5c => Some(58), + 0x5d => Some(66), + 0x5e => Some(67), + 0x5f => Some(68), + 0x60 => Some(69), + 0x61 => Some(70), + 0x62 => Some(71), + 0x63 => Some(72), + 0x64 => Some(73), + 0x65 => Some(74), + 0x66 => Some(75), + 0x67 => Some(76), + 0x68 => Some(77), + 0x69 => Some(78), + 0x6a => Some(79), + 0x6b => Some(80), + 0x6c => Some(81), + 0x6d => Some(82), + 0x6e => Some(83), + 0x6f => Some(84), + 0x70 => Some(85), + 0x71 => Some(86), + 0x72 => Some(87), + 0x73 => Some(89), + 0x74 => Some(106), + 0x75 => Some(107), + 0x76 => Some(113), + 0x77 => Some(118), + 0x78 => Some(119), + 0x79 => Some(120), + 0x7a => Some(121), + 0x7b => Some(122), + _ => None, + } +} + +named!(http2_decode_huffman_len7<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(7u32), http2_huffman_table_len7)) +); + +fn http2_huffman_table_len8(n: u32) -> Option { + match n { + 0xf8 => Some(38), + 0xf9 => Some(42), + 0xfa => Some(44), + 0xfb => Some(59), + 0xfc => Some(88), + 0xfd => Some(90), + _ => None, + } +} + +named!(http2_decode_huffman_len8<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(8u32), http2_huffman_table_len8)) +); + +fn http2_huffman_table_len10(n: u32) -> Option { + match n { + 0x3f8 => Some(33), + 0x3f9 => Some(34), + 0x3fa => Some(40), + 0x3fb => Some(41), + 0x3fc => Some(63), + _ => None, + } +} + +named!(http2_decode_huffman_len10<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(10u32), http2_huffman_table_len10)) +); + +fn http2_huffman_table_len11(n: u32) -> Option { + match n { + 0x7fa => Some(39), + 0x7fb => Some(43), + 0x7fc => Some(124), + _ => None, + } +} + +named!(http2_decode_huffman_len11<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(11u32), http2_huffman_table_len11)) +); + +fn http2_huffman_table_len12(n: u32) -> Option { + match n { + 0xffa => Some(35), + 0xffb => Some(62), + _ => None, + } +} + +named!(http2_decode_huffman_len12<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(12u32), http2_huffman_table_len12)) +); + +fn http2_huffman_table_len13(n: u32) -> Option { + match n { + 0x1ff8 => Some(0), + 0x1ff9 => Some(36), + 0x1ffa => Some(64), + 0x1ffb => Some(91), + 0x1ffc => Some(93), + 0x1ffd => Some(126), + _ => None, + } +} + +named!(http2_decode_huffman_len13<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(13u32), http2_huffman_table_len13)) +); + +fn http2_huffman_table_len14(n: u32) -> Option { + match n { + 0x3ffc => Some(94), + 0x3ffd => Some(125), + _ => None, + } +} + +named!(http2_decode_huffman_len14<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(14u32), http2_huffman_table_len14)) +); + +fn http2_huffman_table_len15(n: u32) -> Option { + match n { + 0x7ffc => Some(60), + 0x7ffd => Some(96), + 0x7ffe => Some(123), + _ => None, + } +} + +named!(http2_decode_huffman_len15<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(15u32), http2_huffman_table_len15)) +); + +fn http2_huffman_table_len19(n: u32) -> Option { + match n { + 0x7fff0 => Some(92), + 0x7fff1 => Some(195), + 0x7fff2 => Some(208), + _ => None, + } +} + +named!(http2_decode_huffman_len19<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(19u32), http2_huffman_table_len19)) +); + +fn http2_huffman_table_len20(n: u32) -> Option { + match n { + 0xfffe6 => Some(128), + 0xfffe7 => Some(130), + 0xfffe8 => Some(131), + 0xfffe9 => Some(162), + 0xfffea => Some(184), + 0xfffeb => Some(194), + 0xfffec => Some(224), + 0xfffed => Some(226), + _ => None, + } +} + +named!(http2_decode_huffman_len20<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(20u32), http2_huffman_table_len20)) +); + +fn http2_huffman_table_len21(n: u32) -> Option { + match n { + 0x1fffdc => Some(153), + 0x1fffdd => Some(161), + 0x1fffde => Some(167), + 0x1fffdf => Some(172), + 0x1fffe0 => Some(176), + 0x1fffe1 => Some(177), + 0x1fffe2 => Some(179), + 0x1fffe3 => Some(209), + 0x1fffe4 => Some(216), + 0x1fffe5 => Some(217), + 0x1fffe6 => Some(227), + 0x1fffe7 => Some(229), + 0x1fffe8 => Some(230), + _ => None, + } +} + +named!(http2_decode_huffman_len21<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(21u32), http2_huffman_table_len21)) +); + +fn http2_huffman_table_len22(n: u32) -> Option { + match n { + 0x3fffd2 => Some(129), + 0x3fffd3 => Some(132), + 0x3fffd4 => Some(133), + 0x3fffd5 => Some(134), + 0x3fffd6 => Some(136), + 0x3fffd7 => Some(146), + 0x3fffd8 => Some(154), + 0x3fffd9 => Some(156), + 0x3fffda => Some(160), + 0x3fffdb => Some(163), + 0x3fffdc => Some(164), + 0x3fffdd => Some(169), + 0x3fffde => Some(170), + 0x3fffdf => Some(173), + 0x3fffe0 => Some(178), + 0x3fffe1 => Some(181), + 0x3fffe2 => Some(185), + 0x3fffe3 => Some(186), + 0x3fffe4 => Some(187), + 0x3fffe5 => Some(189), + 0x3fffe6 => Some(190), + 0x3fffe7 => Some(196), + 0x3fffe8 => Some(198), + 0x3fffe9 => Some(228), + 0x3fffea => Some(232), + 0x3fffeb => Some(233), + _ => None, + } +} + +named!(http2_decode_huffman_len22<(&[u8], usize), u8>, +complete!( map_opt!(take_bits!(22u32), http2_huffman_table_len22)) +); + +fn http2_huffman_table_len23(n: u32) -> Option { + match n { + 0x7fffd8 => Some(1), + 0x7fffd9 => Some(135), + 0x7fffda => Some(137), + 0x7fffdb => Some(138), + 0x7fffdc => Some(139), + 0x7fffdd => Some(140), + 0x7fffde => Some(141), + 0x7fffdf => Some(143), + 0x7fffe0 => Some(147), + 0x7fffe1 => Some(149), + 0x7fffe2 => Some(150), + 0x7fffe3 => Some(151), + 0x7fffe4 => Some(152), + 0x7fffe5 => Some(155), + 0x7fffe6 => Some(157), + 0x7fffe7 => Some(158), + 0x7fffe8 => Some(165), + 0x7fffe9 => Some(166), + 0x7fffea => Some(168), + 0x7fffeb => Some(174), + 0x7fffec => Some(175), + 0x7fffed => Some(180), + 0x7fffee => Some(182), + 0x7fffef => Some(183), + 0x7ffff0 => Some(188), + 0x7ffff1 => Some(191), + 0x7ffff2 => Some(197), + 0x7ffff3 => Some(231), + 0x7ffff4 => Some(239), + _ => None, + } +} + +named!(http2_decode_huffman_len23<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(23u32), http2_huffman_table_len23)) +); + +fn http2_huffman_table_len24(n: u32) -> Option { + match n { + 0xffffea => Some(9), + 0xffffeb => Some(142), + 0xffffec => Some(144), + 0xffffed => Some(145), + 0xffffee => Some(148), + 0xffffef => Some(159), + 0xfffff0 => Some(171), + 0xfffff1 => Some(206), + 0xfffff2 => Some(215), + 0xfffff3 => Some(225), + 0xfffff4 => Some(236), + 0xfffff5 => Some(237), + _ => None, + } +} + +named!(http2_decode_huffman_len24<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(24u32), http2_huffman_table_len24)) +); + +fn http2_huffman_table_len25(n: u32) -> Option { + match n { + 0x1ffffec => Some(199), + 0x1ffffed => Some(207), + 0x1ffffee => Some(234), + 0x1ffffef => Some(235), + _ => None, + } +} + +named!(http2_decode_huffman_len25<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(25u32), http2_huffman_table_len25)) +); + +fn http2_huffman_table_len26(n: u32) -> Option { + match n { + 0x3ffffe0 => Some(192), + 0x3ffffe1 => Some(193), + 0x3ffffe2 => Some(200), + 0x3ffffe3 => Some(201), + 0x3ffffe4 => Some(202), + 0x3ffffe5 => Some(205), + 0x3ffffe6 => Some(210), + 0x3ffffe7 => Some(213), + 0x3ffffe8 => Some(218), + 0x3ffffe9 => Some(219), + 0x3ffffea => Some(238), + 0x3ffffeb => Some(240), + 0x3ffffec => Some(242), + 0x3ffffed => Some(243), + 0x3ffffee => Some(255), + _ => None, + } +} + +named!(http2_decode_huffman_len26<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(26u32), http2_huffman_table_len26)) +); + +fn http2_huffman_table_len27(n: u32) -> Option { + match n { + 0x7ffffde => Some(203), + 0x7ffffdf => Some(204), + 0x7ffffe0 => Some(211), + 0x7ffffe1 => Some(212), + 0x7ffffe2 => Some(214), + 0x7ffffe3 => Some(221), + 0x7ffffe4 => Some(222), + 0x7ffffe5 => Some(223), + 0x7ffffe6 => Some(241), + 0x7ffffe7 => Some(244), + 0x7ffffe8 => Some(245), + 0x7ffffe9 => Some(246), + 0x7ffffea => Some(247), + 0x7ffffeb => Some(248), + 0x7ffffec => Some(250), + 0x7ffffed => Some(251), + 0x7ffffee => Some(252), + 0x7ffffef => Some(253), + 0x7fffff0 => Some(254), + _ => None, + } +} + +named!(http2_decode_huffman_len27<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(27u32), http2_huffman_table_len27)) +); + +fn http2_huffman_table_len28(n: u32) -> Option { + match n { + 0xfffffe2 => Some(2), + 0xfffffe3 => Some(3), + 0xfffffe4 => Some(4), + 0xfffffe5 => Some(5), + 0xfffffe6 => Some(6), + 0xfffffe7 => Some(7), + 0xfffffe8 => Some(8), + 0xfffffe9 => Some(11), + 0xfffffea => Some(12), + 0xfffffeb => Some(14), + 0xfffffec => Some(15), + 0xfffffed => Some(16), + 0xfffffee => Some(17), + 0xfffffef => Some(18), + 0xffffff0 => Some(19), + 0xffffff1 => Some(20), + 0xffffff2 => Some(21), + 0xffffff3 => Some(23), + 0xffffff4 => Some(24), + 0xffffff5 => Some(25), + 0xffffff6 => Some(26), + 0xffffff7 => Some(27), + 0xffffff8 => Some(28), + 0xffffff9 => Some(29), + 0xffffffa => Some(30), + 0xffffffb => Some(31), + 0xffffffc => Some(127), + 0xffffffd => Some(220), + 0xffffffe => Some(249), + _ => None, + } +} + +named!(http2_decode_huffman_len28<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(28u32), http2_huffman_table_len28)) +); + +fn http2_huffman_table_len30(n: u32) -> Option { + match n { + 0x3ffffffc => Some(10), + 0x3ffffffd => Some(13), + 0x3ffffffe => Some(22), + // 0x3fffffff => Some(256), + _ => None, + } +} + +named!(http2_decode_huffman_len30<(&[u8], usize), u8>, + complete!( map_opt!(take_bits!(30u32), http2_huffman_table_len30)) +); + +//hack to end many0 even if some bits are remaining +fn http2_decode_huffman_end(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + return Err(Err::Error((input, ErrorKind::Eof))); +} + +//we could profile and optimize performance here +named!(pub http2_decode_huffman<(&[u8], usize), u8>, + alt!(http2_decode_huffman_len5 | http2_decode_huffman_len6 | http2_decode_huffman_len7 | + http2_decode_huffman_len8 | http2_decode_huffman_len10 | http2_decode_huffman_len11 | + http2_decode_huffman_len12 | http2_decode_huffman_len13 | http2_decode_huffman_len14 | + http2_decode_huffman_len15 | http2_decode_huffman_len19 | http2_decode_huffman_len20 | + http2_decode_huffman_len21 | http2_decode_huffman_len22 | http2_decode_huffman_len23 | + http2_decode_huffman_len24 | http2_decode_huffman_len25 | http2_decode_huffman_len26 | + http2_decode_huffman_len27 | http2_decode_huffman_len28 | http2_decode_huffman_len30 | + http2_decode_huffman_end) +); diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs new file mode 100644 index 0000000000..5a43e629d5 --- /dev/null +++ b/rust/src/http2/logger.rs @@ -0,0 +1,184 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction}; +use super::parser; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; + +fn log_http2_headers( + blocks: &Vec, js: &mut JsonBuilder, +) -> Result<(), JsonError> { + for j in 0..blocks.len() { + js.start_object()?; + match blocks[j].error { + parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess => { + js.set_string_from_bytes("name", &blocks[j].name)?; + js.set_string_from_bytes("value", &blocks[j].value)?; + } + parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate => { + js.set_uint("table_size_update", blocks[j].sizeupdate)?; + } + _ => { + js.set_string("error", &blocks[j].error.to_string())?; + } + } + js.close()?; + } + return Ok(()); +} + +fn log_http2_frames(frames: &Vec, js: &mut JsonBuilder) -> Result { + let mut has_settings = false; + for i in 0..frames.len() { + if let HTTP2FrameTypeData::SETTINGS(set) = &frames[i].data { + if !has_settings { + js.open_array("settings")?; + has_settings = true; + } + for j in 0..set.len() { + js.start_object()?; + js.set_string("settings_id", &set[j].id.to_string())?; + js.set_uint("settings_value", set[j].value as u64)?; + js.close()?; + } + } + } + if has_settings { + js.close()?; + } + + let mut has_headers = false; + for i in 0..frames.len() { + match &frames[i].data { + HTTP2FrameTypeData::HEADERS(hd) => { + if !has_headers { + js.open_array("headers")?; + has_headers = true; + } + log_http2_headers(&hd.blocks, js)?; + } + HTTP2FrameTypeData::PUSHPROMISE(hd) => { + if !has_headers { + js.open_array("headers")?; + has_headers = true; + } + log_http2_headers(&hd.blocks, js)?; + } + HTTP2FrameTypeData::CONTINUATION(hd) => { + if !has_headers { + js.open_array("headers")?; + has_headers = true; + } + log_http2_headers(&hd.blocks, js)?; + } + _ => {} + } + } + if has_headers { + js.close()?; + } + + let mut has_error_code = false; + let mut has_priority = false; + let mut has_multiple = false; + for i in 0..frames.len() { + match &frames[i].data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if !has_error_code { + let errcode: Option = + num::FromPrimitive::from_u32(goaway.errorcode); + match errcode { + Some(errstr) => { + js.set_string("error_code", &errstr.to_string())?; + } + None => { + //use uint32 + js.set_string("error_code", &goaway.errorcode.to_string())?; + } + } + has_error_code = true; + } else if !has_multiple { + js.set_string("has_multiple", "error_code")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if !has_error_code { + let errcode: Option = + num::FromPrimitive::from_u32(rst.errorcode); + match errcode { + Some(errstr) => { + js.set_string("error_code", &errstr.to_string())?; + } + None => { + //use uint32 + js.set_string("error_code", &rst.errorcode.to_string())?; + } + } + has_error_code = true; + } else if !has_multiple { + js.set_string("has_multiple", "error_code")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::PRIORITY(priority) => { + if !has_priority { + js.set_uint("priority", priority.weight as u64)?; + has_priority = true; + } else if !has_multiple { + js.set_string("has_multiple", "priority")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(ref priority) = hd.priority { + if !has_priority { + js.set_uint("priority", priority.weight as u64)?; + has_priority = true; + } else if !has_multiple { + js.set_string("has_multiple", "priority")?; + has_multiple = true; + } + } + } + _ => {} + } + } + return Ok(has_settings || has_headers || has_error_code || has_priority); +} + +fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result { + js.set_uint("stream_id", tx.stream_id as u64)?; + js.open_object("request")?; + let has_request = log_http2_frames(&tx.frames_ts, js)?; + js.close()?; + js.open_object("response")?; + let has_response = log_http2_frames(&tx.frames_tc, js)?; + js.close()?; + + return Ok(has_request || has_response); +} + +#[no_mangle] +pub extern "C" fn rs_http2_log_json(tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool { + let tx = cast_pointer!(tx, HTTP2Transaction); + if let Ok(x) = log_http2(tx, js) { + return x; + } + return false; +} diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs new file mode 100644 index 0000000000..ad86bdf291 --- /dev/null +++ b/rust/src/http2/mod.rs @@ -0,0 +1,23 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +pub mod detect; +pub mod files; +pub mod http2; +mod huffman; +pub mod logger; +mod parser; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs new file mode 100644 index 0000000000..d357e273c3 --- /dev/null +++ b/rust/src/http2/parser.rs @@ -0,0 +1,1155 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::huffman; +use nom::character::complete::digit1; +use nom::combinator::rest; +use nom::error::ErrorKind; +use nom::number::streaming::{be_u16, be_u32, be_u8}; +use nom::Err; +use nom::IResult; +use std::fmt; +use std::str::FromStr; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)] +pub enum HTTP2FrameType { + DATA = 0, + HEADERS = 1, + PRIORITY = 2, + RSTSTREAM = 3, + SETTINGS = 4, + PUSHPROMISE = 5, + PING = 6, + GOAWAY = 7, + WINDOWUPDATE = 8, + CONTINUATION = 9, +} + +impl fmt::Display for HTTP2FrameType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2FrameType { + type Err = String; + + fn from_str(s: &str) -> Result { + let su = s.to_uppercase(); + let su_slice: &str = &*su; + match su_slice { + "DATA" => Ok(HTTP2FrameType::DATA), + "HEADERS" => Ok(HTTP2FrameType::HEADERS), + "PRIORITY" => Ok(HTTP2FrameType::PRIORITY), + "RSTSTREAM" => Ok(HTTP2FrameType::RSTSTREAM), + "SETTINGS" => Ok(HTTP2FrameType::SETTINGS), + "PUSHPROMISE" => Ok(HTTP2FrameType::PUSHPROMISE), + "PING" => Ok(HTTP2FrameType::PING), + "GOAWAY" => Ok(HTTP2FrameType::GOAWAY), + "WINDOWUPDATE" => Ok(HTTP2FrameType::WINDOWUPDATE), + "CONTINUATION" => Ok(HTTP2FrameType::CONTINUATION), + _ => Err(format!("'{}' is not a valid value for HTTP2FrameType", s)), + } + } +} + +#[derive(PartialEq)] +pub struct HTTP2FrameHeader { + //we could add detection on (GOAWAY) additional data + pub length: u32, + pub ftype: u8, + pub flags: u8, + pub reserved: u8, + pub stream_id: u32, +} + +named!(pub http2_parse_frame_header, + do_parse!( + length: bits!( take_bits!(24u32) ) >> + ftype: be_u8 >> + flags: be_u8 >> + stream_id: bits!( tuple!( take_bits!(1u8), + take_bits!(31u32) ) ) >> + (HTTP2FrameHeader{length, ftype, flags, + reserved:stream_id.0, + stream_id:stream_id.1}) + ) +); + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)] +pub enum HTTP2ErrorCode { + NOERROR = 0, + PROTOCOLERROR = 1, + INTERNALERROR = 2, + FLOWCONTROLERROR = 3, + SETTINGSTIMEOUT = 4, + STREAMCLOSED = 5, + FRAMESIZEERROR = 6, + REFUSEDSTREAM = 7, + CANCEL = 8, + COMPRESSIONERROR = 9, + CONNECTERROR = 10, + ENHANCEYOURCALM = 11, + INADEQUATESECURITY = 12, + HTTP11REQUIRED = 13, +} + +impl fmt::Display for HTTP2ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2ErrorCode { + type Err = String; + + fn from_str(s: &str) -> Result { + let su = s.to_uppercase(); + let su_slice: &str = &*su; + match su_slice { + "NO_ERROR" => Ok(HTTP2ErrorCode::NOERROR), + "PROTOCOL_ERROR" => Ok(HTTP2ErrorCode::PROTOCOLERROR), + "FLOW_CONTROL_ERROR" => Ok(HTTP2ErrorCode::FLOWCONTROLERROR), + "SETTINGS_TIMEOUT" => Ok(HTTP2ErrorCode::SETTINGSTIMEOUT), + "STREAM_CLOSED" => Ok(HTTP2ErrorCode::STREAMCLOSED), + "FRAME_SIZE_ERROR" => Ok(HTTP2ErrorCode::FRAMESIZEERROR), + "REFUSED_STREAM" => Ok(HTTP2ErrorCode::REFUSEDSTREAM), + "CANCEL" => Ok(HTTP2ErrorCode::CANCEL), + "COMPRESSION_ERROR" => Ok(HTTP2ErrorCode::COMPRESSIONERROR), + "CONNECT_ERROR" => Ok(HTTP2ErrorCode::CONNECTERROR), + "ENHANCE_YOUR_CALM" => Ok(HTTP2ErrorCode::ENHANCEYOURCALM), + "INADEQUATE_SECURITY" => Ok(HTTP2ErrorCode::INADEQUATESECURITY), + "HTTP_1_1_REQUIRED" => Ok(HTTP2ErrorCode::HTTP11REQUIRED), + _ => Err(format!("'{}' is not a valid value for HTTP2ErrorCode", s)), + } + } +} + +#[derive(Clone, Copy)] +pub struct HTTP2FrameGoAway { + pub errorcode: u32, //HTTP2ErrorCode +} + +named!(pub http2_parse_frame_goaway, + do_parse!( + errorcode: be_u32 >> + (HTTP2FrameGoAway{errorcode}) + ) +); + +#[derive(Clone, Copy)] +pub struct HTTP2FrameRstStream { + pub errorcode: u32, ////HTTP2ErrorCode +} + +named!(pub http2_parse_frame_rststream, + do_parse!( + errorcode: be_u32 >> + (HTTP2FrameRstStream{errorcode}) + ) +); + +#[derive(Clone, Copy)] +pub struct HTTP2FramePriority { + pub weight: u8, +} + +named!(pub http2_parse_frame_priority, + do_parse!( + weight: be_u8 >> + (HTTP2FramePriority{weight}) + ) +); + +#[derive(Clone, Copy)] +pub struct HTTP2FrameWindowUpdate { + pub reserved: u8, + pub sizeinc: u32, +} + +named!(pub http2_parse_frame_windowupdate, + do_parse!( + sizeinc: bits!( tuple!( take_bits!(1u8), + take_bits!(31u32) ) ) >> + (HTTP2FrameWindowUpdate{reserved:sizeinc.0, sizeinc:sizeinc.1}) + ) +); + +#[derive(Clone, Copy)] +pub struct HTTP2FrameHeadersPriority { + pub exclusive: u8, + pub dependency: u32, + pub weight: u8, +} + +named!(pub http2_parse_headers_priority, + do_parse!( + sid: bits!( tuple!( take_bits!(1u8), + take_bits!(31u32) ) ) >> + weight: be_u8 >> + (HTTP2FrameHeadersPriority{exclusive:sid.0, dependency:sid.1, weight}) + ) +); + +pub const HTTP2_STATIC_HEADERS_NUMBER: usize = 61; + +fn http2_frame_header_static( + n: u8, dyn_headers: &Vec, +) -> Option { + let (name, value) = match n { + 1 => (":authority", ""), + 2 => (":method", "GET"), + 3 => (":method", "POST"), + 4 => (":path", "/"), + 5 => (":path", "/index.html"), + 6 => (":scheme", "http"), + 7 => (":scheme", "https"), + 8 => (":status", "200"), + 9 => (":status", "204"), + 10 => (":status", "206"), + 11 => (":status", "304"), + 12 => (":status", "400"), + 13 => (":status", "404"), + 14 => (":status", "500"), + 15 => ("accept-charset", ""), + 16 => ("accept-encoding", "gzip, deflate"), + 17 => ("accept-language", ""), + 18 => ("accept-ranges", ""), + 19 => ("accept", ""), + 20 => ("access-control-allow-origin", ""), + 21 => ("age", ""), + 22 => ("allow", ""), + 23 => ("authorization", ""), + 24 => ("cache-control", ""), + 25 => ("content-disposition", ""), + 26 => ("content-encoding", ""), + 27 => ("content-language", ""), + 28 => ("content-length", ""), + 29 => ("content-location", ""), + 30 => ("content-range", ""), + 31 => ("content-type", ""), + 32 => ("cookie", ""), + 33 => ("date", ""), + 34 => ("etag", ""), + 35 => ("expect", ""), + 36 => ("expires", ""), + 37 => ("from", ""), + 38 => ("host", ""), + 39 => ("if-match", ""), + 40 => ("if-modified-since", ""), + 41 => ("if-none-match", ""), + 42 => ("if-range", ""), + 43 => ("if-unmodified-since", ""), + 44 => ("last-modified", ""), + 45 => ("link", ""), + 46 => ("location", ""), + 47 => ("max-forwards", ""), + 48 => ("proxy-authenticate", ""), + 49 => ("proxy-authorization", ""), + 50 => ("range", ""), + 51 => ("referer", ""), + 52 => ("refresh", ""), + 53 => ("retry-after", ""), + 54 => ("server", ""), + 55 => ("set-cookie", ""), + 56 => ("strict-transport-security", ""), + 57 => ("transfer-encoding", ""), + 58 => ("user-agent", ""), + 59 => ("vary", ""), + 60 => ("via", ""), + 61 => ("www-authenticate", ""), + _ => ("", ""), + }; + if name.len() > 0 { + return Some(HTTP2FrameHeaderBlock { + name: name.as_bytes().to_vec(), + value: value.as_bytes().to_vec(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }); + } else { + //use dynamic table + if dyn_headers.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize { + return Some(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + sizeupdate: 0, + }); + } else { + let indyn = dyn_headers.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(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + return Some(headcopy); + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] +pub enum HTTP2HeaderDecodeStatus { + HTTP2HeaderDecodeSuccess = 0, + HTTP2HeaderDecodeSizeUpdate = 1, + HTTP2HeaderDecodeError = 0x80, + HTTP2HeaderDecodeNotIndexed = 0x81, + HTTP2HeaderDecodeIntegerOverflow = 0x82, +} + +impl fmt::Display for HTTP2HeaderDecodeStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameHeaderBlock { + pub name: Vec, + pub value: Vec, + pub error: HTTP2HeaderDecodeStatus, + pub sizeupdate: u64, +} + +fn http2_parse_headers_block_indexed<'a>( + input: &'a [u8], dyn_headers: &Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!( + input, + complete!(tuple!( + verify!(take_bits!(1u8), |&x| x == 1), + take_bits!(7u8) + )) + ) + } + let (i2, indexed) = parser(input)?; + match http2_frame_header_static(indexed.1, dyn_headers) { + Some(h) => Ok((i2, h)), + _ => Err(Err::Error((i2, ErrorKind::MapOpt))), + } +} + +fn http2_parse_headers_block_string(input: &[u8]) -> IResult<&[u8], Vec> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!(input, tuple!(take_bits!(1u8), take_bits!(7u8))) + } + let (i2, huffslen) = parser(input)?; + let (i3, data) = take!(i2, huffslen.1 as usize)?; + if huffslen.0 == 0 { + return Ok((i3, data.to_vec())); + } else { + let (_, val) = bits!(data, many0!(huffman::http2_decode_huffman))?; + return Ok((i3, val)); + } +} + +fn http2_parse_headers_block_literal_common<'a>( + input: &'a [u8], index: u8, dyn_headers: &Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + let (i3, name, error) = if index == 0 { + match http2_parse_headers_block_string(input) { + Ok((r, n)) => Ok((r, n, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)), + Err(e) => Err(e), + } + } else { + match http2_frame_header_static(index, dyn_headers) { + Some(x) => Ok(( + input, + x.name, + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + )), + None => Ok(( + input, + Vec::new(), + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + )), + } + }?; + let (i4, value) = http2_parse_headers_block_string(i3)?; + return Ok(( + i4, + HTTP2FrameHeaderBlock { + name, + value, + error, + sizeupdate: 0, + }, + )); +} + +fn http2_parse_headers_block_literal_incindex<'a>( + input: &'a [u8], dyn_headers: &mut Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!( + input, + complete!(tuple!( + verify!(take_bits!(2u8), |&x| x == 1), + take_bits!(6u8) + )) + ) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = if indexed.1 == 0x3F { + map!(i2, be_u8, |i| i + 0x3F) + } else { + Ok((i2, indexed.1)) + }?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + match r { + Ok((r, head)) => { + let headcopy = HTTP2FrameHeaderBlock { + name: head.name.to_vec(), + value: head.value.to_vec(), + error: head.error, + sizeupdate: 0, + }; + dyn_headers.push(headcopy); + if dyn_headers.len() > 255 - HTTP2_STATIC_HEADERS_NUMBER { + dyn_headers.remove(0); + } + //we do not limit the dynamic table size + return Ok((r, head)); + } + Err(e) => { + return Err(e); + } + } +} + +fn http2_parse_headers_block_literal_noindex<'a>( + input: &'a [u8], dyn_headers: &Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!( + input, + complete!(tuple!( + verify!(take_bits!(4u8), |&x| x == 0), + take_bits!(4u8) + )) + ) + } + let (i2, indexed) = parser(input)?; + //undocumented in RFC ?! found with wireshark + let (i3, indexreal) = if indexed.1 == 0xF { + map!(i2, be_u8, |i| i + 0xF) + } else { + Ok((i2, indexed.1)) + }?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_headers_block_literal_neverindex<'a>( + input: &'a [u8], dyn_headers: &Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!( + input, + complete!(tuple!( + verify!(take_bits!(4u8), |&x| x == 1), + take_bits!(4u8) + )) + ) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = if indexed.1 == 0xF { + map!(i2, be_u8, |i| i + 0xF) + } else { + Ok((i2, indexed.1)) + }?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_headers_block_dynamic_size(input: &[u8]) -> IResult<&[u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits!( + input, + complete!(tuple!( + verify!(take_bits!(3u8), |&x| x == 1), + take_bits!(5u8) + )) + ) + } + let (i2, maxsize) = parser(input)?; + if maxsize.1 == 31 { + let (i3, maxsize2) = take_while!(i2, |ch| (ch & 0x80) != 0)?; + let (i4, maxsize3) = be_u8(i3)?; + //9 is maximum size to encode a variable length u64 + if maxsize2.len() > 9 { + return Ok(( + i4, + HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow, + sizeupdate: 0, + }, + )); + } + let mut sizeupdate = 31 as u64; + for i in 0..maxsize2.len() { + sizeupdate += ((maxsize2[i] & 0x7F) as u64) << (7 * i); + } + sizeupdate += (maxsize3 as u64) << (7 * maxsize2.len()); + return Ok(( + i4, + HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate, + sizeupdate: sizeupdate, + }, + )); + } + return Ok(( + i2, + HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate, + sizeupdate: maxsize.1 as u64, + }, + )); +} + +fn http2_parse_headers_block<'a>( + input: &'a [u8], dyn_headers: &mut Vec, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + //caller garantees o have at least one byte + if input[0] & 0x80 != 0 { + return http2_parse_headers_block_indexed(input, dyn_headers); + } 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); + } else if input[0] & 0x10 != 0 { + return http2_parse_headers_block_literal_neverindex(input, dyn_headers); + } else { + return http2_parse_headers_block_literal_noindex(input, dyn_headers); + } +} + +#[derive(Clone)] +pub struct HTTP2FrameHeaders { + pub padlength: Option, + pub priority: Option, + pub blocks: Vec, +} + +//end stream +pub const HTTP2_FLAG_HEADER_EOS: u8 = 0x1; +pub const HTTP2_FLAG_HEADER_END_HEADERS: u8 = 0x4; +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, +) -> IResult<&'a [u8], HTTP2FrameHeaders> { + let (i2, padlength) = cond!(input, flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)?; + let (mut i3, priority) = cond!( + i2, + flags & HTTP2_FLAG_HEADER_PRIORITY != 0, + http2_parse_headers_priority + )?; + let mut blocks = Vec::new(); + while i3.len() > 0 { + match http2_parse_headers_block(i3, dyn_headers) { + Ok((rem, b)) => { + blocks.push(b); + debug_validate_bug_on!(i3.len() == rem.len()); + if i3.len() == rem.len() { + //infinite loop + return Err(Err::Error((input, ErrorKind::Eof))); + } + i3 = rem; + } + Err(x) => { + return Err(x); + } + } + } + return Ok(( + i3, + HTTP2FrameHeaders { + padlength, + priority, + blocks, + }, + )); +} + +#[derive(Clone)] +pub struct HTTP2FramePushPromise { + pub padlength: Option, + pub reserved: u8, + pub stream_id: u32, + pub blocks: Vec, +} + +pub fn http2_parse_frame_push_promise<'a>( + input: &'a [u8], flags: u8, dyn_headers: &mut Vec, +) -> 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)))?; + let mut blocks = Vec::new(); + while i3.len() > 0 { + match http2_parse_headers_block(i3, dyn_headers) { + Ok((rem, b)) => { + blocks.push(b); + debug_validate_bug_on!(i3.len() == rem.len()); + if i3.len() == rem.len() { + //infinite loop + return Err(Err::Error((input, ErrorKind::Eof))); + } + i3 = rem; + } + Err(x) => { + return Err(x); + } + } + } + return Ok(( + i3, + HTTP2FramePushPromise { + padlength, + reserved: stream_id.0, + stream_id: stream_id.1, + blocks, + }, + )); +} + +#[derive(Clone)] +pub struct HTTP2FrameContinuation { + pub blocks: Vec, +} + +pub fn http2_parse_frame_continuation<'a>( + input: &'a [u8], dyn_headers: &mut Vec, +) -> IResult<&'a [u8], HTTP2FrameContinuation> { + let mut i3 = input; + let mut blocks = Vec::new(); + while i3.len() > 0 { + match http2_parse_headers_block(i3, dyn_headers) { + Ok((rem, b)) => { + blocks.push(b); + debug_validate_bug_on!(i3.len() == rem.len()); + if i3.len() == rem.len() { + //infinite loop + return Err(Err::Error((input, ErrorKind::Eof))); + } + i3 = rem; + } + Err(x) => { + return Err(x); + } + } + } + return Ok((i3, HTTP2FrameContinuation { blocks })); +} + +#[repr(u16)] +#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)] +pub enum HTTP2SettingsId { + SETTINGSHEADERTABLESIZE = 1, + SETTINGSENABLEPUSH = 2, + SETTINGSMAXCONCURRENTSTREAMS = 3, + SETTINGSINITIALWINDOWSIZE = 4, + SETTINGSMAXFRAMESIZE = 5, + SETTINGSMAXHEADERLISTSIZE = 6, +} + +impl fmt::Display for HTTP2SettingsId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2SettingsId { + type Err = String; + + fn from_str(s: &str) -> Result { + let su = s.to_uppercase(); + let su_slice: &str = &*su; + match su_slice { + "SETTINGS_HEADER_TABLE_SIZE" => Ok(HTTP2SettingsId::SETTINGSHEADERTABLESIZE), + "SETTINGS_ENABLE_PUSH" => Ok(HTTP2SettingsId::SETTINGSENABLEPUSH), + "SETTINGS_MAX_CONCURRENT_STREAMS" => Ok(HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS), + "SETTINGS_INITIAL_WINDOW_SIZE" => Ok(HTTP2SettingsId::SETTINGSINITIALWINDOWSIZE), + "SETTINGS_MAX_FRAME_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXFRAMESIZE), + "SETTINGS_MAX_HEADER_LIST_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXHEADERLISTSIZE), + _ => Err(format!("'{}' is not a valid value for HTTP2SettingsId", s)), + } + } +} + +//TODOask move elsewhere generic with DetectU64Data and such +#[derive(PartialEq, Debug)] +pub enum DetectUintMode { + DetectUintModeEqual, + DetectUintModeLt, + DetectUintModeGt, + DetectUintModeRange, +} + +pub struct DetectU32Data { + pub value: u32, + pub valrange: u32, + pub mode: DetectUintMode, +} + +pub struct DetectHTTP2settingsSigCtx { + pub id: HTTP2SettingsId, //identifier + pub value: Option, //optional value +} + +named!(detect_parse_u32_start_equal<&str,DetectU32Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + opt! (tag!("=") ) >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeEqual}) + ) +); + +named!(detect_parse_u32_start_interval<&str,DetectU32Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + opt!( is_a!( " " ) ) >> + tag!("-") >> + opt!( is_a!( " " ) ) >> + valrange : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU32Data{value, valrange, mode:DetectUintMode::DetectUintModeRange}) + ) +); + +named!(detect_parse_u32_start_lesser<&str,DetectU32Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + tag!("<") >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeLt}) + ) +); + +named!(detect_parse_u32_start_greater<&str,DetectU32Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + tag!(">") >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeGt}) + ) +); + +named!(detect_parse_u32<&str,DetectU32Data>, + do_parse!( + u32 : alt! ( + detect_parse_u32_start_lesser | + detect_parse_u32_start_greater | + complete!( detect_parse_u32_start_interval ) | + detect_parse_u32_start_equal + ) >> + (u32) + ) +); + +named!(pub http2_parse_settingsctx<&str,DetectHTTP2settingsSigCtx>, + do_parse!( + opt!( is_a!( " " ) ) >> + id: map_opt!( alt! ( complete!( is_not!( " <>=" ) ) | rest ), + |s: &str| HTTP2SettingsId::from_str(s).ok() ) >> + value: opt!( complete!( detect_parse_u32 ) ) >> + (DetectHTTP2settingsSigCtx{id, value}) + ) +); + +pub struct DetectU64Data { + pub value: u64, + pub valrange: u64, + pub mode: DetectUintMode, +} + +named!(detect_parse_u64_start_equal<&str,DetectU64Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + opt! (tag!("=") ) >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeEqual}) + ) +); + +named!(detect_parse_u64_start_interval<&str,DetectU64Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + opt!( is_a!( " " ) ) >> + tag!("-") >> + opt!( is_a!( " " ) ) >> + valrange : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU64Data{value, valrange, mode:DetectUintMode::DetectUintModeRange}) + ) +); + +named!(detect_parse_u64_start_lesser<&str,DetectU64Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + tag!("<") >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeLt}) + ) +); + +named!(detect_parse_u64_start_greater<&str,DetectU64Data>, + do_parse!( + opt!( is_a!( " " ) ) >> + tag!(">") >> + opt!( is_a!( " " ) ) >> + value : map_opt!(digit1, |s: &str| s.parse::().ok()) >> + (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeGt}) + ) +); + +named!(pub detect_parse_u64<&str,DetectU64Data>, + do_parse!( + u64 : alt! ( + detect_parse_u64_start_lesser | + detect_parse_u64_start_greater | + complete!( detect_parse_u64_start_interval ) | + detect_parse_u64_start_equal + ) >> + (u64) + ) +); + +#[derive(Clone, Copy)] +pub struct HTTP2FrameSettings { + pub id: HTTP2SettingsId, + pub value: u32, +} + +named!( + http2_parse_frame_setting, + do_parse!( + id: map_opt!(be_u16, num::FromPrimitive::from_u16) + >> value: be_u32 + >> (HTTP2FrameSettings { id, value }) + ) +); + +named!(pub http2_parse_frame_settings>, + many0!( complete!(http2_parse_frame_setting) ) +); + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_http2_parse_header() { + let buf0: &[u8] = &[0x82]; + let mut dynh: Vec = + Vec::with_capacity(255 - HTTP2_STATIC_HEADERS_NUMBER); + let r0 = http2_parse_headers_block(buf0, &mut dynh); + match r0 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":method".as_bytes().to_vec()); + assert_eq!(hd.value, "GET".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf1: &[u8] = &[0x53, 0x03, 0x2A, 0x2F, 0x2A]; + let r1 = http2_parse_headers_block(buf1, &mut dynh); + match r1 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, "accept".as_bytes().to_vec()); + 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); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf: &[u8] = &[ + 0x41, 0x8a, 0xa0, 0xe4, 0x1d, 0x13, 0x9d, 0x09, 0xb8, 0xc8, 0x00, 0x0f, + ]; + let result = http2_parse_headers_block(buf, &mut dynh); + match result { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + 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); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf3: &[u8] = &[0xbe]; + let r3 = http2_parse_headers_block(buf3, &mut dynh); + match r3 { + Ok((remainder, hd)) => { + // same as before + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + 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); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf2: &[u8] = &[ + 0x04, 0x94, 0x62, 0x43, 0x91, 0x8a, 0x47, 0x55, 0xa3, 0xa1, 0x89, 0xd3, 0x4d, 0x0c, + 0x1a, 0xa9, 0x0b, 0xe5, 0x79, 0xd3, 0x4d, 0x1f, + ]; + let r2 = http2_parse_headers_block(buf2, &mut dynh); + match r2 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":path".as_bytes().to_vec()); + 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); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + /// Simple test of some valid data. + #[test] + fn test_http2_parse_settingsctx() { + let s = "SETTINGS_ENABLE_PUSH"; + let r = http2_parse_settingsctx(s); + match r { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSENABLEPUSH); + match ctx.value { + Some(_) => { + panic!("Unexpected value"); + } + None => {} + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + //spaces in the end + let s1 = "SETTINGS_ENABLE_PUSH "; + let r1 = http2_parse_settingsctx(s1); + match r1 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSENABLEPUSH); + match ctx.value { + Some(_) => { + panic!("Unexpected value"); + } + None => {} + } + assert_eq!(rem.len(), 1); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s2 = "SETTINGS_MAX_CONCURRENT_STREAMS 42"; + let r2 = http2_parse_settingsctx(s2); + match r2 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.value, 42); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s3 = "SETTINGS_MAX_CONCURRENT_STREAMS 42-68"; + let r3 = http2_parse_settingsctx(s3); + match r3 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.value, 42); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeRange); + assert_eq!(ctxval.valrange, 68); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s4 = "SETTINGS_MAX_CONCURRENT_STREAMS<54"; + let r4 = http2_parse_settingsctx(s4); + match r4 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.value, 54); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeLt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s5 = "SETTINGS_MAX_CONCURRENT_STREAMS > 76"; + let r5 = http2_parse_settingsctx(s5); + match r5 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.value, 76); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeGt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + } + + #[test] + fn test_http2_parse_headers_block_string() { + let buf: &[u8] = &[0x01, 0xFF]; + let r = http2_parse_headers_block_string(buf); + match r { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + _ => { + panic!("Result should have been ok"); + } + } + let buf2: &[u8] = &[0x83, 0xFF, 0xFF, 0xEA]; + let r2 = http2_parse_headers_block_string(buf2); + match r2 { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + _ => { + panic!("Result should have been ok"); + } + } + } + + #[test] + fn test_http2_parse_frame_header() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x64, + ]; + let result = http2_parse_frame_header(buf); + match result { + Ok((remainder, frame)) => { + // Check the first message. + assert_eq!(frame.length, 6); + assert_eq!(frame.ftype, HTTP2FrameType::SETTINGS as u8); + assert_eq!(frame.flags, 0); + assert_eq!(frame.reserved, 0); + assert_eq!(frame.stream_id, 0); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 6); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9a48a9c75f..f0af87bcff 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -81,3 +81,4 @@ pub mod rdp; pub mod x509; pub mod asn1; pub mod ssh; +pub mod http2; diff --git a/src/Makefile.am b/src/Makefile.am index ad3e6907d3..70e11faf25 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,6 +35,7 @@ app-layer-htp-file.c app-layer-htp-file.h \ app-layer-htp-libhtp.c app-layer-htp-libhtp.h \ app-layer-htp-mem.c app-layer-htp-mem.h \ app-layer-htp-xff.c app-layer-htp-xff.h \ +app-layer-http2.c app-layer-http2.h \ app-layer-modbus.c app-layer-modbus.h \ app-layer-parser.c app-layer-parser.h \ app-layer-protos.c app-layer-protos.h \ @@ -208,6 +209,7 @@ detect-http-stat-code.c detect-http-stat-code.h \ detect-http-stat-msg.c detect-http-stat-msg.h \ detect-http-ua.c detect-http-ua.h \ detect-http-uri.c detect-http-uri.h \ +detect-http2.c detect-http2.h \ detect-icmp-id.c detect-icmp-id.h \ detect-icmp-seq.c detect-icmp-seq.h \ detect-icmpv6hdr.c detect-icmpv6hdr.h \ @@ -372,6 +374,7 @@ output-json-flow.c output-json-flow.h \ output-json-ftp.c output-json-ftp.h \ output-json-netflow.c output-json-netflow.h \ output-json-http.c output-json-http.h \ +output-json-http2.c output-json-http2.h \ output-json-sip.c output-json-sip.h \ output-json-smtp.c output-json-smtp.h \ output-json-ssh.c output-json-ssh.h \ diff --git a/src/alert-prelude.c b/src/alert-prelude.c index 33c853c6bd..48ae6e58b1 100644 --- a/src/alert-prelude.c +++ b/src/alert-prelude.c @@ -687,6 +687,26 @@ static void PacketToDataProtoHTTP(const Packet *p, const PacketAlert *pa, idmef_ } +/** + * \brief Handle ALPROTO_HTTP2 JSON information + * \param p Packet where to extract data + * \param pa Packet alert information + * \param alert IDMEF alert + * \return void + */ +static void PacketToDataProtoHTTP2(const Packet *p, const PacketAlert *pa, idmef_alert_t *alert) +{ + void *http2_state = FlowGetAppState(f); + if (http2_state) { + void *tx_ptr = rs_http2_state_get_tx(http2_state, pa->tx_id); + json_t *js = rs_http2_log_json(tx_ptr); + if (unlikely(js == NULL)) + return; + JsonToAdditionalData(NULL, js, alert); + json_decref(js); + } +} + /** * \brief Handle ALPROTO_TLS JSON information * \param p Packet where to extract data @@ -811,6 +831,9 @@ static int PacketToData(const Packet *p, const PacketAlert *pa, idmef_alert_t *a case ALPROTO_HTTP: PacketToDataProtoHTTP(p, pa, alert); break; + case ALPROTO_HTTP2: + PacketToDataProtoHTTP(p, pa, alert); + break; case ALPROTO_TLS: PacketToDataProtoTLS(p, pa, alert); break; diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c new file mode 100644 index 0000000000..c937c5a539 --- /dev/null +++ b/src/app-layer-http2.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + * Parser for HTTP2, RFC 7540 + */ + +#include "suricata-common.h" +#include "stream.h" +#include "conf.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "app-layer-http2.h" +#include "rust.h" + +static int HTTP2RegisterPatternsForProtocolDetection(void) +{ + /* Using the 24 bytes pattern makes AppLayerTest09 fail/leak + * The complete pattern is "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + */ + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_HTTP2, + "PRI * HTTP/2.0\r\n", + 16, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + return 0; +} + +static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; +static SuricataFileContext sfc = { &sbcfg }; + +void RegisterHTTP2Parsers(void) +{ + const char *proto_name = "http2"; + + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_HTTP2, proto_name); + if (HTTP2RegisterPatternsForProtocolDetection() < 0) + return; + + rs_http2_init(&sfc); + rs_http2_register_parser(); + } + +#ifdef UNITTESTS + //TODOask HTTP2ParserRegisterTests(); +#endif +} diff --git a/src/app-layer-http2.h b/src/app-layer-http2.h new file mode 100644 index 0000000000..043cf7b30c --- /dev/null +++ b/src/app-layer-http2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef __APP_LAYER_HTTP2_H__ +#define __APP_LAYER_HTTP2_H__ + +void RegisterHTTP2Parsers(void); + +#endif /* __APP_LAYER_HTTP2_H__ */ diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index bef28bdf3f..a7c28c1e6a 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -70,6 +70,7 @@ #include "app-layer-template.h" #include "app-layer-template-rust.h" #include "app-layer-rdp.h" +#include "app-layer-http2.h" #include "conf.h" #include "util-spm.h" @@ -1580,6 +1581,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterMQTTParsers(); RegisterTemplateParsers(); RegisterRdpParsers(); + RegisterHTTP2Parsers(); /** IMAP */ AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap"); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 0260d8347d..d6b2618e8f 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -117,6 +117,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_RDP: proto_name = "rdp"; break; + case ALPROTO_HTTP2: + proto_name = "http2"; + break; case ALPROTO_FAILED: proto_name = "failed"; break; @@ -161,6 +164,7 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; + if (strcmp(proto_name,"http2")==0) return ALPROTO_HTTP2; if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED; return ALPROTO_UNKNOWN; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 0b421c8643..278c3e4389 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -56,6 +56,7 @@ enum AppProtoEnum { ALPROTO_TEMPLATE, ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, + ALPROTO_HTTP2, /* used by the probing parser when alproto detection fails * permanently for that particular stream */ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 2ccc2b7d8f..00b2575c25 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -161,6 +161,7 @@ #include "detect-http-stat-msg.h" #include "detect-http-request-line.h" #include "detect-http-response-line.h" +#include "detect-http2.h" #include "detect-byte-extract.h" #include "detect-file-data.h" #include "detect-pkt-data.h" @@ -473,6 +474,7 @@ void SigTableSetup(void) DetectHttpStatMsgRegister(); DetectHttpStatCodeRegister(); + DetectHttp2Register(); DetectDnsQueryRegister(); DetectDnsOpcodeRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 768c9fffc4..52864ddacd 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -172,6 +172,15 @@ enum DetectKeywordId { DETECT_PKT_DATA, DETECT_AL_APP_LAYER_EVENT, + DETECT_HTTP2_FRAMETYPE, + DETECT_HTTP2_ERRORCODE, + DETECT_HTTP2_PRIORITY, + DETECT_HTTP2_WINDOW, + DETECT_HTTP2_SIZEUPDATE, + DETECT_HTTP2_SETTINGS, + DETECT_HTTP2_HEADERNAME, + DETECT_HTTP2_HEADER, + DETECT_DCE_IFACE, DETECT_DCE_OPNUM, DETECT_DCE_STUB_DATA, diff --git a/src/detect-file-data.c b/src/detect-file-data.c index fab29513f3..566291cb81 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -103,6 +103,12 @@ void DetectFiledataRegister(void) DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2, PrefilterMpmFiledataRegister, NULL, ALPROTO_SMB, 0); + DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOSERVER, 2, + PrefilterMpmFiledataRegister, NULL, + ALPROTO_HTTP2, HTTP2StateDataClient); + DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2, + PrefilterMpmFiledataRegister, NULL, + ALPROTO_HTTP2, HTTP2StateDataServer); DetectAppLayerInspectEngineRegister2("file_data", ALPROTO_HTTP, SIG_FLAG_TOCLIENT, HTP_RESPONSE_BODY, @@ -118,6 +124,12 @@ void DetectFiledataRegister(void) DetectAppLayerInspectEngineRegister2("file_data", ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectFiledata, NULL); + DetectAppLayerInspectEngineRegister2("file_data", + ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateDataClient, + DetectEngineInspectFiledata, NULL); + DetectAppLayerInspectEngineRegister2("file_data", + ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateDataServer, + DetectEngineInspectFiledata, NULL); DetectBufferTypeSetDescriptionByName("file_data", "http response body, smb files or smtp attachments data"); @@ -167,7 +179,8 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha if (!DetectProtoContainsProto(&s->proto, IPPROTO_TCP) || (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP && - s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB)) { + s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB && + s->alproto != ALPROTO_HTTP2)) { SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); return -1; } diff --git a/src/detect-filemagic.c b/src/detect-filemagic.c index 6d53c4705a..3fc2d2858e 100644 --- a/src/detect-filemagic.c +++ b/src/detect-filemagic.c @@ -127,9 +127,9 @@ void DetectFilemagicRegister(void) g_file_match_list_id = DetectBufferTypeRegister("files"); AppProto protos_ts[] = { - ALPROTO_HTTP, ALPROTO_SMTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, 0 }; + ALPROTO_HTTP, ALPROTO_SMTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, ALPROTO_HTTP2, 0 }; AppProto protos_tc[] = { - ALPROTO_HTTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, 0 }; + ALPROTO_HTTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, ALPROTO_HTTP2, 0 }; for (int i = 0; protos_ts[i] != 0; i++) { DetectAppLayerInspectEngineRegister2("file.magic", protos_ts[i], diff --git a/src/detect-http2.c b/src/detect-http2.c new file mode 100644 index 0000000000..c73b42e59b --- /dev/null +++ b/src/detect-http2.c @@ -0,0 +1,984 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + */ + +#include "suricata-common.h" + +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-uint.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-content-inspection.h" + +#include "detect-http2.h" +#include "util-byte.h" +#include "rust.h" + +#ifdef UNITTESTS +void DetectHTTP2frameTypeRegisterTests (void); +void DetectHTTP2errorCodeRegisterTests (void); +void DetectHTTP2priorityRegisterTests (void); +void DetectHTTP2windowRegisterTests (void); +void DetectHTTP2settingsRegisterTests (void); +void DetectHTTP2sizeUpdateRegisterTests (void); +#endif + +/* prototypes */ +static int DetectHTTP2frametypeMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2frametypeSetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2frametypeFree (DetectEngineCtx *, void *); + +static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2errorcodeSetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2errorcodeFree (DetectEngineCtx *, void *); + +static int DetectHTTP2priorityMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2prioritySetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2priorityFree (DetectEngineCtx *, void *); + +static int DetectHTTP2windowMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2windowSetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2windowFree (DetectEngineCtx *, void *); + +static int DetectHTTP2sizeUpdateMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2sizeUpdateSetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2sizeUpdateFree (DetectEngineCtx *, void *); + +static int DetectHTTP2settingsMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectHTTP2settingsSetup (DetectEngineCtx *, Signature *, const char *); +void DetectHTTP2settingsFree (DetectEngineCtx *, void *); + +static int DetectHTTP2headerNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg); +static int PrefilterMpmHttp2HeaderNameRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id); +static int DetectEngineInspectHttp2HeaderName( + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, + const Signature *s, + Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +static int DetectHTTP2headerSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg); +static int PrefilterMpmHttp2HeaderRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id); +static int DetectEngineInspectHttp2Header( + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, + const Signature *s, + Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); +static bool DetectHttp2HeaderValidateCallback(const Signature *s, const char **sigerror); + +#ifdef UNITTESTS +void DetectHTTP2RegisterTests (void); +#endif + +static int g_http2_match_buffer_id = 0; +static int g_http2_header_name_buffer_id = 0; +static int g_http2_header_buffer_id = 0; + +static int DetectEngineInspectHTTP2(ThreadVars *tv, DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd, + f, flags, alstate, txv, tx_id); +} + +/** + * \brief Registration function for HTTP2 keywords + */ + +void DetectHttp2Register(void) +{ + sigmatch_table[DETECT_HTTP2_FRAMETYPE].name = "http2.frametype"; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].desc = "match on HTTP2 frame type field"; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].url = "/rules/http2-keywords.html#frametype"; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].Match = NULL; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].AppLayerTxMatch = DetectHTTP2frametypeMatch; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].Setup = DetectHTTP2frametypeSetup; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].Free = DetectHTTP2frametypeFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2frameTypeRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_ERRORCODE].name = "http2.errorcode"; + sigmatch_table[DETECT_HTTP2_ERRORCODE].desc = "match on HTTP2 error code field"; + sigmatch_table[DETECT_HTTP2_ERRORCODE].url = "/rules/http2-keywords.html#errorcode"; + sigmatch_table[DETECT_HTTP2_ERRORCODE].Match = NULL; + sigmatch_table[DETECT_HTTP2_ERRORCODE].AppLayerTxMatch = DetectHTTP2errorcodeMatch; + sigmatch_table[DETECT_HTTP2_ERRORCODE].Setup = DetectHTTP2errorcodeSetup; + sigmatch_table[DETECT_HTTP2_ERRORCODE].Free = DetectHTTP2errorcodeFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2errorCodeRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_PRIORITY].name = "http2.priority"; + sigmatch_table[DETECT_HTTP2_PRIORITY].desc = "match on HTTP2 priority weight field"; + sigmatch_table[DETECT_HTTP2_PRIORITY].url = "/rules/http2-keywords.html#priority"; + sigmatch_table[DETECT_HTTP2_PRIORITY].Match = NULL; + sigmatch_table[DETECT_HTTP2_PRIORITY].AppLayerTxMatch = DetectHTTP2priorityMatch; + sigmatch_table[DETECT_HTTP2_PRIORITY].Setup = DetectHTTP2prioritySetup; + sigmatch_table[DETECT_HTTP2_PRIORITY].Free = DetectHTTP2priorityFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_PRIORITY].RegisterTests = DetectHTTP2priorityRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_WINDOW].name = "http2.window"; + sigmatch_table[DETECT_HTTP2_WINDOW].desc = "match on HTTP2 window update size increment field"; + sigmatch_table[DETECT_HTTP2_WINDOW].url = "/rules/http2-keywords.html#window"; + sigmatch_table[DETECT_HTTP2_WINDOW].Match = NULL; + sigmatch_table[DETECT_HTTP2_WINDOW].AppLayerTxMatch = DetectHTTP2windowMatch; + sigmatch_table[DETECT_HTTP2_WINDOW].Setup = DetectHTTP2windowSetup; + sigmatch_table[DETECT_HTTP2_WINDOW].Free = DetectHTTP2windowFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_WINDOW].RegisterTests = DetectHTTP2windowRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].name = "http2.size_update"; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].desc = "match on HTTP2 dynamic headers table size update"; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].url = "/rules/http2-keywords.html#sizeupdate"; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Match = NULL; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].AppLayerTxMatch = DetectHTTP2sizeUpdateMatch; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Setup = DetectHTTP2sizeUpdateSetup; + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Free = DetectHTTP2sizeUpdateFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_SIZEUPDATE].RegisterTests = DetectHTTP2sizeUpdateRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_SETTINGS].name = "http2.settings"; + sigmatch_table[DETECT_HTTP2_SETTINGS].desc = "match on HTTP2 settings identifier and value fields"; + sigmatch_table[DETECT_HTTP2_SETTINGS].url = "/rules/http2-keywords.html#settings"; + sigmatch_table[DETECT_HTTP2_SETTINGS].Match = NULL; + sigmatch_table[DETECT_HTTP2_SETTINGS].AppLayerTxMatch = DetectHTTP2settingsMatch; + sigmatch_table[DETECT_HTTP2_SETTINGS].Setup = DetectHTTP2settingsSetup; + sigmatch_table[DETECT_HTTP2_SETTINGS].Free = DetectHTTP2settingsFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_HTTP2_SETTINGS].RegisterTests = DetectHTTP2settingsRegisterTests; +#endif + + sigmatch_table[DETECT_HTTP2_HEADERNAME].name = "http2.header_name"; + sigmatch_table[DETECT_HTTP2_HEADERNAME].desc = "sticky buffer to match on one HTTP2 header name"; + sigmatch_table[DETECT_HTTP2_HEADERNAME].url = "/rules/http2-keywords.html#header_name"; + sigmatch_table[DETECT_HTTP2_HEADERNAME].Setup = DetectHTTP2headerNameSetup; + sigmatch_table[DETECT_HTTP2_HEADERNAME].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerMpmRegister2("http2_header_name", SIG_FLAG_TOCLIENT, 2, + PrefilterMpmHttp2HeaderNameRegister, NULL, + ALPROTO_HTTP2, HTTP2StateOpen); + DetectAppLayerInspectEngineRegister2("http2_header_name", + ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateOpen, + DetectEngineInspectHttp2HeaderName, NULL); + DetectAppLayerMpmRegister2("http2_header_name", SIG_FLAG_TOSERVER, 2, + PrefilterMpmHttp2HeaderNameRegister, NULL, + ALPROTO_HTTP2, HTTP2StateOpen); + DetectAppLayerInspectEngineRegister2("http2_header_name", + ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateOpen, + DetectEngineInspectHttp2HeaderName, NULL); + + DetectBufferTypeSetDescriptionByName("http2_header_name", + "HTTP2 header name"); + g_http2_header_name_buffer_id = DetectBufferTypeGetByName("http2_header_name"); + + sigmatch_table[DETECT_HTTP2_HEADER].name = "http2.header"; + sigmatch_table[DETECT_HTTP2_HEADER].desc = "sticky buffer to match on one HTTP2 header name and value"; + sigmatch_table[DETECT_HTTP2_HEADER].url = "/rules/http2-keywords.html#header"; + sigmatch_table[DETECT_HTTP2_HEADER].Setup = DetectHTTP2headerSetup; + sigmatch_table[DETECT_HTTP2_HEADER].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerMpmRegister2("http2_header", SIG_FLAG_TOCLIENT, 2, + PrefilterMpmHttp2HeaderRegister, NULL, + ALPROTO_HTTP2, HTTP2StateOpen); + DetectAppLayerInspectEngineRegister2("http2_header", + ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateOpen, + DetectEngineInspectHttp2Header, NULL); + DetectAppLayerMpmRegister2("http2_header", SIG_FLAG_TOSERVER, 2, + PrefilterMpmHttp2HeaderRegister, NULL, + ALPROTO_HTTP2, HTTP2StateOpen); + DetectAppLayerInspectEngineRegister2("http2_header", + ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateOpen, + DetectEngineInspectHttp2Header, NULL); + + DetectBufferTypeSetDescriptionByName("http2_header", + "HTTP2 header name and value"); + DetectBufferTypeRegisterValidateCallback("http2_header", DetectHttp2HeaderValidateCallback); + g_http2_header_buffer_id = DetectBufferTypeGetByName("http2_header"); + + DetectAppLayerInspectEngineRegister("http2", + ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectHTTP2); + DetectAppLayerInspectEngineRegister("http2", + ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectHTTP2); + + g_http2_match_buffer_id = DetectBufferTypeRegister("http2"); + DetectUintRegister(); + + return; +} + +/** + * \brief This function is used to match HTTP2 frame type rule option on a transaction with those passed via http2.frametype: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2frametypeMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + uint8_t *detect = (uint8_t *)ctx; + + return rs_http2_tx_has_frametype(txv, flags, *detect); +} + +static int DetectHTTP2FuncParseFrameType(const char *str, uint8_t *ft) +{ + // first parse numeric value + if (ByteExtractStringUint8(ft, 10, strlen(str), str) >= 0) { + return 1; + } + + // it it failed so far, parse string value from enumeration + int r = rs_http2_parse_frametype(str); + if (r >= 0) { + *ft = r; + return 1; + } + + return 0; +} + +/** + * \brief this function is used to attach the parsed http2.frametype data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.frametype options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2frametypeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + uint8_t frame_type; + + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + if (!DetectHTTP2FuncParseFrameType(str, &frame_type)) { + SCLogError(SC_ERR_INVALID_SIGNATURE, + "Invalid argument \"%s\" supplied to http2.frametype keyword.", str); + return -1; + } + + uint8_t *http2ft = SCCalloc(1, sizeof(uint8_t)); + if (http2ft == NULL) + return -1; + *http2ft = frame_type; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2frametypeFree(NULL, http2ft); + return -1; + } + + sm->type = DETECT_HTTP2_FRAMETYPE; + sm->ctx = (SigMatchCtx *)http2ft; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with uint8_t + * + * \param ptr pointer to uint8_t + */ +void DetectHTTP2frametypeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +/** + * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.errorcode: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + uint32_t *detect = (uint32_t *)ctx; + + return rs_http2_tx_has_errorcode(txv, flags, *detect); + //TODOask handle negation rules +} + +static int DetectHTTP2FuncParseErrorCode(const char *str, uint32_t *ec) +{ + // first parse numeric value + if (ByteExtractStringUint32(ec, 10, strlen(str), str) >= 0) { + return 1; + } + + // it it failed so far, parse string value from enumeration + int r = rs_http2_parse_errorcode(str); + if (r >= 0) { + *ec = r; + return 1; + } + + return 0; +} + +/** + * \brief this function is used to attach the parsed http2.errorcode data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.errorcode options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2errorcodeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + uint32_t error_code; + + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + if (!DetectHTTP2FuncParseErrorCode(str, &error_code)) { + SCLogError(SC_ERR_INVALID_SIGNATURE, + "Invalid argument \"%s\" supplied to http2.errorcode keyword.", str); + return -1; + } + + uint32_t *http2ec = SCCalloc(1, sizeof(uint32_t)); + if (http2ec == NULL) + return -1; + *http2ec = error_code; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2errorcodeFree(NULL, http2ec); + return -1; + } + + sm->type = DETECT_HTTP2_ERRORCODE; + sm->ctx = (SigMatchCtx *)http2ec; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with uint32_t + * + * \param ptr pointer to uint32_t + */ +void DetectHTTP2errorcodeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +/** + * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.priority: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2priorityMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + uint32_t nb = 0; + int value = rs_http2_tx_get_next_priority(txv, flags, nb); + const DetectU8Data *du8 = (const DetectU8Data *)ctx; + while (value >= 0) { + if (DetectU8Match(value, du8)) { + return 1; + } + nb++; + value = rs_http2_tx_get_next_priority(txv, flags, nb); + } + return 0; +} + +/** + * \brief this function is used to attach the parsed http2.priority data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.priority options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2prioritySetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + DetectU8Data *prio = DetectU8Parse(str); + if (prio == NULL) + return -1; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + SCFree(prio); + return -1; + } + + sm->type = DETECT_HTTP2_PRIORITY; + sm->ctx = (SigMatchCtx *)prio; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with uint32_t + * + * \param ptr pointer to DetectU8Data + */ +void DetectHTTP2priorityFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +/** + * \brief This function is used to match HTTP2 window rule option on a transaction with those passed via http2.window: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2windowMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + uint32_t nb = 0; + int value = rs_http2_tx_get_next_window(txv, flags, nb); + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + while (value >= 0) { + if (DetectU32Match(value, du32)) { + return 1; + } + nb++; + value = rs_http2_tx_get_next_window(txv, flags, nb); + } + return 0; +} + +/** + * \brief this function is used to attach the parsed http2.window data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.window options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2windowSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + DetectU32Data *wu = DetectU32Parse(str); + if (wu == NULL) + return -1; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + SCFree(wu); + return -1; + } + + sm->type = DETECT_HTTP2_WINDOW; + sm->ctx = (SigMatchCtx *)wu; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with uint32_t + * + * \param ptr pointer to DetectU8Data + */ +void DetectHTTP2windowFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + +/** + * \brief This function is used to match HTTP2 size update rule option on a transaction with those passed via http2.size_update: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2sizeUpdateMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + return rs_http2_detect_sizeupdatectx_match(ctx, txv, flags); +} + +/** + * \brief this function is used to attach the parsed http2.size_update data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.size_update options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2sizeUpdateSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + void *su = rs_detect_u64_parse(str); + if (su == NULL) + return -1; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2settingsFree(NULL, su); + return -1; + } + + sm->type = DETECT_HTTP2_SIZEUPDATE; + sm->ctx = (SigMatchCtx *)su; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with uint32_t + * + * \param ptr pointer to DetectU8Data + */ +void DetectHTTP2sizeUpdateFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_detect_u64_free(ptr); +} + +/** + * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.settings: + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectHTTP2settingsMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) + +{ + return rs_http2_detect_settingsctx_match(ctx, txv, flags); +} + +/** + * \brief this function is used to attach the parsed http2.settings data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided http2.settings options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectHTTP2settingsSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + void *http2set = rs_http2_detect_settingsctx_parse(str); + if (http2set == NULL) + return -1; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2settingsFree(NULL, http2set); + return -1; + } + + sm->type = DETECT_HTTP2_SETTINGS; + sm->ctx = (SigMatchCtx *)http2set; + + SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id); + + return 0; +} + +/** + * \brief this function will free memory associated with rust signature context + * + * \param ptr pointer to rust signature context + */ +void DetectHTTP2settingsFree(DetectEngineCtx *de_ctx, void *ptr) +{ + rs_http2_detect_settingsctx_free(ptr); +} + +static int DetectHTTP2headerNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + if (DetectBufferSetActiveList(s, g_http2_header_name_buffer_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + return 0; +} + +static void PrefilterMpmHttp2HNameFree(void *ptr) +{ + SCFree(ptr); +} + +static InspectionBuffer *GetHttp2HNameData(DetectEngineThreadCtx *det_ctx, + const uint8_t flags, const DetectEngineTransforms *transforms, + Flow *_f, const struct MpmListIdDataArgs *cbdata, + int list_id, bool first) +{ + SCEnter(); + + InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id); + InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id); + if (buffer == NULL) + return NULL; + if (!first && buffer->inspect != NULL) + return buffer; + + uint32_t b_len = 0; + const uint8_t *b = NULL; + + if (rs_http2_tx_get_header_name(cbdata->txv, flags, (uint32_t)cbdata->local_id, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +static void PrefilterTxHttp2HName(DetectEngineThreadCtx *det_ctx, + const void *pectx, + Packet *p, Flow *f, void *txv, + const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx; + const MpmCtx *mpm_ctx = ctx->mpm_ctx; + const int list_id = ctx->list_id; + + int local_id = 0; + + while(1) { + // loop until we get a NULL + + struct MpmListIdDataArgs cbdata = { local_id, txv }; + InspectionBuffer *buffer = GetHttp2HNameData(det_ctx, flags, ctx->transforms, + f, &cbdata, list_id, true); + if (buffer == NULL) + break; + + if (buffer->inspect_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, + &det_ctx->mtcu, &det_ctx->pmq, + buffer->inspect, buffer->inspect_len); + } + + local_id++; + } +} + +static int PrefilterMpmHttp2HeaderNameRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id) +{ + //TODOask use PrefilterMpmListId elsewhere + PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx)); + if (pectx == NULL) + return -1; + pectx->list_id = list_id; + pectx->mpm_ctx = mpm_ctx; + pectx->transforms = &mpm_reg->transforms; + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttp2HName, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, + pectx, PrefilterMpmHttp2HNameFree, mpm_reg->name); +} + +static int DetectEngineInspectHttp2HeaderName( + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, + const Signature *s, + Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + int local_id = 0; + + const DetectEngineTransforms *transforms = NULL; + if (!engine->mpm) { + transforms = engine->v2.transforms; + } + + while (1) { + //TODOask use MpmListIdDataArgs elsewhere + struct MpmListIdDataArgs cbdata = { local_id, txv, }; + InspectionBuffer *buffer = GetHttp2HNameData(det_ctx, flags, + transforms, f, &cbdata, engine->sm_list, false); + + if (buffer == NULL || buffer->inspect == NULL) + break; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + + const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, + NULL, f, + (uint8_t *)buffer->inspect, + buffer->inspect_len, + buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE, + DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match == 1) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + local_id++; + } + + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +static int DetectHTTP2headerSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + if (DetectBufferSetActiveList(s, g_http2_header_buffer_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0) + return -1; + + return 0; +} + +static void PrefilterMpmHttp2HeaderFree(void *ptr) +{ + SCFree(ptr); +} + +static InspectionBuffer *GetHttp2HeaderData(DetectEngineThreadCtx *det_ctx, + const uint8_t flags, const DetectEngineTransforms *transforms, + Flow *_f, const struct MpmListIdDataArgs *cbdata, + int list_id, bool first) +{ + SCEnter(); + + InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id); + InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id); + if (buffer == NULL) + return NULL; + if (!first && buffer->inspect != NULL) + return buffer; + + uint32_t b_len = 0; + const uint8_t *b = NULL; + + if (rs_http2_tx_get_header(cbdata->txv, flags, (uint32_t)cbdata->local_id, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +static void PrefilterTxHttp2Header(DetectEngineThreadCtx *det_ctx, + const void *pectx, + Packet *p, Flow *f, void *txv, + const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx; + const MpmCtx *mpm_ctx = ctx->mpm_ctx; + const int list_id = ctx->list_id; + + int local_id = 0; + + while(1) { + // loop until we get a NULL + + struct MpmListIdDataArgs cbdata = { local_id, txv }; + InspectionBuffer *buffer = GetHttp2HeaderData(det_ctx, flags, ctx->transforms, + f, &cbdata, list_id, true); + if (buffer == NULL) + break; + + if (buffer->inspect_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, + &det_ctx->mtcu, &det_ctx->pmq, + buffer->inspect, buffer->inspect_len); + } + + local_id++; + } +} + +static int PrefilterMpmHttp2HeaderRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id) +{ + PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx)); + if (pectx == NULL) + return -1; + pectx->list_id = list_id; + pectx->mpm_ctx = mpm_ctx; + pectx->transforms = &mpm_reg->transforms; + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttp2Header, + mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, + pectx, PrefilterMpmHttp2HeaderFree, mpm_reg->name); +} + +static int DetectEngineInspectHttp2Header( + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, + const Signature *s, + Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + int local_id = 0; + + const DetectEngineTransforms *transforms = NULL; + if (!engine->mpm) { + transforms = engine->v2.transforms; + } + + while (1) { + struct MpmListIdDataArgs cbdata = { local_id, txv, }; + InspectionBuffer *buffer = GetHttp2HeaderData(det_ctx, flags, + transforms, f, &cbdata, engine->sm_list, false); + + if (buffer == NULL || buffer->inspect == NULL) + break; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + + const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, + NULL, f, + (uint8_t *)buffer->inspect, + buffer->inspect_len, + buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE, + DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match == 1) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + local_id++; + } + + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +static bool DetectHttp2HeaderValidateCallback(const Signature *s, const char **sigerror) +{ + const SigMatch *sm = s->init_data->smlists[g_http2_header_buffer_id]; + for ( ; sm != NULL; sm = sm->next) { + if (sm->type != DETECT_CONTENT) + continue; + const DetectContentData *cd = (DetectContentData *)sm->ctx; + bool escaped = false; + bool namevaluesep = false; + for (size_t i = 0; i < cd->content_len; ++i) { + if (escaped) { + if (cd->content[i] == ' ') { + if (namevaluesep) { + *sigerror = "Invalid http2.header string : " + "': ' is a special sequence for separation between name and value " + " and thus can only be present once"; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + return false; + } + namevaluesep = true; + } else if (cd->content[i] != ':') { + *sigerror = "Invalid http2.header string : " + "':' is an escaping character for itself, " + "or space for the separation between name and value"; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + return false; + } + escaped = false; + } else if(cd->content[i] == ':') { + escaped = true; + } + } + if (escaped) { + *sigerror = "Invalid http2.header string : " + "':' is an escaping character for itself, " + "or space for the separation between name and value"; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + return false; + } + } + return true; +} + +#ifdef UNITTESTS +#include "tests/detect-http2.c" +#endif diff --git a/src/detect-http2.h b/src/detect-http2.h new file mode 100644 index 0000000000..11bc17e66c --- /dev/null +++ b/src/detect-http2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef _DETECT_HTTP2_H +#define _DETECT_HTTP2_H + +void DetectHttp2Register(void); + +#endif /* _DETECT_HTTP2_H */ diff --git a/src/output-json-alert.c b/src/output-json-alert.c index a6dfe1142f..32c51f4dbe 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -162,6 +162,24 @@ static void AlertJsonSsh(const Flow *f, JsonBuilder *js) return; } +static void AlertJsonHttp2(const Flow *f, const uint64_t tx_id, JsonBuilder *js) +{ + void *h2_state = FlowGetAppState(f); + if (h2_state) { + void *tx_ptr = rs_http2_state_get_tx(h2_state, tx_id); + JsonBuilderMark mark = { 0, 0, 0 }; + jb_get_mark(js, &mark); + jb_open_object(js, "http2"); + if (rs_http2_log_json(tx_ptr, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + } + + return; +} + static void AlertJsonDnp3(const Flow *f, const uint64_t tx_id, JsonBuilder *js) { DNP3State *dnp3_state = (DNP3State *)FlowGetAppState(f); @@ -504,6 +522,9 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb, case ALPROTO_DNP3: AlertJsonDnp3(p->flow, tx_id, jb); break; + case ALPROTO_HTTP2: + AlertJsonHttp2(p->flow, tx_id, jb); + break; case ALPROTO_DNS: AlertJsonDns(p->flow, tx_id, jb); break; diff --git a/src/output-json-file.c b/src/output-json-file.c index 8e03b27ed8..5932c6ab8f 100644 --- a/src/output-json-file.c +++ b/src/output-json-file.c @@ -62,6 +62,7 @@ #include "output-json-email-common.h" #include "output-json-nfs.h" #include "output-json-smb.h" +#include "output-json-http2.h" #include "app-layer-htp.h" #include "app-layer-htp-xff.h" @@ -172,6 +173,15 @@ JsonBuilder *JsonBuildFileInfoRecord(const Packet *p, const File *ff, jb_restore_mark(js, &mark); } break; + case ALPROTO_HTTP2: + jb_get_mark(js, &mark); + jb_open_object(js, "http2"); + if (EveHTTP2AddMetadata(p->flow, ff->txid, js)) { + jb_close(js); + } else { + jb_restore_mark(js, &mark); + } + break; } jb_set_string(js, "app_proto", AppProtoToString(p->flow->alproto)); diff --git a/src/output-json-http2.c b/src/output-json-http2.c new file mode 100644 index 0000000000..9357c26b05 --- /dev/null +++ b/src/output-json-http2.c @@ -0,0 +1,251 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + * Implements HTTP2 JSON logging portion of the engine. + */ + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "pkt-var.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-print.h" +#include "util-unittest.h" + +#include "util-debug.h" +#include "app-layer-parser.h" +#include "output.h" +#include "app-layer-http2.h" +#include "app-layer.h" +#include "util-privs.h" +#include "util-buffer.h" + +#include "util-logopenfile.h" +#include "util-crypt.h" + +#include "output-json.h" +#include "output-json-http2.h" +#include "rust.h" + +#define MODULE_NAME "LogHttp2Log" + +typedef struct OutputHttp2Ctx_ { + LogFileCtx *file_ctx; + OutputJsonCommonSettings cfg; +} OutputHttp2Ctx; + + +typedef struct JsonHttp2LogThread_ { + OutputHttp2Ctx *http2log_ctx; + MemBuffer *buffer; +} JsonHttp2LogThread; + + +bool EveHTTP2AddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *jb) +{ + void *state = FlowGetAppState(f); + if (state) { + void *tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP2, state, tx_id); + if (tx) { + return rs_http2_log_json(tx, jb); + } + } + return false; +} + +static int JsonHttp2Logger(ThreadVars *tv, void *thread_data, const Packet *p, + Flow *f, void *state, void *txptr, uint64_t tx_id) +{ + JsonHttp2LogThread *aft = (JsonHttp2LogThread *)thread_data; + OutputHttp2Ctx *http2_ctx = aft->http2log_ctx; + + if (unlikely(state == NULL)) { + return 0; + } + + JsonBuilder *js = CreateEveHeaderWithTxId(p, LOG_DIR_FLOW, "http2", NULL, tx_id); + if (unlikely(js == NULL)) + return 0; + + EveAddCommonOptions(&http2_ctx->cfg, p, f, js); + + /* reset */ + MemBufferReset(aft->buffer); + + jb_open_object(js, "http2"); + if (!rs_http2_log_json(txptr, js)) { + goto end; + } + jb_close(js); + OutputJsonBuilderBuffer(js, http2_ctx->file_ctx, &aft->buffer); +end: + jb_free(js); + return 0; +} + +static TmEcode JsonHttp2LogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + JsonHttp2LogThread *aft = SCMalloc(sizeof(JsonHttp2LogThread)); + if (unlikely(aft == NULL)) + return TM_ECODE_FAILED; + memset(aft, 0, sizeof(JsonHttp2LogThread)); + + if(initdata == NULL) + { + SCLogDebug("Error getting context for EveLogHTTP2. \"initdata\" argument NULL"); + SCFree(aft); + return TM_ECODE_FAILED; + } + + /* Use the Ouptut Context (file pointer and mutex) */ + aft->http2log_ctx = ((OutputCtx *)initdata)->data; + + aft->buffer = MemBufferCreateNew(JSON_OUTPUT_BUFFER_SIZE); + if (aft->buffer == NULL) { + SCFree(aft); + return TM_ECODE_FAILED; + } + + *data = (void *)aft; + return TM_ECODE_OK; +} + +static TmEcode JsonHttp2LogThreadDeinit(ThreadVars *t, void *data) +{ + JsonHttp2LogThread *aft = (JsonHttp2LogThread *)data; + if (aft == NULL) { + return TM_ECODE_OK; + } + + MemBufferFree(aft->buffer); + /* clear memory */ + memset(aft, 0, sizeof(JsonHttp2LogThread)); + + SCFree(aft); + return TM_ECODE_OK; +} + +static void OutputHttp2LogDeinit(OutputCtx *output_ctx) +{ + OutputHttp2Ctx *http2_ctx = output_ctx->data; + LogFileCtx *logfile_ctx = http2_ctx->file_ctx; + LogFileFreeCtx(logfile_ctx); + SCFree(http2_ctx); + SCFree(output_ctx); +} + +#define DEFAULT_LOG_FILENAME "http2.json" +static OutputInitResult OutputHttp2LogInit(ConfNode *conf) +{ + OutputInitResult result = { NULL, false }; + LogFileCtx *file_ctx = LogFileNewCtx(); + if(file_ctx == NULL) { + SCLogError(SC_ERR_HTTP2_LOG_GENERIC, "couldn't create new file_ctx"); + return result; + } + + if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) { + LogFileFreeCtx(file_ctx); + return result; + } + + OutputHttp2Ctx *http2_ctx = SCMalloc(sizeof(OutputHttp2Ctx)); + if (unlikely(http2_ctx == NULL)) { + LogFileFreeCtx(file_ctx); + return result; + } + + OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); + if (unlikely(output_ctx == NULL)) { + LogFileFreeCtx(file_ctx); + SCFree(http2_ctx); + return result; + } + + http2_ctx->file_ctx = file_ctx; + + output_ctx->data = http2_ctx; + output_ctx->DeInit = OutputHttp2LogDeinit; + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static void OutputHttp2LogDeinitSub(OutputCtx *output_ctx) +{ + OutputHttp2Ctx *http2_ctx = output_ctx->data; + SCFree(http2_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputHttp2LogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ojc = parent_ctx->data; + + OutputHttp2Ctx *http2_ctx = SCMalloc(sizeof(OutputHttp2Ctx)); + if (unlikely(http2_ctx == NULL)) + return result; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); + if (unlikely(output_ctx == NULL)) { + SCFree(http2_ctx); + return result; + } + + http2_ctx->file_ctx = ojc->file_ctx; + http2_ctx->cfg = ojc->cfg; + + output_ctx->data = http2_ctx; + output_ctx->DeInit = OutputHttp2LogDeinitSub; + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +void JsonHttp2LogRegister (void) +{ + /* register as separate module */ + OutputRegisterTxModuleWithProgress(LOGGER_JSON_HTTP2, + MODULE_NAME, "http2-json-log", + OutputHttp2LogInit, ALPROTO_HTTP2, JsonHttp2Logger, + HTTP2StateClosed, HTTP2StateClosed, + JsonHttp2LogThreadInit, JsonHttp2LogThreadDeinit, NULL); + + /* also register as child of eve-log */ + OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_HTTP2, + "eve-log", MODULE_NAME, "eve-log.http2", + OutputHttp2LogInitSub, ALPROTO_HTTP2, JsonHttp2Logger, + HTTP2StateClosed, HTTP2StateClosed, + JsonHttp2LogThreadInit, JsonHttp2LogThreadDeinit, NULL); +} diff --git a/src/output-json-http2.h b/src/output-json-http2.h new file mode 100644 index 0000000000..66bf2ade96 --- /dev/null +++ b/src/output-json-http2.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef __OUTPUT_JSON_HTTP2_H__ +#define __OUTPUT_JSON_HTTP2_H__ + +void JsonHttp2LogRegister(void); +bool EveHTTP2AddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *jb); + +#endif /* __OUTPUT_JSON_HTTP2_H__ */ diff --git a/src/output.c b/src/output.c index 379a58768c..b41f2b488d 100644 --- a/src/output.c +++ b/src/output.c @@ -79,6 +79,7 @@ #include "output-json-template.h" #include "output-json-template-rust.h" #include "output-json-rdp.h" +#include "output-json-http2.h" #include "output-lua.h" #include "output-json-dnp3.h" #include "output-json-metadata.h" @@ -1094,6 +1095,7 @@ void OutputRegisterLoggers(void) /* http log */ LogHttpLogRegister(); JsonHttpLogRegister(); + JsonHttp2LogRegister(); /* tls log */ LogTlsLogRegister(); JsonTlsLogRegister(); diff --git a/src/suricata-common.h b/src/suricata-common.h index f1e7001376..e9d52a080e 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -468,6 +468,7 @@ typedef enum { LOGGER_JSON_TEMPLATE, LOGGER_JSON_RDP, LOGGER_JSON_DCERPC, + LOGGER_JSON_HTTP2, LOGGER_ALERT_DEBUG, LOGGER_ALERT_FAST, diff --git a/src/tests/detect-http2.c b/src/tests/detect-http2.c new file mode 100644 index 0000000000..2ce1bd4481 --- /dev/null +++ b/src/tests/detect-http2.c @@ -0,0 +1,161 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "../suricata-common.h" + +#include "../detect-engine.h" + +#include "../detect-http2.h" + +#include "../util-unittest.h" + +/** + * \test signature with a valid http2.frametype value. + */ + +static int DetectHTTP2frameTypeParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.frametype:GOAWAY; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \brief this function registers unit tests for DetectHTTP2frameType + */ +void DetectHTTP2frameTypeRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2frameTypeParseTest01", DetectHTTP2frameTypeParseTest01); +} + +/** + * \test signature with a valid http2.errorcode value. + */ + +static int DetectHTTP2errorCodeParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.errorcode:NO_ERROR; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +void DetectHTTP2errorCodeRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2errorCodeParseTest01", DetectHTTP2errorCodeParseTest01); +} + +/** + * \test signature with a valid http2.priority value. + */ + +static int DetectHTTP2priorityParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.priority:>100; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +void DetectHTTP2priorityRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2priorityParseTest01", DetectHTTP2priorityParseTest01); +} + +/** + * \test signature with a valid http2.window value. + */ + +static int DetectHTTP2windowParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.window:<42; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +void DetectHTTP2windowRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2windowParseTest01", DetectHTTP2windowParseTest01); +} + + +/** + * \test signature with a valid http2.settings value. + */ + +static int DetectHTTP2settingsParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.settings:SETTINGS_MAX_HEADER_LIST_SIZE >1024; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +void DetectHTTP2settingsRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2settingsParseTest01", DetectHTTP2settingsParseTest01); +} + + +/** +* \test signature with a valid http2.size_update value. +*/ + +static int DetectHTTP2sizeUpdateParseTest01 (void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig(de_ctx, + "alert http2 any any -> any any (http2.size_update:>4096; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +void DetectHTTP2sizeUpdateRegisterTests(void) +{ + UtRegisterTest("DetectHTTP2sizeUpdateParseTest01", DetectHTTP2sizeUpdateParseTest01); +} diff --git a/src/util-error.c b/src/util-error.c index 8cb634af98..822f7033de 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -108,6 +108,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_DEBUG_LOG_GENERIC); CASE_CODE (SC_ERR_UNIFIED_LOG_GENERIC); CASE_CODE (SC_ERR_HTTP_LOG_GENERIC); + CASE_CODE (SC_ERR_HTTP2_LOG_GENERIC); CASE_CODE (SC_ERR_FTP_LOG_GENERIC); CASE_CODE (SC_ERR_UNIFIED_ALERT_GENERIC); CASE_CODE (SC_ERR_UNIFIED2_ALERT_GENERIC); diff --git a/src/util-error.h b/src/util-error.h index d3260af10e..260f4697ae 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -363,6 +363,7 @@ typedef enum { SC_WARN_HASSH_DISABLED, SC_WARN_FILESTORE_CONFIG, SC_WARN_PATH_READ_ERROR, + SC_ERR_HTTP2_LOG_GENERIC, SC_ERR_MAX } SCError; diff --git a/src/util-profiling.c b/src/util-profiling.c index 37a37794f8..cc6d5c0856 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1322,6 +1322,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_TEMPLATE); CASE_CODE (LOGGER_JSON_RDP); CASE_CODE (LOGGER_JSON_DCERPC); + CASE_CODE (LOGGER_JSON_HTTP2); CASE_CODE (LOGGER_TLS_STORE); CASE_CODE (LOGGER_TLS); CASE_CODE (LOGGER_FILE_STORE); diff --git a/suricata.yaml.in b/suricata.yaml.in index f747c50140..8833480cc4 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -281,6 +281,7 @@ outputs: - ssh - mqtt: # passwords: yes # enable output of passwords + - http2 - stats: totals: yes # stats for all threads merged together threads: no # per thread stats @@ -756,6 +757,8 @@ app-layer: ssh: enabled: yes #hassh: yes + http2: + enabled: yes smtp: enabled: yes raw-extraction: no -- 2.47.2