--- /dev/null
+# 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;)
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)]
#[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,
--- /dev/null
+/* 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<parser::DetectHTTP2settingsSigCtx> = transmute(ctx);
+}
+
+fn http2_detect_settings_match(
+ set: &Vec<parser::HTTP2FrameSettings>, 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<parser::DetectU64Data> = 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<u8> {
+ //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;
+}
--- /dev/null
+/* 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)
+ }
+ }
+}
--- /dev/null
+/* 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<parser::HTTP2FrameSettings>),
+ 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<HTTP2Frame>,
+ pub frames_ts: Vec<HTTP2Frame>,
+
+ 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<u8>,
+}
+
+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<HTTP2Event> {
+ 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<parser::HTTP2FrameHeaderBlock>,
+ dynamic_headers_tc: Vec<parser::HTTP2FrameHeaderBlock>,
+ transactions: Vec<HTTP2Transaction>,
+ 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<HTTP2State> = 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.");
+ }
+}
--- /dev/null
+/* 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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)
+);
--- /dev/null
+/* 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<parser::HTTP2FrameHeaderBlock>, 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<HTTP2Frame>, js: &mut JsonBuilder) -> Result<bool, JsonError> {
+ 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<parser::HTTP2ErrorCode> =
+ 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<parser::HTTP2ErrorCode> =
+ 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<bool, JsonError> {
+ 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;
+}
--- /dev/null
+/* 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;
--- /dev/null
+/* 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<Self, Self::Err> {
+ 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<HTTP2FrameHeader>,
+ 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<Self, Self::Err> {
+ 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<HTTP2FrameGoAway>,
+ do_parse!(
+ errorcode: be_u32 >>
+ (HTTP2FrameGoAway{errorcode})
+ )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameRstStream {
+ pub errorcode: u32, ////HTTP2ErrorCode
+}
+
+named!(pub http2_parse_frame_rststream<HTTP2FrameRstStream>,
+ do_parse!(
+ errorcode: be_u32 >>
+ (HTTP2FrameRstStream{errorcode})
+ )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FramePriority {
+ pub weight: u8,
+}
+
+named!(pub http2_parse_frame_priority<HTTP2FramePriority>,
+ 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<HTTP2FrameWindowUpdate>,
+ 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<HTTP2FrameHeadersPriority>,
+ 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<HTTP2FrameHeaderBlock>,
+) -> Option<HTTP2FrameHeaderBlock> {
+ 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<u8>,
+ pub value: Vec<u8>,
+ pub error: HTTP2HeaderDecodeStatus,
+ pub sizeupdate: u64,
+}
+
+fn http2_parse_headers_block_indexed<'a>(
+ input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> 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<u8>> {
+ 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<HTTP2FrameHeaderBlock>,
+) -> 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<HTTP2FrameHeaderBlock>,
+) -> 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<HTTP2FrameHeaderBlock>,
+) -> 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<HTTP2FrameHeaderBlock>,
+) -> 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<HTTP2FrameHeaderBlock>,
+) -> 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<u8>,
+ pub priority: Option<HTTP2FrameHeadersPriority>,
+ pub blocks: Vec<HTTP2FrameHeaderBlock>,
+}
+
+//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<HTTP2FrameHeaderBlock>,
+) -> 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<u8>,
+ pub reserved: u8,
+ pub stream_id: u32,
+ pub blocks: Vec<HTTP2FrameHeaderBlock>,
+}
+
+pub fn http2_parse_frame_push_promise<'a>(
+ input: &'a [u8], flags: u8, dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> 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<HTTP2FrameHeaderBlock>,
+}
+
+pub fn http2_parse_frame_continuation<'a>(
+ input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> 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<Self, Self::Err> {
+ 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<DetectU32Data>, //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::<u32>().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::<u32>().ok()) >>
+ opt!( is_a!( " " ) ) >>
+ tag!("-") >>
+ opt!( is_a!( " " ) ) >>
+ valrange : map_opt!(digit1, |s: &str| s.parse::<u32>().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::<u32>().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::<u32>().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::<u64>().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::<u64>().ok()) >>
+ opt!( is_a!( " " ) ) >>
+ tag!("-") >>
+ opt!( is_a!( " " ) ) >>
+ valrange : map_opt!(digit1, |s: &str| s.parse::<u64>().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::<u64>().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::<u64>().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<HTTP2FrameSettings>,
+ do_parse!(
+ id: map_opt!(be_u16, num::FromPrimitive::from_u16)
+ >> value: be_u32
+ >> (HTTP2FrameSettings { id, value })
+ )
+);
+
+named!(pub http2_parse_frame_settings<Vec<HTTP2FrameSettings>>,
+ 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<HTTP2FrameHeaderBlock> =
+ 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);
+ }
+ }
+ }
+
+}
pub mod x509;
pub mod asn1;
pub mod ssh;
+pub mod http2;
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 \
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 \
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 \
}
+/**
+ * \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
case ALPROTO_HTTP:
PacketToDataProtoHTTP(p, pa, alert);
break;
+ case ALPROTO_HTTP2:
+ PacketToDataProtoHTTP(p, pa, alert);
+ break;
case ALPROTO_TLS:
PacketToDataProtoTLS(p, pa, alert);
break;
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ *
+ * 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
+}
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ */
+
+#ifndef __APP_LAYER_HTTP2_H__
+#define __APP_LAYER_HTTP2_H__
+
+void RegisterHTTP2Parsers(void);
+
+#endif /* __APP_LAYER_HTTP2_H__ */
#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"
RegisterMQTTParsers();
RegisterTemplateParsers();
RegisterRdpParsers();
+ RegisterHTTP2Parsers();
/** IMAP */
AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap");
case ALPROTO_RDP:
proto_name = "rdp";
break;
+ case ALPROTO_HTTP2:
+ proto_name = "http2";
+ break;
case ALPROTO_FAILED:
proto_name = "failed";
break;
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;
ALPROTO_TEMPLATE,
ALPROTO_TEMPLATE_RUST,
ALPROTO_RDP,
+ ALPROTO_HTTP2,
/* used by the probing parser when alproto detection fails
* permanently for that particular stream */
#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"
DetectHttpStatMsgRegister();
DetectHttpStatCodeRegister();
+ DetectHttp2Register();
DetectDnsQueryRegister();
DetectDnsOpcodeRegister();
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,
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,
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");
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;
}
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],
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ *
+ */
+
+#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
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ */
+
+#ifndef _DETECT_HTTP2_H
+#define _DETECT_HTTP2_H
+
+void DetectHttp2Register(void);
+
+#endif /* _DETECT_HTTP2_H */
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);
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;
#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"
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));
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ *
+ * 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);
+}
--- /dev/null
+/* 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 <p.antoine@catenacyber.fr>
+ */
+
+#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__ */
#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"
/* http log */
LogHttpLogRegister();
JsonHttpLogRegister();
+ JsonHttp2LogRegister();
/* tls log */
LogTlsLogRegister();
JsonTlsLogRegister();
LOGGER_JSON_TEMPLATE,
LOGGER_JSON_RDP,
LOGGER_JSON_DCERPC,
+ LOGGER_JSON_HTTP2,
LOGGER_ALERT_DEBUG,
LOGGER_ALERT_FAST,
--- /dev/null
+/* 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);
+}
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);
SC_WARN_HASSH_DISABLED,
SC_WARN_FILESTORE_CONFIG,
SC_WARN_PATH_READ_ERROR,
+ SC_ERR_HTTP2_LOG_GENERIC,
SC_ERR_MAX
} SCError;
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);
- ssh
- mqtt:
# passwords: yes # enable output of passwords
+ - http2
- stats:
totals: yes # stats for all threads merged together
threads: no # per thread stats
ssh:
enabled: yes
#hassh: yes
+ http2:
+ enabled: yes
smtp:
enabled: yes
raw-extraction: no