From ebfe9e7390fe1f5ec88d8d4116cc1a858901f95f Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Wed, 11 Mar 2020 10:02:06 +0100 Subject: [PATCH 01/17] http2: first skeleton --- rust/src/http2/http2.rs | 493 +++++++++++++++++++++++++++++++++++++++ rust/src/http2/logger.rs | 40 ++++ rust/src/http2/mod.rs | 21 ++ rust/src/http2/parser.rs | 78 +++++++ rust/src/lib.rs | 1 + src/Makefile.am | 1 + src/app-layer-http2.c | 45 ++++ src/app-layer-http2.h | 30 +++ src/app-layer-protos.c | 3 + src/app-layer-protos.h | 1 + 10 files changed, 713 insertions(+) create mode 100644 rust/src/http2/http2.rs create mode 100644 rust/src/http2/logger.rs create mode 100644 rust/src/http2/mod.rs create mode 100644 rust/src/http2/parser.rs create mode 100644 src/app-layer-http2.c create mode 100644 src/app-layer-http2.h diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs new file mode 100644 index 000000000000..5c769c29f83d --- /dev/null +++ b/rust/src/http2/http2.rs @@ -0,0 +1,493 @@ +/* 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::parser; +use crate::applayer::{self, LoggerFlags}; +use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use crate::log::*; +use crate::parser::*; +use nom; +use std; +use std::ffi::CString; +use std::mem::transmute; + +static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN; + +const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384; + +pub struct HTTP2Transaction { + tx_id: u64, + pub request: Option, + pub response: Option, + + logged: LoggerFlags, + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, +} + +impl HTTP2Transaction { + pub fn new() -> HTTP2Transaction { + HTTP2Transaction { + tx_id: 0, + request: None, + response: None, + logged: LoggerFlags::new(), + de_state: None, + events: std::ptr::null_mut(), + } + } + + 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); + } + } +} + +impl Drop for HTTP2Transaction { + fn drop(&mut self) { + self.free(); + } +} + +pub struct HTTP2State { + tx_id: u64, + request_buffer: Vec, + response_buffer: Vec, + transactions: Vec, +} + +impl HTTP2State { + pub fn new() -> Self { + Self { + tx_id: 0, + request_buffer: Vec::new(), + response_buffer: Vec::new(), + transactions: Vec::new(), + } + } + + // 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 new_tx(&mut self) -> HTTP2Transaction { + let mut tx = HTTP2Transaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn find_request(&mut self) -> Option<&mut HTTP2Transaction> { + for tx in &mut self.transactions { + if tx.response.is_none() { + return Some(tx); + } + } + None + } + + fn parse_request(&mut self, input: &[u8]) -> bool { + // We're not interested in empty requests. + if input.len() == 0 { + return true; + } + + // For simplicity, always extend the buffer and work on it. + self.request_buffer.extend(input); + + return true; + } + + fn parse_response(&mut self, input: &[u8]) -> bool { + // We're not interested in empty responses. + if input.len() == 0 { + return true; + } + + // For simplicity, always extend the buffer and work on it. + self.response_buffer.extend(input); + + return true; + } + + 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); + +/// C entry point for a probing parser. +#[no_mangle] +pub extern "C" fn rs_http2_probing_parser( + _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 + { + //TODO why unsafe ? + return unsafe { ALPROTO_FAILED }; + } + //TODO check known type + 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 _drop: Box = unsafe { transmute(state) }; +} + +#[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_request( + _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, +) -> i32 { + let eof = unsafe { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { + true + } else { + false + } + }; + + if eof { + // If needed, handled EOF, or pass it into the parser. + } + + let state = cast_pointer!(state, HTTP2State); + let buf = build_slice!(input, input_len as usize); + if state.parse_request(buf) { + return 1; + } + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_http2_parse_response( + _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, +) -> i32 { + let _eof = unsafe { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { + true + } else { + false + } + }; + let state = cast_pointer!(state, HTTP2State); + let buf = build_slice!(input, input_len as usize); + if state.parse_response(buf) { + return 1; + } + return -1; +} + +#[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 { + // This parser uses 1 to signal transaction completion status. + return 1; +} + +#[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 { + let tx = cast_pointer!(tx, HTTP2Transaction); + + // Transaction is done if we have a response. + if tx.response.is_some() { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_logged( + _state: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, +) -> u32 { + let tx = cast_pointer!(tx, HTTP2Transaction); + return tx.logged.get(); +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_set_logged( + _state: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, + logged: u32, +) { + let tx = cast_pointer!(tx, HTTP2Transaction); + tx.logged.set(logged); +} + +#[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 { + return -1; +} + +#[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 { + return -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(); + } + } +} + +/// Get the request buffer for a transaction from C. +/// +/// No required for parsing, but an example function for retrieving a +/// pointer to the request buffer from C for detection. +#[no_mangle] +pub extern "C" fn rs_http2_get_request_buffer( + tx: *mut std::os::raw::c_void, + buf: *mut *const u8, + len: *mut u32, +) -> u8 { + let tx = cast_pointer!(tx, HTTP2Transaction); + if let Some(ref request) = tx.request { + if request.len() > 0 { + unsafe { + *len = request.len() as u32; + *buf = request.as_ptr(); + } + return 1; + } + } + return 0; +} + +/// Get the response buffer for a transaction from C. +#[no_mangle] +pub extern "C" fn rs_http2_get_response_buffer( + tx: *mut std::os::raw::c_void, + buf: *mut *const u8, + len: *mut u32, +) -> u8 { + let tx = cast_pointer!(tx, HTTP2Transaction); + if let Some(ref response) = tx.response { + if response.len() > 0 { + unsafe { + *len = response.len() as u32; + *buf = response.as_ptr(); + } + return 1; + } + } + return 0; +} + +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"http2-rust\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_register_parser() { + 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: Some(rs_http2_probing_parser), + probe_tc: Some(rs_http2_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_http2_state_new, + state_free: rs_http2_state_free, + tx_free: rs_http2_state_tx_free, + parse_ts: rs_http2_parse_request, + parse_tc: rs_http2_parse_response, + get_tx_count: rs_http2_state_get_tx_count, + get_tx: rs_http2_state_get_tx, + //TODO + tx_get_comp_st: rs_http2_state_progress_completion_status, + tx_get_progress: rs_http2_tx_get_alstate_progress, + get_tx_logged: Some(rs_http2_tx_get_logged), + set_tx_logged: Some(rs_http2_tx_set_logged), + 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_tx_mpm_id: None, + set_tx_mpm_id: None, + get_files: None, + get_tx_iterator: Some(rs_http2_state_get_tx_iterator), + get_tx_detect_flags: None, + set_tx_detect_flags: None, + }; + + 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); + } + SCLogNotice!("Rust http2 parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for HTTP2."); + } +} diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs new file mode 100644 index 000000000000..aa70b3dff5a5 --- /dev/null +++ b/rust/src/http2/logger.rs @@ -0,0 +1,40 @@ +/* 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 std; +use crate::json::*; +use super::http2::HTTP2Transaction; + +fn log_http2(tx: &HTTP2Transaction) -> Option { + let js = Json::object(); + if let Some(ref request) = tx.request { + js.set_string("request", request); + } + if let Some(ref response) = tx.response { + js.set_string("response", response); + } + return Some(js); +} + +#[no_mangle] +pub extern "C" fn rs_http2_logger_log(tx: *mut std::os::raw::c_void) -> *mut JsonT { + let tx = cast_pointer!(tx, HTTP2Transaction); + match log_http2(tx) { + Some(js) => js.unwrap(), + None => std::ptr::null_mut(), + } +} diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs new file mode 100644 index 000000000000..63a0e6916902 --- /dev/null +++ b/rust/src/http2/mod.rs @@ -0,0 +1,21 @@ +/* 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. + */ + +//TODO pub mod detect; +pub mod logger; +mod parser; +pub mod http2; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs new file mode 100644 index 000000000000..1cf2a1cdcb46 --- /dev/null +++ b/rust/src/http2/parser.rs @@ -0,0 +1,78 @@ +/* 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::number::streaming::be_u8; + +#[derive(PartialEq)] +pub struct HTTP2FrameHeader { + pub length: u32, + ftype: u8, + pub flags: u8, + pub reserved: u8, + stream_id: u32, +} + +named!(pub http2_parse_frame_header, + do_parse!( + length: bits!( take_bits!(24u32) ) >> + ftype: be_u8 >> + flags: be_u8 >> + stream_id: bits!( tuple!( take_bits!(1u8), + take_bits!(31u32) ) ) >> + (HTTP2FrameHeader{length, ftype, flags, + reserved:stream_id.0, + stream_id:stream_id.1}) + ) +); + +#[cfg(test)] +mod tests { + + use super::*; + use nom::*; + + /// Simple test of some valid data. + #[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, 4); + assert_eq!(frame.flags, 0); + assert_eq!(frame.reserved, 0); + assert_eq!(frame.stream_id, 0); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 6); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 30624ba7b102..5ba149ccb4f9 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -70,3 +70,4 @@ pub mod rfb; pub mod applayertemplate; pub mod rdp; pub mod x509; +pub mod http2; diff --git a/src/Makefile.am b/src/Makefile.am index c47012c346c0..a4320a6d1276 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -35,6 +35,7 @@ app-layer-htp-file.c app-layer-htp-file.h \ app-layer-htp-libhtp.c app-layer-htp-libhtp.h \ app-layer-htp-mem.c app-layer-htp-mem.h \ app-layer-htp-xff.c app-layer-htp-xff.h \ +app-layer-http2.c app-layer-http2.h \ app-layer-modbus.c app-layer-modbus.h \ app-layer-parser.c app-layer-parser.h \ app-layer-protos.c app-layer-protos.h \ diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c new file mode 100644 index 000000000000..e7ce0425595c --- /dev/null +++ b/src/app-layer-http2.c @@ -0,0 +1,45 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + * Parser for HTTP2, RFC 7540 + */ + +#include "suricata-common.h" +#include "stream.h" +#include "conf.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "app-layer-http2.h" +#include "rust.h" + +void RegisterHTTP2Parsers(void) +{ + rs_http2_register_parser(); + +#ifdef UNITTESTS + //TODO +#endif +} diff --git a/src/app-layer-http2.h b/src/app-layer-http2.h new file mode 100644 index 000000000000..14273681e17f --- /dev/null +++ b/src/app-layer-http2.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef __APP_LAYER_HTTP2_H__ +#define __APP_LAYER_HTTP2_H__ + +void RegisterHTTP2Parsers(void); +//TODO void HTTP2ParserRegisterTests(void); + +#endif /* __APP_LAYER_HTTP2_H__ */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 9db24bac995e..875b25d58298 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -114,6 +114,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_RDP: proto_name = "rdp"; break; + case ALPROTO_HTTP2: + proto_name = "http2"; + break; case ALPROTO_FAILED: proto_name = "failed"; break; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 589d68d85356..ac47c4b88b15 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -55,6 +55,7 @@ enum AppProtoEnum { ALPROTO_TEMPLATE, ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, + ALPROTO_HTTP2, /* used by the probing parser when alproto detection fails * permanently for that particular stream */ From 7cf1c69c3e7746b087ea6738681b58d17c78e60f Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Wed, 11 Mar 2020 10:20:35 +0100 Subject: [PATCH 02/17] right probing --- rust/src/http2/http2.rs | 8 ++++---- rust/src/http2/logger.rs | 4 ++-- rust/src/http2/mod.rs | 2 +- rust/src/http2/parser.rs | 3 ++- src/app-layer-http2.c | 19 +++++++++++++++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 5c769c29f83d..322ad81e4b78 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -180,7 +180,7 @@ export_tx_set_detect_state!(rs_http2_tx_set_detect_state, HTTP2Transaction); /// C entry point for a probing parser. #[no_mangle] -pub extern "C" fn rs_http2_probing_parser( +pub extern "C" fn rs_http2_probing_parser_tc( _flow: *const Flow, _direction: u8, input: *const u8, @@ -194,11 +194,11 @@ pub extern "C" fn rs_http2_probing_parser( if header.reserved != 0 || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE || header.flags & 0xFE != 0 + || header.ftype != 4 { //TODO why unsafe ? return unsafe { ALPROTO_FAILED }; } - //TODO check known type return unsafe { ALPROTO_HTTP2 }; } Err(nom::Err::Incomplete(_)) => { @@ -447,8 +447,8 @@ pub unsafe extern "C" fn rs_http2_register_parser() { name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, default_port: default_port.as_ptr(), ipproto: IPPROTO_TCP, - probe_ts: Some(rs_http2_probing_parser), - probe_tc: Some(rs_http2_probing_parser), + probe_ts: None, // big magic string + probe_tc: Some(rs_http2_probing_parser_tc), min_depth: 0, max_depth: 16, state_new: rs_http2_state_new, diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index aa70b3dff5a5..f006e3774259 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -15,9 +15,9 @@ * 02110-1301, USA. */ -use std; -use crate::json::*; use super::http2::HTTP2Transaction; +use crate::json::*; +use std; fn log_http2(tx: &HTTP2Transaction) -> Option { let js = Json::object(); diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs index 63a0e6916902..e221ccd892d8 100644 --- a/rust/src/http2/mod.rs +++ b/rust/src/http2/mod.rs @@ -16,6 +16,6 @@ */ //TODO pub mod detect; +pub mod http2; pub mod logger; mod parser; -pub mod http2; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 1cf2a1cdcb46..8b1cf6c52e99 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -20,7 +20,8 @@ use nom::number::streaming::be_u8; #[derive(PartialEq)] pub struct HTTP2FrameHeader { pub length: u32, - ftype: u8, + //TODO explicit enum for type settings=4 + pub ftype: u8, pub flags: u8, pub reserved: u8, stream_id: u32, diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c index e7ce0425595c..cd516a32ebab 100644 --- a/src/app-layer-http2.c +++ b/src/app-layer-http2.c @@ -35,8 +35,27 @@ #include "app-layer-http2.h" #include "rust.h" +static int HTTP2RegisterPatternsForProtocolDetection(void) +{ + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_HTTP2, + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", + 24, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + return 0; +} + void RegisterHTTP2Parsers(void) { + const char *proto_name = "http2"; + + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_HTTP2, proto_name); + if (HTTP2RegisterPatternsForProtocolDetection() < 0) + return; + } + rs_http2_register_parser(); #ifdef UNITTESTS From 37b5cc4ec6171b9a1f05513a0f088a2b7ee0e87c Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Thu, 12 Mar 2020 09:19:09 +0100 Subject: [PATCH 03/17] http2 parse --- rust/src/http2/http2.rs | 312 ++++++++++++++++++++++++++++++++------- rust/src/http2/logger.rs | 1 + rust/src/http2/mod.rs | 2 +- rust/src/http2/parser.rs | 40 ++++- 4 files changed, 293 insertions(+), 62 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 322ad81e4b78..9b8cfc4c2673 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -22,13 +22,20 @@ use crate::log::*; use crate::parser::*; use nom; use std; -use std::ffi::CString; +use std::ffi::{CStr, CString}; use std::mem::transmute; static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN; const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384; +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq)] +pub enum HTTP2ConnectionState { + Http2StateInit = 0, + Http2StateMagicDone = 1, +} + pub struct HTTP2Transaction { tx_id: u64, pub request: Option, @@ -67,11 +74,31 @@ impl Drop for HTTP2Transaction { } } +//TODO rules file +#[repr(u32)] +pub enum HTTP2Event { + InvalidFrameHeader = 0, + InvalidClientMagic, +} + +impl HTTP2Event { + fn from_i32(value: i32) -> Option { + match value { + 0 => Some(HTTP2Event::InvalidFrameHeader), + 1 => Some(HTTP2Event::InvalidClientMagic), + _ => None, + } + } +} + pub struct HTTP2State { tx_id: u64, request_buffer: Vec, response_buffer: Vec, + request_frame_size: u32, + response_frame_size: u32, transactions: Vec, + progress: HTTP2ConnectionState, } impl HTTP2State { @@ -80,10 +107,23 @@ impl HTTP2State { tx_id: 0, request_buffer: Vec::new(), response_buffer: Vec::new(), + request_frame_size: 0, + response_frame_size: 0, transactions: Vec::new(), + progress: HTTP2ConnectionState::Http2StateInit, } } + 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(); @@ -127,27 +167,133 @@ impl HTTP2State { None } - fn parse_request(&mut self, input: &[u8]) -> bool { - // We're not interested in empty requests. - if input.len() == 0 { - return true; + fn parse_ts(&mut self, input: &[u8]) -> bool { + let mut toparse = input; + //first consume frame bytes + if self.request_frame_size > 0 { + let ilen = input.len() as u32; + if self.request_frame_size >= ilen { + self.request_frame_size -= ilen; + return true; + } else { + let start = self.request_frame_size as usize; + toparse = &toparse[start..]; + self.request_frame_size = 0; + } + } + //second extend buffer if present + if self.request_buffer.len() > 0 { + self.request_buffer.extend(toparse); + //parse one header locally as we borrow self and self.request_buffer + match parser::http2_parse_frame_header(&self.request_buffer) { + Ok((rem, head)) => { + let hl = head.length as usize; + if rem.len() < hl { + let rl = rem.len() as u32; + self.request_frame_size = head.length - rl; + return true; + } else { + toparse = &toparse[toparse.len() - rem.len() - hl..]; + } + } + Err(nom::Err::Incomplete(_)) => { + return true; + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return false; + } + } + } + //then parse all we can + while toparse.len() > 0 { + match parser::http2_parse_frame_header(toparse) { + Ok((rem, head)) => { + let hl = head.length as usize; + if rem.len() < hl { + let rl = rem.len() as u32; + self.request_frame_size = head.length - rl; + return true; + } else { + toparse = &rem[rem.len() - hl..]; + } + } + Err(nom::Err::Incomplete(_)) => { + self.request_buffer.extend(toparse); + return true; + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return false; + } + } } - - // For simplicity, always extend the buffer and work on it. - self.request_buffer.extend(input); - return true; } - fn parse_response(&mut self, input: &[u8]) -> bool { - // We're not interested in empty responses. - if input.len() == 0 { - return true; + fn parse_tc(&mut self, input: &[u8]) -> bool { + let mut toparse = input; + //first consume frame bytes + if self.response_frame_size > 0 { + let ilen = input.len() as u32; + if self.response_frame_size >= ilen { + self.response_frame_size -= ilen; + return true; + } else { + let start = self.response_frame_size as usize; + toparse = &toparse[start..]; + self.response_frame_size = 0; + } + } + //second extend buffer if present + if self.response_buffer.len() > 0 { + self.response_buffer.extend(toparse); + //parse one header locally as we borrow self and self.response_buffer + match parser::http2_parse_frame_header(&self.response_buffer) { + Ok((rem, head)) => { + //TODO parse deeper based on frame type + let hl = head.length as usize; + if rem.len() < hl { + let rl = rem.len() as u32; + self.response_frame_size = head.length - rl; + return true; + } else { + toparse = &toparse[toparse.len() - rem.len() - hl..]; + } + } + Err(nom::Err::Incomplete(_)) => { + return true; + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return false; + } + } + } + //then parse all we can + while toparse.len() > 0 { + match parser::http2_parse_frame_header(toparse) { + Ok((rem, head)) => { + //TODO parse deeper based on frame type + let hl = head.length as usize; + if rem.len() < hl { + let rl = rem.len() as u32; + self.response_frame_size = head.length - rl; + return true; + } else { + toparse = &rem[rem.len() - hl..]; + } + } + Err(nom::Err::Incomplete(_)) => { + self.response_buffer.extend(toparse); + return true; + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return false; + } + } } - - // For simplicity, always extend the buffer and work on it. - self.response_buffer.extend(input); - return true; } @@ -194,9 +340,8 @@ pub extern "C" fn rs_http2_probing_parser_tc( if header.reserved != 0 || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE || header.flags & 0xFE != 0 - || header.ftype != 4 + || header.ftype != parser::HTTP2FrameType::Http2FrameTypeSETTINGS { - //TODO why unsafe ? return unsafe { ALPROTO_FAILED }; } return unsafe { ALPROTO_HTTP2 }; @@ -232,55 +377,80 @@ pub extern "C" fn rs_http2_state_tx_free(state: *mut std::os::raw::c_void, tx_id } #[no_mangle] -pub extern "C" fn rs_http2_parse_request( +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, + _pstate: *mut std::os::raw::c_void, input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, ) -> i32 { - let eof = unsafe { - if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { - true + let state = cast_pointer!(state, HTTP2State); + let mut buf = build_slice!(input, input_len as usize); + + if state.progress < HTTP2ConnectionState::Http2StateMagicDone { + //skip magic lol + if state.request_buffer.len() > 0 { + state.request_buffer.extend(buf); + if state.request_buffer.len() >= 24 { + //skip magic + match std::str::from_utf8(&state.request_buffer[..24]) { + Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {} + Ok(&_) => { + state.set_event(HTTP2Event::InvalidClientMagic); + } + Err(_) => { + return -1; + } + } + buf = &buf[state.request_buffer.len() - buf.len() - 24..]; + state.request_buffer.clear() + } else { + //still more buffer + return 1; + } } else { - false + if buf.len() >= 24 { + //skip magic + match std::str::from_utf8(&buf[..24]) { + Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {} + Ok(&_) => { + state.set_event(HTTP2Event::InvalidClientMagic); + } + Err(_) => { + return -1; + } + } + buf = &buf[24..]; + } else { + //need to bufferize content + state.request_buffer.extend(buf); + return 1; + } } - }; - - if eof { - // If needed, handled EOF, or pass it into the parser. + state.progress = HTTP2ConnectionState::Http2StateMagicDone; } - let state = cast_pointer!(state, HTTP2State); - let buf = build_slice!(input, input_len as usize); - if state.parse_request(buf) { + if state.parse_ts(buf) { return 1; } return -1; } #[no_mangle] -pub extern "C" fn rs_http2_parse_response( +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, + _pstate: *mut std::os::raw::c_void, input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, ) -> i32 { - let _eof = unsafe { - if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { - true - } else { - false - } - }; let state = cast_pointer!(state, HTTP2State); let buf = build_slice!(input, input_len as usize); - if state.parse_response(buf) { + if state.parse_tc(buf) { return 1; } return -1; @@ -357,20 +527,50 @@ pub extern "C" fn rs_http2_state_get_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, + 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 { - return -1; + 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, + _ => -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, + event_id: std::os::raw::c_int, + event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, ) -> i8 { - return -1; + 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", + }; + 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( @@ -447,18 +647,18 @@ pub unsafe extern "C" fn rs_http2_register_parser() { 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 + probe_ts: None, // big magic string should be enough probe_tc: Some(rs_http2_probing_parser_tc), - min_depth: 0, - max_depth: 16, + min_depth: 9, // frame header size + max_depth: 24, // client magic size state_new: rs_http2_state_new, state_free: rs_http2_state_free, tx_free: rs_http2_state_tx_free, - parse_ts: rs_http2_parse_request, - parse_tc: rs_http2_parse_response, + 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, - //TODO + //TODO progress completion tx_get_comp_st: rs_http2_state_progress_completion_status, tx_get_progress: rs_http2_tx_get_alstate_progress, get_tx_logged: Some(rs_http2_tx_get_logged), diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index f006e3774259..efd461264fde 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -20,6 +20,7 @@ use crate::json::*; use std; fn log_http2(tx: &HTTP2Transaction) -> Option { + //TODO log type let js = Json::object(); if let Some(ref request) = tx.request { js.set_string("request", request); diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs index e221ccd892d8..841bae8e0eb8 100644 --- a/rust/src/http2/mod.rs +++ b/rust/src/http2/mod.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -//TODO pub mod detect; +//TODO pub mod detect; detect frame type pub mod http2; pub mod logger; mod parser; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 8b1cf6c52e99..12e966d24b1f 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -17,11 +17,25 @@ use nom::number::streaming::be_u8; +#[repr(u8)] +#[derive(PartialEq, FromPrimitive, Debug)] +pub enum HTTP2FrameType { + Http2FrameTypeDATA = 0, + Http2FrameTypeHEADERS = 1, + Http2FrameTypePRIORITY = 2, + Http2frameTypeRSTSTREAM = 3, + Http2FrameTypeSETTINGS = 4, + Http2frameTypePUSHPROMISE = 5, + Http2FrameTypePING = 6, + Http2FrameTypeGOAWAY = 7, + Http2frameTypeWINDOWUPDATE = 8, + Http2FrameTypeCONTINUATION = 9, +} + #[derive(PartialEq)] pub struct HTTP2FrameHeader { pub length: u32, - //TODO explicit enum for type settings=4 - pub ftype: u8, + pub ftype: HTTP2FrameType, pub flags: u8, pub reserved: u8, stream_id: u32, @@ -30,7 +44,8 @@ pub struct HTTP2FrameHeader { named!(pub http2_parse_frame_header, do_parse!( length: bits!( take_bits!(24u32) ) >> - ftype: be_u8 >> + ftype: map_opt!( be_u8, + num::FromPrimitive::from_u8 ) >> flags: be_u8 >> stream_id: bits!( tuple!( take_bits!(1u8), take_bits!(31u32) ) ) >> @@ -53,13 +68,12 @@ mod tests { 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, 4); + assert_eq!(frame.ftype, HTTP2FrameType::Http2FrameTypeSETTINGS); assert_eq!(frame.flags, 0); assert_eq!(frame.reserved, 0); assert_eq!(frame.stream_id, 0); @@ -74,6 +88,22 @@ mod tests { panic!("Result should not be an error: {:?}.", err); } } + let buf2: &[u8] = &[ + 0x00, 0x00, 0x06, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x64, + ]; + let result2 = http2_parse_frame_header(buf2); + match result2 { + Ok((_, _)) => { + panic!("Result should not have been Ok."); + } + Err(Err::Error((_, kind))) => { + assert_eq!(kind, error::ErrorKind::MapOpt); + } + Err(err) => { + panic!("Result should not have been an unknown error: {:?}.", err); + } + } } } From 5cb3a00da06edc364eab95a4c02406ecbc3118f6 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Thu, 12 Mar 2020 10:56:04 +0100 Subject: [PATCH 04/17] http2 log --- rust/src/http2/http2.rs | 103 ++++++----------- rust/src/http2/logger.rs | 10 +- rust/src/http2/parser.rs | 27 +++-- src/Makefile.am | 1 + src/alert-prelude.c | 23 ++++ src/app-layer-http2.c | 2 +- src/app-layer-parser.c | 2 + src/app-layer-protos.c | 1 + src/output-json-http2.c | 243 +++++++++++++++++++++++++++++++++++++++ src/output-json-http2.h | 29 +++++ src/output.c | 2 + src/suricata-common.h | 1 + src/util-error.c | 1 + src/util-error.h | 1 + src/util-profiling.c | 1 + 15 files changed, 362 insertions(+), 85 deletions(-) create mode 100644 src/output-json-http2.c create mode 100644 src/output-json-http2.h diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 9b8cfc4c2673..ea70ebe154c6 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -38,8 +38,7 @@ pub enum HTTP2ConnectionState { pub struct HTTP2Transaction { tx_id: u64, - pub request: Option, - pub response: Option, + pub ftype: Option, logged: LoggerFlags, de_state: Option<*mut core::DetectEngineState>, @@ -50,8 +49,7 @@ impl HTTP2Transaction { pub fn new() -> HTTP2Transaction { HTTP2Transaction { tx_id: 0, - request: None, - response: None, + ftype: None, logged: LoggerFlags::new(), de_state: None, events: std::ptr::null_mut(), @@ -158,15 +156,6 @@ impl HTTP2State { return tx; } - fn find_request(&mut self) -> Option<&mut HTTP2Transaction> { - for tx in &mut self.transactions { - if tx.response.is_none() { - return Some(tx); - } - } - None - } - fn parse_ts(&mut self, input: &[u8]) -> bool { let mut toparse = input; //first consume frame bytes @@ -188,12 +177,18 @@ impl HTTP2State { match parser::http2_parse_frame_header(&self.request_buffer) { Ok((rem, head)) => { let hl = head.length as usize; - if rem.len() < hl { - let rl = rem.len() as u32; + let rlu = rem.len(); + //TODO handle transactions the right way + let mut tx = self.new_tx(); + tx.ftype = Some(head.ftype); + self.transactions.push(tx); + + if rlu < hl { + let rl = rlu as u32; self.request_frame_size = head.length - rl; return true; } else { - toparse = &toparse[toparse.len() - rem.len() - hl..]; + toparse = &toparse[toparse.len() - rlu - hl..]; } } Err(nom::Err::Incomplete(_)) => { @@ -209,6 +204,11 @@ impl HTTP2State { while toparse.len() > 0 { match parser::http2_parse_frame_header(toparse) { Ok((rem, head)) => { +//TODO debug not logged SCLogNotice!("rs_http2_parse_ts http2_parse_frame_header ok"); + let mut tx = self.new_tx(); + tx.ftype = Some(head.ftype); + self.transactions.push(tx); + let hl = head.length as usize; if rem.len() < hl { let rl = rem.len() as u32; @@ -251,14 +251,20 @@ impl HTTP2State { //parse one header locally as we borrow self and self.response_buffer match parser::http2_parse_frame_header(&self.response_buffer) { Ok((rem, head)) => { - //TODO parse deeper based on frame type let hl = head.length as usize; - if rem.len() < hl { - let rl = rem.len() as u32; + let rlu = rem.len(); + + let mut tx = self.new_tx(); + tx.ftype = Some(head.ftype); + self.transactions.push(tx); + + //TODO parse deeper based on frame type + if rlu < hl { + let rl = rlu as u32; self.response_frame_size = head.length - rl; return true; } else { - toparse = &toparse[toparse.len() - rem.len() - hl..]; + toparse = &toparse[toparse.len() - rlu - hl..]; } } Err(nom::Err::Incomplete(_)) => { @@ -274,6 +280,10 @@ impl HTTP2State { while toparse.len() > 0 { match parser::http2_parse_frame_header(toparse) { Ok((rem, head)) => { + let mut tx = self.new_tx(); + tx.ftype = Some(head.ftype); + self.transactions.push(tx); + //TODO parse deeper based on frame type let hl = head.length as usize; if rem.len() < hl { @@ -324,6 +334,7 @@ impl HTTP2State { export_tx_get_detect_state!(rs_http2_tx_get_detect_state, HTTP2Transaction); export_tx_set_detect_state!(rs_http2_tx_set_detect_state, HTTP2Transaction); +//TODO connection upgrade from HTTP1 /// C entry point for a probing parser. #[no_mangle] pub extern "C" fn rs_http2_probing_parser_tc( @@ -333,6 +344,7 @@ pub extern "C" fn rs_http2_probing_parser_tc( input_len: u32, _rdir: *mut u8, ) -> AppProto { +//TODO debug not called SCLogNotice!("rs_http2_probing_parser_tc"); if input != std::ptr::null_mut() { let slice = build_slice!(input, input_len as usize); match parser::http2_parse_frame_header(slice) { @@ -340,7 +352,7 @@ pub extern "C" fn rs_http2_probing_parser_tc( if header.reserved != 0 || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE || header.flags & 0xFE != 0 - || header.ftype != parser::HTTP2FrameType::Http2FrameTypeSETTINGS + || header.ftype != parser::HTTP2FrameType::SETTINGS { return unsafe { ALPROTO_FAILED }; } @@ -492,7 +504,7 @@ pub extern "C" fn rs_http2_tx_get_alstate_progress( let tx = cast_pointer!(tx, HTTP2Transaction); // Transaction is done if we have a response. - if tx.response.is_some() { + if tx.ftype.is_some() { return 1; } return 0; @@ -594,51 +606,8 @@ pub extern "C" fn rs_http2_state_get_tx_iterator( } } -/// Get the request buffer for a transaction from C. -/// -/// No required for parsing, but an example function for retrieving a -/// pointer to the request buffer from C for detection. -#[no_mangle] -pub extern "C" fn rs_http2_get_request_buffer( - tx: *mut std::os::raw::c_void, - buf: *mut *const u8, - len: *mut u32, -) -> u8 { - let tx = cast_pointer!(tx, HTTP2Transaction); - if let Some(ref request) = tx.request { - if request.len() > 0 { - unsafe { - *len = request.len() as u32; - *buf = request.as_ptr(); - } - return 1; - } - } - return 0; -} - -/// Get the response buffer for a transaction from C. -#[no_mangle] -pub extern "C" fn rs_http2_get_response_buffer( - tx: *mut std::os::raw::c_void, - buf: *mut *const u8, - len: *mut u32, -) -> u8 { - let tx = cast_pointer!(tx, HTTP2Transaction); - if let Some(ref response) = tx.response { - if response.len() > 0 { - unsafe { - *len = response.len() as u32; - *buf = response.as_ptr(); - } - return 1; - } - } - return 0; -} - // Parser name as a C style string. -const PARSER_NAME: &'static [u8] = b"http2-rust\0"; +const PARSER_NAME: &'static [u8] = b"http2\0"; #[no_mangle] pub unsafe extern "C" fn rs_http2_register_parser() { @@ -649,7 +618,7 @@ pub unsafe extern "C" fn rs_http2_register_parser() { ipproto: IPPROTO_TCP, probe_ts: None, // big magic string should be enough probe_tc: Some(rs_http2_probing_parser_tc), - min_depth: 9, // frame header size + min_depth: 0, // frame header size max_depth: 24, // client magic size state_new: rs_http2_state_new, state_free: rs_http2_state_free, diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index efd461264fde..1c6e5286f84b 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -20,19 +20,15 @@ use crate::json::*; use std; fn log_http2(tx: &HTTP2Transaction) -> Option { - //TODO log type let js = Json::object(); - if let Some(ref request) = tx.request { - js.set_string("request", request); - } - if let Some(ref response) = tx.response { - js.set_string("response", response); + if let Some(ref ftype) = tx.ftype { + js.set_string("frame_type", &ftype.to_string()); } return Some(js); } #[no_mangle] -pub extern "C" fn rs_http2_logger_log(tx: *mut std::os::raw::c_void) -> *mut JsonT { +pub extern "C" fn rs_http2_log_json(tx: *mut std::os::raw::c_void) -> *mut JsonT { let tx = cast_pointer!(tx, HTTP2Transaction); match log_http2(tx) { Some(js) => js.unwrap(), diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 12e966d24b1f..1fc6d8dd0c03 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -16,20 +16,27 @@ */ use nom::number::streaming::be_u8; +use std::fmt; #[repr(u8)] #[derive(PartialEq, FromPrimitive, Debug)] pub enum HTTP2FrameType { - Http2FrameTypeDATA = 0, - Http2FrameTypeHEADERS = 1, - Http2FrameTypePRIORITY = 2, - Http2frameTypeRSTSTREAM = 3, - Http2FrameTypeSETTINGS = 4, - Http2frameTypePUSHPROMISE = 5, - Http2FrameTypePING = 6, - Http2FrameTypeGOAWAY = 7, - Http2frameTypeWINDOWUPDATE = 8, - Http2FrameTypeCONTINUATION = 9, + 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) + } } #[derive(PartialEq)] diff --git a/src/Makefile.am b/src/Makefile.am index a4320a6d1276..4978defe5d75 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -347,6 +347,7 @@ output-json-flow.c output-json-flow.h \ output-json-ftp.c output-json-ftp.h \ output-json-netflow.c output-json-netflow.h \ output-json-http.c output-json-http.h \ +output-json-http2.c output-json-http2.h \ output-json-sip.c output-json-sip.h \ output-json-smtp.c output-json-smtp.h \ output-json-ssh.c output-json-ssh.h \ diff --git a/src/alert-prelude.c b/src/alert-prelude.c index 81f8bf1d9acc..0add97972805 100644 --- a/src/alert-prelude.c +++ b/src/alert-prelude.c @@ -687,6 +687,26 @@ static void PacketToDataProtoHTTP(const Packet *p, const PacketAlert *pa, idmef_ } +/** + * \brief Handle ALPROTO_HTTP2 JSON information + * \param p Packet where to extract data + * \param pa Packet alert information + * \param alert IDMEF alert + * \return void + */ +static void PacketToDataProtoHTTP2(const Packet *p, const PacketAlert *pa, idmef_alert_t *alert) +{ + void *http2_state = FlowGetAppState(f); + if (http2_state) { + void *tx_ptr = rs_http2_state_get_tx(http2_state, pa->tx_id); + json_t *js = rs_http2_log_json(tx_ptr); + if (unlikely(js == NULL)) + return; + JsonToAdditionalData(NULL, js, alert); + json_decref(js); + } +} + /** * \brief Handle ALPROTO_TLS JSON information * \param p Packet where to extract data @@ -811,6 +831,9 @@ static int PacketToData(const Packet *p, const PacketAlert *pa, idmef_alert_t *a case ALPROTO_HTTP: PacketToDataProtoHTTP(p, pa, alert); break; + case ALPROTO_HTTP2: + PacketToDataProtoHTTP(p, pa, alert); + break; case ALPROTO_TLS: PacketToDataProtoTLS(p, pa, alert); break; diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c index cd516a32ebab..1a3075047e2e 100644 --- a/src/app-layer-http2.c +++ b/src/app-layer-http2.c @@ -59,6 +59,6 @@ void RegisterHTTP2Parsers(void) rs_http2_register_parser(); #ifdef UNITTESTS - //TODO + //TODO tests #endif } diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index f160fd106104..1d9fd58840ed 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -69,6 +69,7 @@ #include "app-layer-template.h" #include "app-layer-template-rust.h" #include "app-layer-rdp.h" +#include "app-layer-http2.h" #include "conf.h" #include "util-spm.h" @@ -1587,6 +1588,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterRFBParsers(); RegisterTemplateParsers(); RegisterRdpParsers(); + RegisterHTTP2Parsers(); /** IMAP */ AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap"); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 875b25d58298..4b41b6c0dc26 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -160,6 +160,7 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; + if (strcmp(proto_name,"http2")==0) return ALPROTO_HTTP2; if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED; return ALPROTO_UNKNOWN; diff --git a/src/output-json-http2.c b/src/output-json-http2.c new file mode 100644 index 000000000000..8fd0e27928e6 --- /dev/null +++ b/src/output-json-http2.c @@ -0,0 +1,243 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + * Implements HTTP2 JSON logging portion of the engine. + */ + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "pkt-var.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-print.h" +#include "util-unittest.h" + +#include "util-debug.h" +#include "app-layer-parser.h" +#include "output.h" +#include "app-layer-http2.h" +#include "app-layer.h" +#include "util-privs.h" +#include "util-buffer.h" + +#include "util-logopenfile.h" +#include "util-crypt.h" + +#include "output-json.h" +#include "output-json-http2.h" +#include "rust.h" + +#define MODULE_NAME "LogHttp2Log" + +//TODO Cannot this be generic ? and most of the file as well +typedef struct OutputHttp2Ctx_ { + LogFileCtx *file_ctx; + OutputJsonCommonSettings cfg; +} OutputHttp2Ctx; + + +typedef struct JsonHttp2LogThread_ { + OutputHttp2Ctx *http2log_ctx; + MemBuffer *buffer; +} JsonHttp2LogThread; + + +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; + } + + json_t *js = CreateJSONHeader(p, LOG_DIR_FLOW, "http2"); + if (unlikely(js == NULL)) + return 0; + + JsonAddCommonOptions(&http2_ctx->cfg, p, f, js); + + /* reset */ + MemBufferReset(aft->buffer); + + json_t *tjs = rs_http2_log_json(txptr); + if (unlikely(tjs == NULL)) { + free(js); + return 0; + } + json_object_set_new(js, "http2", tjs); + + OutputJSONBuffer(js, http2_ctx->file_ctx, &aft->buffer); + json_object_clear(js); + json_decref(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, + 1, 1, //TODO progress + 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, + 1, 1, + JsonHttp2LogThreadInit, JsonHttp2LogThreadDeinit, NULL); +} diff --git a/src/output-json-http2.h b/src/output-json-http2.h new file mode 100644 index 000000000000..88ba420ab2df --- /dev/null +++ b/src/output-json-http2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef __OUTPUT_JSON_HTTP2_H__ +#define __OUTPUT_JSON_HTTP2_H__ + +void JsonHttp2LogRegister(void); + +#endif /* __OUTPUT_JSON_HTTP2_H__ */ diff --git a/src/output.c b/src/output.c index 9dd643fc26fe..d88f54b81be3 100644 --- a/src/output.c +++ b/src/output.c @@ -79,6 +79,7 @@ #include "output-json-template.h" #include "output-json-template-rust.h" #include "output-json-rdp.h" +#include "output-json-http2.h" #include "output-lua.h" #include "output-json-dnp3.h" #include "output-json-metadata.h" @@ -1109,6 +1110,7 @@ void OutputRegisterLoggers(void) /* http log */ LogHttpLogRegister(); JsonHttpLogRegister(); + JsonHttp2LogRegister(); /* tls log */ LogTlsLogRegister(); JsonTlsLogRegister(); diff --git a/src/suricata-common.h b/src/suricata-common.h index 6bbd224addf6..ddb5296bd14a 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -443,6 +443,7 @@ typedef enum { LOGGER_JSON_RFB, LOGGER_JSON_TEMPLATE, LOGGER_JSON_RDP, + LOGGER_JSON_HTTP2, LOGGER_ALERT_DEBUG, LOGGER_ALERT_FAST, diff --git a/src/util-error.c b/src/util-error.c index 81f64cfef409..9dc7bdf6104b 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -108,6 +108,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_DEBUG_LOG_GENERIC); CASE_CODE (SC_ERR_UNIFIED_LOG_GENERIC); CASE_CODE (SC_ERR_HTTP_LOG_GENERIC); + CASE_CODE (SC_ERR_HTTP2_LOG_GENERIC); CASE_CODE (SC_ERR_FTP_LOG_GENERIC); CASE_CODE (SC_ERR_UNIFIED_ALERT_GENERIC); CASE_CODE (SC_ERR_UNIFIED2_ALERT_GENERIC); diff --git a/src/util-error.h b/src/util-error.h index 67bb9daa9fbb..2cb9cc8cf495 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -358,6 +358,7 @@ typedef enum { SC_ERR_PCRE_COPY_SUBSTRING, SC_WARN_PCRE_JITSTACK, SC_WARN_REGISTRATION_FAILED, + SC_ERR_HTTP2_LOG_GENERIC, SC_ERR_MAX } SCError; diff --git a/src/util-profiling.c b/src/util-profiling.c index 7a590a557308..87dfe2df66ca 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1323,6 +1323,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_RFB); CASE_CODE (LOGGER_JSON_TEMPLATE); CASE_CODE (LOGGER_JSON_RDP); + CASE_CODE (LOGGER_JSON_HTTP2); CASE_CODE (LOGGER_TLS_STORE); CASE_CODE (LOGGER_TLS); CASE_CODE (LOGGER_FILE_STORE); From 0c323aae2c3edb89101a4bf95cffd1994b221859 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 13 Mar 2020 09:37:56 +0100 Subject: [PATCH 05/17] applayer: allow rust parsers to have only one probe --- src/app-layer-register.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app-layer-register.c b/src/app-layer-register.c index 17691d26ac93..6a592f139bce 100644 --- a/src/app-layer-register.c +++ b/src/app-layer-register.c @@ -54,7 +54,7 @@ AppProto AppLayerRegisterProtocolDetection(const struct AppLayerParser *p, int e AppLayerProtoDetectRegisterProtocol(alproto, p->name); - if (p->ProbeTS == NULL || p->ProbeTC == NULL) { + if (p->ProbeTS == NULL && p->ProbeTC == NULL) { return alproto; } From 6e6bc2e0ca1e6380ce275f4957922b462c3803ee Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 13 Mar 2020 11:25:17 +0100 Subject: [PATCH 06/17] activate log in eve for HTTP2 --- rust/src/http2/http2.rs | 4 +--- suricata.yaml.in | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index ea70ebe154c6..722dd63e4007 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -204,7 +204,6 @@ impl HTTP2State { while toparse.len() > 0 { match parser::http2_parse_frame_header(toparse) { Ok((rem, head)) => { -//TODO debug not logged SCLogNotice!("rs_http2_parse_ts http2_parse_frame_header ok"); let mut tx = self.new_tx(); tx.ftype = Some(head.ftype); self.transactions.push(tx); @@ -344,7 +343,6 @@ pub extern "C" fn rs_http2_probing_parser_tc( input_len: u32, _rdir: *mut u8, ) -> AppProto { -//TODO debug not called SCLogNotice!("rs_http2_probing_parser_tc"); if input != std::ptr::null_mut() { let slice = build_slice!(input, input_len as usize); match parser::http2_parse_frame_header(slice) { @@ -402,7 +400,7 @@ pub extern "C" fn rs_http2_parse_ts( let mut buf = build_slice!(input, input_len as usize); if state.progress < HTTP2ConnectionState::Http2StateMagicDone { - //skip magic lol + //skip magic if state.request_buffer.len() > 0 { state.request_buffer.extend(buf); if state.request_buffer.len() >= 24 { diff --git a/suricata.yaml.in b/suricata.yaml.in index 2f4bdccc904c..d1f3eacaf144 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -274,6 +274,7 @@ outputs: # to an IP address is logged. extended: no - ssh + - http2 - stats: totals: yes # stats for all threads merged together threads: no # per thread stats From 59529c41e20427c09048b47b18989cda797c0d22 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 13 Mar 2020 11:43:56 +0100 Subject: [PATCH 07/17] parsing fix --- rust/src/http2/http2.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 722dd63e4007..3e91440fd2b3 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -182,13 +182,15 @@ impl HTTP2State { let mut tx = self.new_tx(); tx.ftype = Some(head.ftype); self.transactions.push(tx); + self.request_buffer.clear(); if rlu < hl { let rl = rlu as u32; self.request_frame_size = head.length - rl; return true; } else { - toparse = &toparse[toparse.len() - rlu - hl..]; + //toparse.len() - rlu == 9 + toparse = &toparse[hl - (toparse.len() - rlu)..]; } } Err(nom::Err::Incomplete(_)) => { @@ -214,7 +216,7 @@ impl HTTP2State { self.request_frame_size = head.length - rl; return true; } else { - toparse = &rem[rem.len() - hl..]; + toparse = &rem[hl..]; } } Err(nom::Err::Incomplete(_)) => { @@ -256,6 +258,7 @@ impl HTTP2State { let mut tx = self.new_tx(); tx.ftype = Some(head.ftype); self.transactions.push(tx); + self.response_buffer.clear(); //TODO parse deeper based on frame type if rlu < hl { @@ -263,7 +266,7 @@ impl HTTP2State { self.response_frame_size = head.length - rl; return true; } else { - toparse = &toparse[toparse.len() - rlu - hl..]; + toparse = &toparse[hl - (toparse.len() - rlu)..]; } } Err(nom::Err::Incomplete(_)) => { @@ -290,7 +293,7 @@ impl HTTP2State { self.response_frame_size = head.length - rl; return true; } else { - toparse = &rem[rem.len() - hl..]; + toparse = &rem[hl..]; } } Err(nom::Err::Incomplete(_)) => { From cc2ee6f7d62adc8a16d73869ef152b1947b2caf2 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 13 Mar 2020 15:42:26 +0100 Subject: [PATCH 08/17] http2 detect --- rust/src/http2/http2.rs | 13 ++- rust/src/http2/mod.rs | 2 +- rust/src/http2/parser.rs | 24 ++++- src/Makefile.am | 1 + src/app-layer-http2.c | 1 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 2 + src/detect-http2.c | 178 +++++++++++++++++++++++++++++++++++ src/detect-http2.h | 29 ++++++ src/tests/detect-http2.c | 50 ++++++++++ 10 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 src/detect-http2.c create mode 100644 src/detect-http2.h create mode 100644 src/tests/detect-http2.c diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 3e91440fd2b3..a7f737e0af39 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -16,7 +16,7 @@ */ use super::parser; -use crate::applayer::{self, LoggerFlags}; +use crate::applayer::{self, LoggerFlags, TxDetectFlags}; use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; use crate::log::*; use crate::parser::*; @@ -42,6 +42,7 @@ pub struct HTTP2Transaction { logged: LoggerFlags, de_state: Option<*mut core::DetectEngineState>, + detect_flags: TxDetectFlags, events: *mut core::AppLayerDecoderEvents, } @@ -52,6 +53,7 @@ impl HTTP2Transaction { ftype: None, logged: LoggerFlags::new(), de_state: None, + detect_flags: TxDetectFlags::default(), events: std::ptr::null_mut(), } } @@ -336,6 +338,9 @@ impl HTTP2State { 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_detect_flags_set!(rs_http2_set_tx_detect_flags, HTTP2Transaction); +export_tx_detect_flags_get!(rs_http2_get_tx_detect_flags, HTTP2Transaction); + //TODO connection upgrade from HTTP1 /// C entry point for a probing parser. #[no_mangle] @@ -619,7 +624,7 @@ pub unsafe extern "C" fn rs_http2_register_parser() { ipproto: IPPROTO_TCP, probe_ts: None, // big magic string should be enough probe_tc: Some(rs_http2_probing_parser_tc), - min_depth: 0, // frame header size + min_depth: 9, // frame header size max_depth: 24, // client magic size state_new: rs_http2_state_new, state_free: rs_http2_state_free, @@ -644,8 +649,8 @@ pub unsafe extern "C" fn rs_http2_register_parser() { set_tx_mpm_id: None, get_files: None, get_tx_iterator: Some(rs_http2_state_get_tx_iterator), - get_tx_detect_flags: None, - set_tx_detect_flags: None, + get_tx_detect_flags: Some(rs_http2_get_tx_detect_flags), + set_tx_detect_flags: Some(rs_http2_set_tx_detect_flags), }; let ip_proto_str = CString::new("tcp").unwrap(); diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs index 841bae8e0eb8..b0f8655a1eb4 100644 --- a/rust/src/http2/mod.rs +++ b/rust/src/http2/mod.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -//TODO pub mod detect; detect frame type +pub mod detect; pub mod http2; pub mod logger; mod parser; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 1fc6d8dd0c03..a8f70806d73c 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -19,7 +19,7 @@ use nom::number::streaming::be_u8; use std::fmt; #[repr(u8)] -#[derive(PartialEq, FromPrimitive, Debug)] +#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)] pub enum HTTP2FrameType { DATA = 0, HEADERS = 1, @@ -39,6 +39,28 @@ impl fmt::Display for HTTP2FrameType { } } +impl std::str::FromStr for HTTP2FrameType { + type Err = String; + + //TODO more automatic + fn from_str(s: &str) -> Result { + //TODO let su = s.to_uppercase(); + match s { + "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 { pub length: u32, diff --git a/src/Makefile.am b/src/Makefile.am index 4978defe5d75..e1c93d10cf8d 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -204,6 +204,7 @@ detect-http-stat-code.c detect-http-stat-code.h \ detect-http-stat-msg.c detect-http-stat-msg.h \ detect-http-ua.c detect-http-ua.h \ detect-http-uri.c detect-http-uri.h \ +detect-http2.c detect-http2.h \ detect-icmp-id.c detect-icmp-id.h \ detect-icmp-seq.c detect-icmp-seq.h \ detect-icmpv6hdr.c detect-icmpv6hdr.h \ diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c index 1a3075047e2e..cc31c9756159 100644 --- a/src/app-layer-http2.c +++ b/src/app-layer-http2.c @@ -37,6 +37,7 @@ static int HTTP2RegisterPatternsForProtocolDetection(void) { + //TODO is this too restrictive and can be evaded ? if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_HTTP2, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 24, 0, STREAM_TOSERVER) < 0) diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 5830a8566c75..382ef3d0ce97 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -158,6 +158,7 @@ #include "detect-http-stat-msg.h" #include "detect-http-request-line.h" #include "detect-http-response-line.h" +#include "detect-http2.h" #include "detect-byte-extract.h" #include "detect-file-data.h" #include "detect-pkt-data.h" @@ -447,6 +448,7 @@ void SigTableSetup(void) DetectHttpStatMsgRegister(); DetectHttpStatCodeRegister(); + DetectHttp2Register(); DetectDnsQueryRegister(); DetectDnsOpcodeRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index f488ecac210f..f40c2b2c717c 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -167,6 +167,8 @@ enum DetectKeywordId { DETECT_PKT_DATA, DETECT_AL_APP_LAYER_EVENT, + DETECT_HTTP2_FRAMETYPE, + DETECT_DCE_IFACE, DETECT_DCE_OPNUM, DETECT_DCE_STUB_DATA, diff --git a/src/detect-http2.c b/src/detect-http2.c new file mode 100644 index 000000000000..a85591d02151 --- /dev/null +++ b/src/detect-http2.c @@ -0,0 +1,178 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + * + */ + +#include "suricata-common.h" + +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" + +#include "detect-http2.h" +#include "util-byte.h" +#include "rust.h" + +/* 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 (void *); +#ifdef UNITTESTS +void DetectHTTP2RegisterTests (void); +#endif + +static int g_http2_match_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"; + //TODO create a new doc file for HTTP2 keywords + sigmatch_table[DETECT_HTTP2_FRAMETYPE].url = DOC_URL DOC_VERSION "/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 = DetectHTTP2RegisterTests; +#endif + //TODO investigate why this is needed + 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"); + + 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; + + int value = rs_http2_tx_get_frametype(txv, flags); + if (value < 0) { + //no value, no match + return 0; + } + return *detect == value; +} + +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; + } + + //TODO do we need to allocate ? Could we unionize sm->ctx ? + uint8_t *http2ft = SCCalloc(1, sizeof(uint8_t)); + if (http2ft == NULL) + return -1; + *http2ft = frame_type; + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2frametypeFree(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(void *ptr) +{ + SCFree(ptr); +} + +#ifdef UNITTESTS +#include "tests/detect-http2.c" +#endif diff --git a/src/detect-http2.h b/src/detect-http2.h new file mode 100644 index 000000000000..11bc17e66c04 --- /dev/null +++ b/src/detect-http2.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Philippe Antoine + */ + +#ifndef _DETECT_HTTP2_H +#define _DETECT_HTTP2_H + +void DetectHttp2Register(void); + +#endif /* _DETECT_HTTP2_H */ diff --git a/src/tests/detect-http2.c b/src/tests/detect-http2.c new file mode 100644 index 000000000000..64871ba77ef5 --- /dev/null +++ b/src/tests/detect-http2.c @@ -0,0 +1,50 @@ +/* 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 DetectICMPv6mtu + */ +void DetectHTTP2RegisterTests(void) +{ + UtRegisterTest("DetectHTTP2frameTypeParseTest01", DetectHTTP2frameTypeParseTest01); +} From 00a9ff1ac1352ebb95c4cc1f9a5d19f55aa101b7 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 27 Mar 2020 14:23:48 +0100 Subject: [PATCH 09/17] rebased ok --- rust/src/http2/detect.rs | 74 ++++++++++++++++++++++++++++++++++++++++ rust/src/http2/http2.rs | 64 ++++++++++++---------------------- 2 files changed, 95 insertions(+), 43 deletions(-) create mode 100644 rust/src/http2/detect.rs diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs new file mode 100644 index 000000000000..134661a0bd98 --- /dev/null +++ b/rust/src/http2/detect.rs @@ -0,0 +1,74 @@ +/* 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::HTTP2Transaction; +use super::parser; +use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; +use std::ffi::CStr; +use std::str::FromStr; + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_frametype( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.ftype { + None => { + return -1; + } + Some(x) => { + return *x as i32; + } + }, + STREAM_TOCLIENT => match &tx.ftype { + None => { + return -1; + } + Some(x) => { + return *x as i32; + } + }, + _ => {} + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_parse_frametype( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = unsafe { CStr::from_ptr(str) }; + match ft_name.to_str() { + Ok(s) => { + let p = parser::HTTP2FrameType::from_str(s); + match p { + Ok(x) => { + return x as i32; + } + Err(_) => { + return -1; + } + } + } + Err(_) => { + return -1; + } + } +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index a7f737e0af39..9331fe1f2b01 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -16,10 +16,9 @@ */ use super::parser; -use crate::applayer::{self, LoggerFlags, TxDetectFlags}; +use crate::applayer::{self, *}; use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; use crate::log::*; -use crate::parser::*; use nom; use std; use std::ffi::{CStr, CString}; @@ -403,57 +402,36 @@ pub extern "C" fn rs_http2_parse_ts( input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, -) -> i32 { +) -> AppLayerResult { let state = cast_pointer!(state, HTTP2State); let mut buf = build_slice!(input, input_len as usize); if state.progress < HTTP2ConnectionState::Http2StateMagicDone { //skip magic - if state.request_buffer.len() > 0 { - state.request_buffer.extend(buf); - if state.request_buffer.len() >= 24 { - //skip magic - match std::str::from_utf8(&state.request_buffer[..24]) { - Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {} - Ok(&_) => { - state.set_event(HTTP2Event::InvalidClientMagic); - } - Err(_) => { - return -1; - } + if buf.len() >= 24 { + //skip magic + match std::str::from_utf8(&buf[..24]) { + Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => { + buf = &buf[buf.len() - 24..]; } - buf = &buf[state.request_buffer.len() - buf.len() - 24..]; - state.request_buffer.clear() - } else { - //still more buffer - return 1; - } - } else { - if buf.len() >= 24 { - //skip magic - match std::str::from_utf8(&buf[..24]) { - Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {} - Ok(&_) => { - state.set_event(HTTP2Event::InvalidClientMagic); - } - Err(_) => { - return -1; - } + Ok(&_) => { + state.set_event(HTTP2Event::InvalidClientMagic); + } + Err(_) => { + return AppLayerResult::err(); } - buf = &buf[24..]; - } else { - //need to bufferize content - state.request_buffer.extend(buf); - return 1; } + state.progress = HTTP2ConnectionState::Http2StateMagicDone; + } else { + //still more buffer + return AppLayerResult::incomplete(0 as u32, 24 as u32); } - state.progress = HTTP2ConnectionState::Http2StateMagicDone; } if state.parse_ts(buf) { - return 1; + return AppLayerResult::ok(); } - return -1; + return AppLayerResult::err(); } #[no_mangle] @@ -465,13 +443,13 @@ pub extern "C" fn rs_http2_parse_tc( input_len: u32, _data: *const std::os::raw::c_void, _flags: u8, -) -> i32 { +) -> AppLayerResult { let state = cast_pointer!(state, HTTP2State); let buf = build_slice!(input, input_len as usize); if state.parse_tc(buf) { - return 1; + return AppLayerResult::ok(); } - return -1; + return AppLayerResult::err(); } #[no_mangle] From f8c0e9d2ff62f1169539a5d4b819bfc84e220fe7 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 27 Mar 2020 15:47:02 +0100 Subject: [PATCH 10/17] HTTP2ErrorCode --- rust/src/http2/http2.rs | 181 ++++++++++++++++++--------------------- rust/src/http2/logger.rs | 8 +- rust/src/http2/parser.rs | 72 +++++++++++++++- src/detect-http2.c | 1 - 4 files changed, 158 insertions(+), 104 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 9331fe1f2b01..96cd765055c3 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -35,10 +35,20 @@ pub enum HTTP2ConnectionState { Http2StateMagicDone = 1, } +const HTTP2_FRAME_HEADER_LEN: usize = 9; + +pub enum HTTP2FrameTypeData { + //TODO SETTINGS(parser::HTTP2FrameSettings), + GOAWAY(parser::HTTP2FrameGoAway), +} + pub struct HTTP2Transaction { tx_id: u64, pub ftype: Option, + /// Command specific data + pub type_data: Option, + logged: LoggerFlags, de_state: Option<*mut core::DetectEngineState>, detect_flags: TxDetectFlags, @@ -50,6 +60,7 @@ impl HTTP2Transaction { HTTP2Transaction { tx_id: 0, ftype: None, + type_data: None, logged: LoggerFlags::new(), de_state: None, detect_flags: TxDetectFlags::default(), @@ -78,6 +89,7 @@ impl Drop for HTTP2Transaction { pub enum HTTP2Event { InvalidFrameHeader = 0, InvalidClientMagic, + InvalidFrameData, } impl HTTP2Event { @@ -85,6 +97,7 @@ impl HTTP2Event { match value { 0 => Some(HTTP2Event::InvalidFrameHeader), 1 => Some(HTTP2Event::InvalidClientMagic), + 2 => Some(HTTP2Event::InvalidFrameData), _ => None, } } @@ -92,8 +105,6 @@ impl HTTP2Event { pub struct HTTP2State { tx_id: u64, - request_buffer: Vec, - response_buffer: Vec, request_frame_size: u32, response_frame_size: u32, transactions: Vec, @@ -104,8 +115,6 @@ impl HTTP2State { pub fn new() -> Self { Self { tx_id: 0, - request_buffer: Vec::new(), - response_buffer: Vec::new(), request_frame_size: 0, response_frame_size: 0, transactions: Vec::new(), @@ -157,131 +166,98 @@ impl HTTP2State { return tx; } - fn parse_ts(&mut self, input: &[u8]) -> bool { - let mut toparse = input; + fn parse_ts(&mut self, mut input: &[u8]) -> AppLayerResult { //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 true; + return AppLayerResult::ok(); } else { let start = self.request_frame_size as usize; - toparse = &toparse[start..]; + input = &input[start..]; self.request_frame_size = 0; } } - //second extend buffer if present - if self.request_buffer.len() > 0 { - self.request_buffer.extend(toparse); - //parse one header locally as we borrow self and self.request_buffer - match parser::http2_parse_frame_header(&self.request_buffer) { - Ok((rem, head)) => { - let hl = head.length as usize; - let rlu = rem.len(); - //TODO handle transactions the right way - let mut tx = self.new_tx(); - tx.ftype = Some(head.ftype); - self.transactions.push(tx); - self.request_buffer.clear(); - if rlu < hl { - let rl = rlu as u32; - self.request_frame_size = head.length - rl; - return true; - } else { - //toparse.len() - rlu == 9 - toparse = &toparse[hl - (toparse.len() - rlu)..]; - } - } - Err(nom::Err::Incomplete(_)) => { - return true; - } - Err(_) => { - self.set_event(HTTP2Event::InvalidFrameHeader); - return false; - } - } - } //then parse all we can - while toparse.len() > 0 { - match parser::http2_parse_frame_header(toparse) { + while input.len() > 0 { + match parser::http2_parse_frame_header(input) { Ok((rem, head)) => { + //TODO handle transactions the right way let mut tx = self.new_tx(); tx.ftype = Some(head.ftype); + match head.ftype { + parser::HTTP2FrameType::GOAWAY => { + match parser::http2_parse_frame_goaway(rem) { + Ok((_, goaway)) => { + tx.type_data = Some(HTTP2FrameTypeData::GOAWAY(goaway)); + } + Err(nom::Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + 4 as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + } + } + } + //TODO parse deeper based on other frame type + _ => {} + } self.transactions.push(tx); let hl = head.length as usize; if rem.len() < hl { let rl = rem.len() as u32; self.request_frame_size = head.length - rl; - return true; + return AppLayerResult::ok(); } else { - toparse = &rem[hl..]; + input = &rem[hl..]; } } Err(nom::Err::Incomplete(_)) => { - self.request_buffer.extend(toparse); - return true; + //we may have consumed data from previous records + if input.len() < HTTP2_FRAME_HEADER_LEN { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + HTTP2_FRAME_HEADER_LEN as u32, + ); + } else { + //TODO BUG_ON ? + SCLogDebug!("HTTP2 invalid length frame header"); + return AppLayerResult::err(); + } } Err(_) => { self.set_event(HTTP2Event::InvalidFrameHeader); - return false; + return AppLayerResult::err(); } } } - return true; + return AppLayerResult::ok(); } - fn parse_tc(&mut self, input: &[u8]) -> bool { - let mut toparse = input; + 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 true; + return AppLayerResult::ok(); } else { let start = self.response_frame_size as usize; - toparse = &toparse[start..]; + input = &input[start..]; self.response_frame_size = 0; } } - //second extend buffer if present - if self.response_buffer.len() > 0 { - self.response_buffer.extend(toparse); - //parse one header locally as we borrow self and self.response_buffer - match parser::http2_parse_frame_header(&self.response_buffer) { - Ok((rem, head)) => { - let hl = head.length as usize; - let rlu = rem.len(); - - let mut tx = self.new_tx(); - tx.ftype = Some(head.ftype); - self.transactions.push(tx); - self.response_buffer.clear(); - - //TODO parse deeper based on frame type - if rlu < hl { - let rl = rlu as u32; - self.response_frame_size = head.length - rl; - return true; - } else { - toparse = &toparse[hl - (toparse.len() - rlu)..]; - } - } - Err(nom::Err::Incomplete(_)) => { - return true; - } - Err(_) => { - self.set_event(HTTP2Event::InvalidFrameHeader); - return false; - } - } - } //then parse all we can - while toparse.len() > 0 { - match parser::http2_parse_frame_header(toparse) { + while input.len() > 0 { + match parser::http2_parse_frame_header(input) { Ok((rem, head)) => { let mut tx = self.new_tx(); tx.ftype = Some(head.ftype); @@ -292,22 +268,31 @@ impl HTTP2State { if rem.len() < hl { let rl = rem.len() as u32; self.response_frame_size = head.length - rl; - return true; + return AppLayerResult::ok(); } else { - toparse = &rem[hl..]; + input = &rem[hl..]; } } Err(nom::Err::Incomplete(_)) => { - self.response_buffer.extend(toparse); - return true; + //we may have consumed data from previous records + if input.len() < HTTP2_FRAME_HEADER_LEN { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + HTTP2_FRAME_HEADER_LEN as u32, + ); + } else { + //TODO BUG_ON ? + SCLogDebug!("HTTP2 invalid length frame header"); + return AppLayerResult::err(); + } } Err(_) => { self.set_event(HTTP2Event::InvalidFrameHeader); - return false; + return AppLayerResult::err(); } } } - return true; + return AppLayerResult::ok(); } fn tx_iterator( @@ -412,7 +397,7 @@ pub extern "C" fn rs_http2_parse_ts( //skip magic match std::str::from_utf8(&buf[..24]) { Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => { - buf = &buf[buf.len() - 24..]; + buf = &buf[24..]; } Ok(&_) => { state.set_event(HTTP2Event::InvalidClientMagic); @@ -428,10 +413,7 @@ pub extern "C" fn rs_http2_parse_ts( } } - if state.parse_ts(buf) { - return AppLayerResult::ok(); - } - return AppLayerResult::err(); + return state.parse_ts(buf); } #[no_mangle] @@ -446,10 +428,7 @@ pub extern "C" fn rs_http2_parse_tc( ) -> AppLayerResult { let state = cast_pointer!(state, HTTP2State); let buf = build_slice!(input, input_len as usize); - if state.parse_tc(buf) { - return AppLayerResult::ok(); - } - return AppLayerResult::err(); + return state.parse_tc(buf); } #[no_mangle] @@ -536,6 +515,7 @@ pub extern "C" fn rs_http2_state_get_event_info( match s { "invalid_frame_header" => HTTP2Event::InvalidFrameHeader as i32, "invalid_client_magic" => HTTP2Event::InvalidClientMagic as i32, + "invalid_frame_data" => HTTP2Event::InvalidFrameData as i32, _ => -1, // unknown event } } @@ -558,6 +538,7 @@ pub extern "C" fn rs_http2_state_get_event_info_by_id( let estr = match e { HTTP2Event::InvalidFrameHeader => "invalid_frame_header\0", HTTP2Event::InvalidClientMagic => "invalid_client_magic\0", + HTTP2Event::InvalidFrameData => "invalid_frame_data\0", }; unsafe { *event_name = estr.as_ptr() as *const std::os::raw::c_char; diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index 1c6e5286f84b..376a58cc20b2 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use super::http2::HTTP2Transaction; +use super::http2::{HTTP2FrameTypeData, HTTP2Transaction}; use crate::json::*; use std; @@ -24,6 +24,12 @@ fn log_http2(tx: &HTTP2Transaction) -> Option { if let Some(ref ftype) = tx.ftype { js.set_string("frame_type", &ftype.to_string()); } + match &tx.type_data { + Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { + js.set_string("error_code", &goaway.errorcode.to_string()); + } + _ => {} + } return Some(js); } diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index a8f70806d73c..1be1649957d6 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use nom::number::streaming::be_u8; +use nom::number::streaming::{be_u32, be_u8}; use std::fmt; #[repr(u8)] @@ -42,7 +42,6 @@ impl fmt::Display for HTTP2FrameType { impl std::str::FromStr for HTTP2FrameType { type Err = String; - //TODO more automatic fn from_str(s: &str) -> Result { //TODO let su = s.to_uppercase(); match s { @@ -84,6 +83,75 @@ named!(pub http2_parse_frame_header, ) ); +#[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 { + //TODO let su = s.to_uppercase(); + match s { + "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 { + //TODO detection on this field + pub errorcode: HTTP2ErrorCode, +} + +named!(pub http2_parse_frame_goaway, + do_parse!( + errorcode: map_opt!( be_u32, + num::FromPrimitive::from_u32 ) >> + (HTTP2FrameGoAway{errorcode}) + ) +); + +//TODO HTTP2FrameSettings +/*pub struct HTTP2FrameSettings { +id: u16, +value: u32, +}*/ + #[cfg(test)] mod tests { diff --git a/src/detect-http2.c b/src/detect-http2.c index a85591d02151..f0a1190399dc 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -69,7 +69,6 @@ void DetectHttp2Register(void) #ifdef UNITTESTS sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2RegisterTests; #endif - //TODO investigate why this is needed DetectAppLayerInspectEngineRegister("http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectHTTP2); From b6a9991cef5b4edaae280d3b84a450a54cefefe5 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 27 Mar 2020 16:58:52 +0100 Subject: [PATCH 11/17] DETECT_HTTP2_ERRORCODE --- rust/src/http2/detect.rs | 54 ++++++++++++++++- src/detect-engine-register.h | 1 + src/detect-http2.c | 111 ++++++++++++++++++++++++++++++++++- 3 files changed, 164 insertions(+), 2 deletions(-) diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index 134661a0bd98..5658b14366f9 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use super::http2::HTTP2Transaction; +use super::http2::{HTTP2Transaction, HTTP2FrameTypeData}; use super::parser; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use std::ffi::CStr; @@ -72,3 +72,55 @@ pub extern "C" fn rs_http2_parse_frametype( } } } + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_errorcode( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.type_data { + Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { + return goaway.errorcode as i32; + } + _ => { + return -1; + } + }, + STREAM_TOCLIENT => match &tx.type_data { + Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { + return goaway.errorcode as i32; + } + _ => { + return -1; + } + }, + _ => {} + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_http2_parse_errorcode( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = unsafe { CStr::from_ptr(str) }; + match ft_name.to_str() { + Ok(s) => { + let p = parser::HTTP2ErrorCode::from_str(s); + match p { + Ok(x) => { + return x as i32; + } + Err(_) => { + return -1; + } + } + } + Err(_) => { + return -1; + } + } +} diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index f40c2b2c717c..028c872779e3 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -168,6 +168,7 @@ enum DetectKeywordId { DETECT_AL_APP_LAYER_EVENT, DETECT_HTTP2_FRAMETYPE, + DETECT_HTTP2_ERRORCODE, DETECT_DCE_IFACE, DETECT_DCE_OPNUM, diff --git a/src/detect-http2.c b/src/detect-http2.c index f0a1190399dc..114614f9953c 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -38,6 +38,13 @@ static int DetectHTTP2frametypeMatch(DetectEngineThreadCtx *det_ctx, const SigMatchCtx *ctx); static int DetectHTTP2frametypeSetup (DetectEngineCtx *, Signature *, const char *); void DetectHTTP2frametypeFree (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 (void *); + #ifdef UNITTESTS void DetectHTTP2RegisterTests (void); #endif @@ -69,6 +76,19 @@ void DetectHttp2Register(void) #ifdef UNITTESTS sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2RegisterTests; #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 = DOC_URL DOC_VERSION "/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 + //TODO should we call multiple times DetectHTTP2RegisterTests ? + sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2RegisterTests; +#endif + DetectAppLayerInspectEngineRegister("http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectHTTP2); @@ -142,7 +162,6 @@ static int DetectHTTP2frametypeSetup (DetectEngineCtx *de_ctx, Signature *s, con return -1; } - //TODO do we need to allocate ? Could we unionize sm->ctx ? uint8_t *http2ft = SCCalloc(1, sizeof(uint8_t)); if (http2ft == NULL) return -1; @@ -172,6 +191,96 @@ void DetectHTTP2frametypeFree(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; + + int value = rs_http2_tx_get_errorcode(txv, flags); + if (value < 0) { + //no value, no match + return 0; + } + return *detect == (uint32_t) value; +} + +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(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(void *ptr) +{ + SCFree(ptr); +} + #ifdef UNITTESTS #include "tests/detect-http2.c" #endif From b6ab082ee559d3afe197c3535e9b0e4fd7c23401 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Sat, 28 Mar 2020 14:57:51 +0100 Subject: [PATCH 12/17] fixup --- rust/src/http2/detect.rs | 2 +- rust/src/http2/http2.rs | 2 +- rust/src/http2/parser.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index 5658b14366f9..bf9034c8b9b1 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use super::http2::{HTTP2Transaction, HTTP2FrameTypeData}; +use super::http2::{HTTP2FrameTypeData, HTTP2Transaction}; use super::parser; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use std::ffi::CStr; diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 96cd765055c3..e19cb0bb90a8 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -197,7 +197,7 @@ impl HTTP2State { Err(nom::Err::Incomplete(_)) => { return AppLayerResult::incomplete( (il - input.len()) as u32, - 4 as u32, + (HTTP2_FRAME_HEADER_LEN + 4) as u32, ); } Err(_) => { diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 1be1649957d6..18881dab218d 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -62,6 +62,7 @@ impl std::str::FromStr for HTTP2FrameType { #[derive(PartialEq)] pub struct HTTP2FrameHeader { + //TODO detection on GOAWAY additional data = length pub length: u32, pub ftype: HTTP2FrameType, pub flags: u8, @@ -134,7 +135,6 @@ impl std::str::FromStr for HTTP2ErrorCode { #[derive(Clone, Copy)] pub struct HTTP2FrameGoAway { - //TODO detection on this field pub errorcode: HTTP2ErrorCode, } From d0befc18508b92b2801d3afb1431d5b383baf315 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 3 Apr 2020 09:16:24 +0200 Subject: [PATCH 13/17] detect: adds engine for u8 keywords --- src/detect-engine-uint.c | 170 ++++++++++++++++++++++++++++++++++++++- src/detect-engine-uint.h | 13 ++- src/detect-icmpv6-mtu.c | 2 +- 3 files changed, 179 insertions(+), 6 deletions(-) diff --git a/src/detect-engine-uint.c b/src/detect-engine-uint.c index 927cb35b38a8..1e503925500d 100644 --- a/src/detect-engine-uint.c +++ b/src/detect-engine-uint.c @@ -218,13 +218,175 @@ PrefilterPacketU32Compare(PrefilterPacketHeaderValue v, void *smctx) return false; } -static bool g_detect_u32_registered = false; +static bool g_detect_uint_registered = false; -void DetectU32Register(void) +void DetectUintRegister(void) { - if (g_detect_u32_registered == false) { + if (g_detect_uint_registered == false) { // register only once DetectSetupParseRegexes(PARSE_REGEX, &uint_pcre); - g_detect_u32_registered = true; + g_detect_uint_registered = true; } } + +//same as u32 but with u8 +int DetectU8Match(const uint8_t parg, const DetectU8Data *du8) +{ + switch (du8->mode) { + case DETECT_UINT_EQ: + if (parg == du8->arg1) { + return 1; + } + return 0; + case DETECT_UINT_LT: + if (parg < du8->arg1) { + return 1; + } + return 0; + case DETECT_UINT_GT: + if (parg > du8->arg1) { + return 1; + } + return 0; + case DETECT_UINT_RA: + if (parg > du8->arg1 && parg < du8->arg2) { + return 1; + } + return 0; + default: + BUG_ON("unknown mode"); + } + return 0; +} + +/** + * \brief This function is used to parse u8 options passed via some u8 keyword + * + * \param u8str Pointer to the user provided u8 options + * + * \retval DetectU8Data pointer to DetectU8Data on success + * \retval NULL on failure + */ + +DetectU8Data *DetectU8Parse (const char *u8str) +{ + DetectU8Data u8da; + DetectU8Data *u8d = NULL; + char arg1[16] = ""; + char arg2[16] = ""; + char arg3[16] = ""; + +#define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + + ret = DetectParsePcreExec(&uint_pcre, u8str, 0, 0, ov, MAX_SUBSTRINGS); + if (ret < 2 || ret > 4) { + SCLogError(SC_ERR_PCRE_MATCH, "parse error, ret %" PRId32 "", ret); + return NULL; + } + + res = pcre_copy_substring((char *) u8str, ov, MAX_SUBSTRINGS, 1, arg1, sizeof(arg1)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_COPY_SUBSTRING, "pcre_copy_substring failed"); + return NULL; + } + SCLogDebug("Arg1 \"%s\"", arg1); + + if (ret >= 3) { + res = pcre_copy_substring((char *) u8str, ov, MAX_SUBSTRINGS, 2, arg2, sizeof(arg2)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_COPY_SUBSTRING, "pcre_copy_substring failed"); + return NULL; + } + SCLogDebug("Arg2 \"%s\"", arg2); + + if (ret >= 4) { + res = pcre_copy_substring((char *) u8str, ov, MAX_SUBSTRINGS, 3, arg3, sizeof(arg3)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_COPY_SUBSTRING, "pcre_copy_substring failed"); + return NULL; + } + SCLogDebug("Arg3 \"%s\"", arg3); + } + } + + if (strlen(arg2) > 0) { + /*set the values*/ + switch(arg2[0]) { + case '<': + case '>': + if (strlen(arg3) == 0) + return NULL; + + if (ByteExtractStringUint8(&u8da.arg1, 10, strlen(arg3), arg3) < 0) { + SCLogError(SC_ERR_BYTE_EXTRACT_FAILED, "ByteExtractStringUint8 failed"); + return NULL; + } + + SCLogDebug("u8 is %"PRIu8"",u8da.arg1); + if (strlen(arg1) > 0) + return NULL; + + if (arg2[0] == '<') { + u8da.mode = DETECT_UINT_LT; + } else { // arg2[0] == '>' + u8da.mode = DETECT_UINT_GT; + } + break; + case '-': + if (strlen(arg1)== 0) + return NULL; + if (strlen(arg3)== 0) + return NULL; + + u8da.mode = DETECT_UINT_RA; + if (ByteExtractStringUint8(&u8da.arg1, 10, strlen(arg1), arg1) < 0) { + SCLogError(SC_ERR_BYTE_EXTRACT_FAILED, "ByteExtractStringUint8 failed"); + return NULL; + } + if (ByteExtractStringUint8(&u8da.arg2, 10, strlen(arg3), arg3) < 0) { + SCLogError(SC_ERR_BYTE_EXTRACT_FAILED, "ByteExtractStringUint8 failed"); + return NULL; + } + + SCLogDebug("u8 is %"PRIu8" to %"PRIu8"", u8da.arg1, u8da.arg2); + if (u8da.arg1 >= u8da.arg2) { + SCLogError(SC_ERR_INVALID_SIGNATURE, "Invalid u8 range. "); + return NULL; + } + break; + default: + u8da.mode = DETECT_UINT_EQ; + + if (strlen(arg2) > 0 || + strlen(arg3) > 0 || + strlen(arg1) == 0) + return NULL; + + if (ByteExtractStringUint8(&u8da.arg1, 10, strlen(arg1), arg1) < 0) { + SCLogError(SC_ERR_BYTE_EXTRACT_FAILED, "ByteExtractStringUint8 failed"); + return NULL; + } + } + } else { + u8da.mode = DETECT_UINT_EQ; + + if (strlen(arg3) > 0 || + strlen(arg1) == 0) + return NULL; + + if (ByteExtractStringUint8(&u8da.arg1, 10, strlen(arg1), arg1) < 0) { + SCLogError(SC_ERR_BYTE_EXTRACT_FAILED, "ByteExtractStringUint8 failed"); + return NULL; + } + } + u8d = SCCalloc(1, sizeof (DetectU8Data)); + if (unlikely(u8d == NULL)) + return NULL; + u8d->arg1 = u8da.arg1; + u8d->arg2 = u8da.arg2; + u8d->mode = u8da.mode; + + return u8d; +} diff --git a/src/detect-engine-uint.h b/src/detect-engine-uint.h index 76ec1630a716..f76a50121fc6 100644 --- a/src/detect-engine-uint.h +++ b/src/detect-engine-uint.h @@ -44,6 +44,17 @@ int DetectU32Match(const uint32_t parg, const DetectU32Data *du32); DetectU32Data *DetectU32Parse (const char *u32str); void PrefilterPacketU32Set(PrefilterPacketHeaderValue *v, void *smctx); bool PrefilterPacketU32Compare(PrefilterPacketHeaderValue v, void *smctx); -void DetectU32Register(void); + +void DetectUintRegister(void); + +typedef struct DetectU8Data_ { + uint8_t arg1; /**< first arg value in the signature*/ + uint8_t arg2; /**< second arg value in the signature, in case of range + operator*/ + DetectUintMode mode; /**< operator used in the signature */ +} DetectU8Data; + +int DetectU8Match(const uint8_t parg, const DetectU8Data *du8); +DetectU8Data *DetectU8Parse (const char *u8str); #endif /* __DETECT_UTIL_UINT_H */ diff --git a/src/detect-icmpv6-mtu.c b/src/detect-icmpv6-mtu.c index 916f7e8e6d0b..c5b8d9d48753 100644 --- a/src/detect-icmpv6-mtu.c +++ b/src/detect-icmpv6-mtu.c @@ -59,7 +59,7 @@ void DetectICMPv6mtuRegister(void) sigmatch_table[DETECT_ICMPV6MTU].SupportsPrefilter = PrefilterIcmpv6mtuIsPrefilterable; sigmatch_table[DETECT_ICMPV6MTU].SetupPrefilter = PrefilterSetupIcmpv6mtu; - DetectU32Register(); + DetectUintRegister(); return; } From e4fc2ef18411b1502dbbe04064b99c878a22f901 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 3 Apr 2020 09:16:52 +0200 Subject: [PATCH 14/17] http2: add priority --- rust/src/http2/detect.rs | 33 +++++++++++++- rust/src/http2/http2.rs | 17 ++++++++ rust/src/http2/logger.rs | 3 ++ rust/src/http2/parser.rs | 12 ++++++ src/detect-engine-register.h | 1 + src/detect-http2.c | 84 ++++++++++++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 2 deletions(-) diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index bf9034c8b9b1..a54957a45615 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -47,7 +47,7 @@ pub extern "C" fn rs_http2_tx_get_frametype( _ => {} } - return 0; + return -1; } #[no_mangle] @@ -99,7 +99,7 @@ pub extern "C" fn rs_http2_tx_get_errorcode( _ => {} } - return 0; + return -1; } #[no_mangle] @@ -124,3 +124,32 @@ pub extern "C" fn rs_http2_parse_errorcode( } } } + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_priority( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.type_data { + Some(HTTP2FrameTypeData::PRIORITY(prio)) => { + return prio.weight as i32; + } + _ => { + return -1; + } + }, + STREAM_TOCLIENT => match &tx.type_data { + Some(HTTP2FrameTypeData::PRIORITY(prio)) => { + return prio.weight as i32; + } + _ => { + return -1; + } + }, + _ => {} + } + + return -1; +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index e19cb0bb90a8..37b1721e9477 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -39,6 +39,7 @@ const HTTP2_FRAME_HEADER_LEN: usize = 9; pub enum HTTP2FrameTypeData { //TODO SETTINGS(parser::HTTP2FrameSettings), + PRIORITY(parser::HTTP2FramePriority), GOAWAY(parser::HTTP2FrameGoAway), } @@ -205,6 +206,22 @@ impl HTTP2State { } } } + parser::HTTP2FrameType::PRIORITY => { + match parser::http2_parse_frame_priority(rem) { + Ok((_, priority)) => { + tx.type_data = Some(HTTP2FrameTypeData::PRIORITY(priority)); + } + Err(nom::Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + 1) as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + } + } + } //TODO parse deeper based on other frame type _ => {} } diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index 376a58cc20b2..1de706991090 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -28,6 +28,9 @@ fn log_http2(tx: &HTTP2Transaction) -> Option { Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { js.set_string("error_code", &goaway.errorcode.to_string()); } + Some(HTTP2FrameTypeData::PRIORITY(priority)) => { + js.set_integer("priority", priority.weight as u64); + } _ => {} } return Some(js); diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index 18881dab218d..f1dd012db057 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -146,6 +146,18 @@ named!(pub http2_parse_frame_goaway, ) ); +#[derive(Clone, Copy)] +pub struct HTTP2FramePriority { + pub weight: u8, +} + +named!(pub http2_parse_frame_priority, + do_parse!( + weight: be_u8 >> + (HTTP2FramePriority{weight}) + ) +); + //TODO HTTP2FrameSettings /*pub struct HTTP2FrameSettings { id: u16, diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 028c872779e3..d625c8db7992 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -169,6 +169,7 @@ enum DetectKeywordId { DETECT_HTTP2_FRAMETYPE, DETECT_HTTP2_ERRORCODE, + DETECT_HTTP2_PRIORITY, DETECT_DCE_IFACE, DETECT_DCE_OPNUM, diff --git a/src/detect-http2.c b/src/detect-http2.c index 114614f9953c..474ed4e49c08 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -27,6 +27,7 @@ #include "detect.h" #include "detect-parse.h" #include "detect-engine.h" +#include "detect-engine-uint.h" #include "detect-http2.h" #include "util-byte.h" @@ -45,6 +46,13 @@ static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx, static int DetectHTTP2errorcodeSetup (DetectEngineCtx *, Signature *, const char *); void DetectHTTP2errorcodeFree (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 (void *); + + #ifdef UNITTESTS void DetectHTTP2RegisterTests (void); #endif @@ -89,6 +97,17 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2RegisterTests; #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 = DOC_URL DOC_VERSION "/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 = DetectHTTP2RegisterTests; +#endif + DetectAppLayerInspectEngineRegister("http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectHTTP2); @@ -97,6 +116,7 @@ void DetectHttp2Register(void) DetectEngineInspectHTTP2); g_http2_match_buffer_id = DetectBufferTypeRegister("http2"); + DetectUintRegister(); return; } @@ -281,6 +301,70 @@ void DetectHTTP2errorcodeFree(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) + +{ + int value = rs_http2_tx_get_priority(txv, flags); + if (value < 0) { + //no value, no match + return 0; + } + + const DetectU8Data *du8 = (const DetectU8Data *)ctx; + return DetectU8Match(value, du8); +} + +/** + * \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(void *ptr) +{ + SCFree(ptr); +} + #ifdef UNITTESTS #include "tests/detect-http2.c" #endif From c193fd8e44279e2522b0676215338e22c0300b3f Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 3 Apr 2020 09:56:17 +0200 Subject: [PATCH 15/17] More HTTP2 simple frame types parsing --- rust/src/http2/detect.rs | 35 ++++++++++++++++ rust/src/http2/http2.rs | 41 +++++++++++++++++- rust/src/http2/logger.rs | 6 +++ rust/src/http2/parser.rs | 27 ++++++++++++ src/detect-engine-register.h | 1 + src/detect-http2.c | 80 ++++++++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 1 deletion(-) diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index a54957a45615..9d3059384d06 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -84,6 +84,9 @@ pub extern "C" fn rs_http2_tx_get_errorcode( Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { return goaway.errorcode as i32; } + Some(HTTP2FrameTypeData::RSTSTREAM(rst)) => { + return rst.errorcode as i32; + } _ => { return -1; } @@ -92,6 +95,9 @@ pub extern "C" fn rs_http2_tx_get_errorcode( Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { return goaway.errorcode as i32; } + Some(HTTP2FrameTypeData::RSTSTREAM(rst)) => { + return rst.errorcode as i32; + } _ => { return -1; } @@ -153,3 +159,32 @@ pub extern "C" fn rs_http2_tx_get_priority( return -1; } + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_window( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.type_data { + Some(HTTP2FrameTypeData::WINDOWUPDATE(wu)) => { + return wu.sizeinc as i32; + } + _ => { + return -1; + } + }, + STREAM_TOCLIENT => match &tx.type_data { + Some(HTTP2FrameTypeData::WINDOWUPDATE(wu)) => { + return wu.sizeinc as i32; + } + _ => { + return -1; + } + }, + _ => {} + } + + return -1; +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 37b1721e9477..b95ac67ac6df 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -38,9 +38,15 @@ pub enum HTTP2ConnectionState { const HTTP2_FRAME_HEADER_LEN: usize = 9; pub enum HTTP2FrameTypeData { - //TODO SETTINGS(parser::HTTP2FrameSettings), + //TODO PUSH_PROMISE + //TODO DATA + //TODO HEADERS + //TODO CONTINATION PRIORITY(parser::HTTP2FramePriority), GOAWAY(parser::HTTP2FrameGoAway), + RSTSTREAM(parser::HTTP2FrameRstStream), + //TODO SETTINGS(parser::HTTP2FrameSettings), + WINDOWUPDATE(parser::HTTP2FrameWindowUpdate), } pub struct HTTP2Transaction { @@ -206,6 +212,22 @@ impl HTTP2State { } } } + parser::HTTP2FrameType::RSTSTREAM => { + match parser::http2_parse_frame_rststream(rem) { + Ok((_, rst)) => { + tx.type_data = Some(HTTP2FrameTypeData::RSTSTREAM(rst)); + } + Err(nom::Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + 4) as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + } + } + } parser::HTTP2FrameType::PRIORITY => { match parser::http2_parse_frame_priority(rem) { Ok((_, priority)) => { @@ -222,6 +244,23 @@ impl HTTP2State { } } } + parser::HTTP2FrameType::WINDOWUPDATE => { + match parser::http2_parse_frame_windowupdate(rem) { + Ok((_, wu)) => { + tx.type_data = Some(HTTP2FrameTypeData::WINDOWUPDATE(wu)); + } + Err(nom::Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + 4) as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + } + } + } + //ignore ping case with opaque u64 //TODO parse deeper based on other frame type _ => {} } diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index 1de706991090..3edd0e80d60e 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -28,9 +28,15 @@ fn log_http2(tx: &HTTP2Transaction) -> Option { Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { js.set_string("error_code", &goaway.errorcode.to_string()); } + Some(HTTP2FrameTypeData::RSTSTREAM(rst)) => { + js.set_string("error_code", &rst.errorcode.to_string()); + } Some(HTTP2FrameTypeData::PRIORITY(priority)) => { js.set_integer("priority", priority.weight as u64); } + Some(HTTP2FrameTypeData::WINDOWUPDATE(wu)) => { + js.set_integer("window", wu.sizeinc as u64); + } _ => {} } return Some(js); diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index f1dd012db057..c64c9c0e1c9a 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -146,6 +146,19 @@ named!(pub http2_parse_frame_goaway, ) ); +#[derive(Clone, Copy)] +pub struct HTTP2FrameRstStream { + pub errorcode: HTTP2ErrorCode, +} + +named!(pub http2_parse_frame_rststream, + do_parse!( + errorcode: map_opt!( be_u32, + num::FromPrimitive::from_u32 ) >> + (HTTP2FrameRstStream{errorcode}) + ) +); + #[derive(Clone, Copy)] pub struct HTTP2FramePriority { pub weight: u8, @@ -158,6 +171,20 @@ named!(pub http2_parse_frame_priority, ) ); +#[derive(Clone, Copy)] +pub struct HTTP2FrameWindowUpdate { + pub reserved: u8, + pub sizeinc: u32, +} + +named!(pub http2_parse_frame_windowupdate, + do_parse!( + sizeinc: bits!( tuple!( take_bits!(1u8), + take_bits!(31u32) ) ) >> + (HTTP2FrameWindowUpdate{reserved:sizeinc.0, sizeinc:sizeinc.1}) + ) +); + //TODO HTTP2FrameSettings /*pub struct HTTP2FrameSettings { id: u16, diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index d625c8db7992..8405b41607c3 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -170,6 +170,7 @@ enum DetectKeywordId { DETECT_HTTP2_FRAMETYPE, DETECT_HTTP2_ERRORCODE, DETECT_HTTP2_PRIORITY, + DETECT_HTTP2_WINDOW, DETECT_DCE_IFACE, DETECT_DCE_OPNUM, diff --git a/src/detect-http2.c b/src/detect-http2.c index 474ed4e49c08..609b620fd609 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -52,6 +52,11 @@ static int DetectHTTP2priorityMatch(DetectEngineThreadCtx *det_ctx, static int DetectHTTP2prioritySetup (DetectEngineCtx *, Signature *, const char *); void DetectHTTP2priorityFree (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 (void *); #ifdef UNITTESTS void DetectHTTP2RegisterTests (void); @@ -108,6 +113,17 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_PRIORITY].RegisterTests = DetectHTTP2RegisterTests; #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 = DOC_URL DOC_VERSION "/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 = DetectHTTP2RegisterTests; +#endif + DetectAppLayerInspectEngineRegister("http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectHTTP2); @@ -365,6 +381,70 @@ void DetectHTTP2priorityFree(void *ptr) SCFree(ptr); } +/** + * \brief This function is used to match HTTP2 error code 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) + +{ + int value = rs_http2_tx_get_window(txv, flags); + if (value < 0) { + //no value, no match + return 0; + } + + const DetectU32Data *du32 = (const DetectU32Data *)ctx; + return DetectU32Match(value, du32); +} + +/** + * \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(void *ptr) +{ + SCFree(ptr); +} + #ifdef UNITTESTS #include "tests/detect-http2.c" #endif From 708ce1ace3c309749635d94580a339c78cbfe657 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 3 Apr 2020 10:28:51 +0200 Subject: [PATCH 16/17] http2 minor fixes --- rust/src/http2/http2.rs | 13 +++----- rust/src/http2/parser.rs | 10 +++--- src/detect-http2.c | 9 +++-- src/tests/detect-http2.c | 72 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 83 insertions(+), 21 deletions(-) diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index b95ac67ac6df..23771ae72749 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -261,7 +261,6 @@ impl HTTP2State { } } //ignore ping case with opaque u64 - //TODO parse deeper based on other frame type _ => {} } self.transactions.push(tx); @@ -283,9 +282,7 @@ impl HTTP2State { HTTP2_FRAME_HEADER_LEN as u32, ); } else { - //TODO BUG_ON ? - SCLogDebug!("HTTP2 invalid length frame header"); - return AppLayerResult::err(); + panic!("HTTP2 invalid length frame header"); } } Err(_) => { @@ -319,7 +316,7 @@ impl HTTP2State { tx.ftype = Some(head.ftype); self.transactions.push(tx); - //TODO parse deeper based on frame type + //TODO parse frame types as in request once transactions are well handled let hl = head.length as usize; if rem.len() < hl { let rl = rem.len() as u32; @@ -337,9 +334,7 @@ impl HTTP2State { HTTP2_FRAME_HEADER_LEN as u32, ); } else { - //TODO BUG_ON ? - SCLogDebug!("HTTP2 invalid length frame header"); - return AppLayerResult::err(); + panic!("HTTP2 invalid length frame header"); } } Err(_) => { @@ -381,7 +376,7 @@ export_tx_set_detect_state!(rs_http2_tx_set_detect_state, HTTP2Transaction); export_tx_detect_flags_set!(rs_http2_set_tx_detect_flags, HTTP2Transaction); export_tx_detect_flags_get!(rs_http2_get_tx_detect_flags, HTTP2Transaction); -//TODO connection upgrade from HTTP1 +//TODO 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( diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index c64c9c0e1c9a..a9201176ee45 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -43,8 +43,9 @@ impl std::str::FromStr for HTTP2FrameType { type Err = String; fn from_str(s: &str) -> Result { - //TODO let su = s.to_uppercase(); - match s { + 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), @@ -113,8 +114,9 @@ impl std::str::FromStr for HTTP2ErrorCode { type Err = String; fn from_str(s: &str) -> Result { - //TODO let su = s.to_uppercase(); - match s { + 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), diff --git a/src/detect-http2.c b/src/detect-http2.c index 609b620fd609..88c81863c07e 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -87,7 +87,7 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_FRAMETYPE].Setup = DetectHTTP2frametypeSetup; sigmatch_table[DETECT_HTTP2_FRAMETYPE].Free = DetectHTTP2frametypeFree; #ifdef UNITTESTS - sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2RegisterTests; + sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2frameTypeRegisterTests; #endif sigmatch_table[DETECT_HTTP2_ERRORCODE].name = "http2.errorcode"; @@ -98,8 +98,7 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_ERRORCODE].Setup = DetectHTTP2errorcodeSetup; sigmatch_table[DETECT_HTTP2_ERRORCODE].Free = DetectHTTP2errorcodeFree; #ifdef UNITTESTS - //TODO should we call multiple times DetectHTTP2RegisterTests ? - sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2RegisterTests; + sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2errorCodeRegisterTests; #endif sigmatch_table[DETECT_HTTP2_PRIORITY].name = "http2.priority"; @@ -110,7 +109,7 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_PRIORITY].Setup = DetectHTTP2prioritySetup; sigmatch_table[DETECT_HTTP2_PRIORITY].Free = DetectHTTP2priorityFree; #ifdef UNITTESTS - sigmatch_table[DETECT_HTTP2_PRIORITY].RegisterTests = DetectHTTP2RegisterTests; + sigmatch_table[DETECT_HTTP2_PRIORITY].RegisterTests = DetectHTTP2priorityRegisterTests; #endif sigmatch_table[DETECT_HTTP2_WINDOW].name = "http2.window"; @@ -121,7 +120,7 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_WINDOW].Setup = DetectHTTP2windowSetup; sigmatch_table[DETECT_HTTP2_WINDOW].Free = DetectHTTP2windowFree; #ifdef UNITTESTS - sigmatch_table[DETECT_HTTP2_WINDOW].RegisterTests = DetectHTTP2RegisterTests; + sigmatch_table[DETECT_HTTP2_WINDOW].RegisterTests = DetectHTTP2windowRegisterTests; #endif DetectAppLayerInspectEngineRegister("http2", diff --git a/src/tests/detect-http2.c b/src/tests/detect-http2.c index 64871ba77ef5..7d43c203f19a 100644 --- a/src/tests/detect-http2.c +++ b/src/tests/detect-http2.c @@ -38,13 +38,79 @@ static int DetectHTTP2frameTypeParseTest01 (void) DetectEngineCtxFree(de_ctx); PASS; - } /** - * \brief this function registers unit tests for DetectICMPv6mtu + * \brief this function registers unit tests for DetectHTTP2frameType */ -void DetectHTTP2RegisterTests(void) +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); +} + From 57b42432e94774981318574692c4b2044d7904e7 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 3 Apr 2020 11:36:32 +0200 Subject: [PATCH 17/17] http2 settings --- rust/src/http2/detect.rs | 81 ++++++++++++++++++ rust/src/http2/http2.rs | 34 ++++++-- rust/src/http2/logger.rs | 4 + rust/src/http2/parser.rs | 56 +++++++++++-- src/detect-engine-register.h | 1 + src/detect-http2.c | 155 +++++++++++++++++++++++++++++++++++ src/tests/detect-http2.c | 22 +++++ 7 files changed, 338 insertions(+), 15 deletions(-) diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs index 9d3059384d06..ddafdc099edc 100644 --- a/rust/src/http2/detect.rs +++ b/rust/src/http2/detect.rs @@ -188,3 +188,84 @@ pub extern "C" fn rs_http2_tx_get_window( return -1; } + +#[no_mangle] +pub extern "C" fn rs_http2_parse_settingsid( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = unsafe { CStr::from_ptr(str) }; + match ft_name.to_str() { + Ok(s) => { + let p = parser::HTTP2SettingsId::from_str(s); + match p { + Ok(x) => { + return x as i32; + } + Err(_) => { + return -1; + } + } + } + Err(_) => { + return -1; + } + } +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_settingsid( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.type_data { + Some(HTTP2FrameTypeData::SETTINGS(set)) => { + return set.id as i32; + } + _ => { + return -1; + } + }, + STREAM_TOCLIENT => match &tx.type_data { + Some(HTTP2FrameTypeData::SETTINGS(set)) => { + return set.id as i32; + } + _ => { + return -1; + } + }, + _ => {} + } + + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_http2_tx_get_settingsvalue( + tx: *mut std::os::raw::c_void, + direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + match direction { + STREAM_TOSERVER => match &tx.type_data { + Some(HTTP2FrameTypeData::SETTINGS(set)) => { + return set.value as i32; + } + _ => { + return -1; + } + }, + STREAM_TOCLIENT => match &tx.type_data { + Some(HTTP2FrameTypeData::SETTINGS(set)) => { + return set.value as i32; + } + _ => { + return -1; + } + }, + _ => {} + } + + return -1; +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs index 23771ae72749..d47768feeed5 100644 --- a/rust/src/http2/http2.rs +++ b/rust/src/http2/http2.rs @@ -45,7 +45,7 @@ pub enum HTTP2FrameTypeData { PRIORITY(parser::HTTP2FramePriority), GOAWAY(parser::HTTP2FrameGoAway), RSTSTREAM(parser::HTTP2FrameRstStream), - //TODO SETTINGS(parser::HTTP2FrameSettings), + SETTINGS(parser::HTTP2FrameSettings), WINDOWUPDATE(parser::HTTP2FrameWindowUpdate), } @@ -201,10 +201,26 @@ impl HTTP2State { Ok((_, goaway)) => { tx.type_data = Some(HTTP2FrameTypeData::GOAWAY(goaway)); } - Err(nom::Err::Incomplete(_)) => { + Err(nom::Err::Incomplete(nom::Needed::Size(x))) => { return AppLayerResult::incomplete( (il - input.len()) as u32, - (HTTP2_FRAME_HEADER_LEN + 4) as u32, + (HTTP2_FRAME_HEADER_LEN + x) as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + } + } + } + parser::HTTP2FrameType::SETTINGS => { + match parser::http2_parse_frame_settings(rem) { + Ok((_, set)) => { + tx.type_data = Some(HTTP2FrameTypeData::SETTINGS(set)); + } + Err(nom::Err::Incomplete(nom::Needed::Size(x))) => { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + x) as u32, ); } Err(_) => { @@ -217,10 +233,10 @@ impl HTTP2State { Ok((_, rst)) => { tx.type_data = Some(HTTP2FrameTypeData::RSTSTREAM(rst)); } - Err(nom::Err::Incomplete(_)) => { + Err(nom::Err::Incomplete(nom::Needed::Size(x))) => { return AppLayerResult::incomplete( (il - input.len()) as u32, - (HTTP2_FRAME_HEADER_LEN + 4) as u32, + (HTTP2_FRAME_HEADER_LEN + x) as u32, ); } Err(_) => { @@ -233,10 +249,10 @@ impl HTTP2State { Ok((_, priority)) => { tx.type_data = Some(HTTP2FrameTypeData::PRIORITY(priority)); } - Err(nom::Err::Incomplete(_)) => { + Err(nom::Err::Incomplete(nom::Needed::Size(x))) => { return AppLayerResult::incomplete( (il - input.len()) as u32, - (HTTP2_FRAME_HEADER_LEN + 1) as u32, + (HTTP2_FRAME_HEADER_LEN + x) as u32, ); } Err(_) => { @@ -249,10 +265,10 @@ impl HTTP2State { Ok((_, wu)) => { tx.type_data = Some(HTTP2FrameTypeData::WINDOWUPDATE(wu)); } - Err(nom::Err::Incomplete(_)) => { + Err(nom::Err::Incomplete(nom::Needed::Size(x))) => { return AppLayerResult::incomplete( (il - input.len()) as u32, - (HTTP2_FRAME_HEADER_LEN + 4) as u32, + (HTTP2_FRAME_HEADER_LEN + x) as u32, ); } Err(_) => { diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs index 3edd0e80d60e..c913378f437b 100644 --- a/rust/src/http2/logger.rs +++ b/rust/src/http2/logger.rs @@ -28,6 +28,10 @@ fn log_http2(tx: &HTTP2Transaction) -> Option { Some(HTTP2FrameTypeData::GOAWAY(goaway)) => { js.set_string("error_code", &goaway.errorcode.to_string()); } + Some(HTTP2FrameTypeData::SETTINGS(set)) => { + js.set_string("settings_id", &set.id.to_string()); + js.set_integer("settings_value", set.value as u64); + } Some(HTTP2FrameTypeData::RSTSTREAM(rst)) => { js.set_string("error_code", &rst.errorcode.to_string()); } diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs index a9201176ee45..212244adef4f 100644 --- a/rust/src/http2/parser.rs +++ b/rust/src/http2/parser.rs @@ -15,7 +15,7 @@ * 02110-1301, USA. */ -use nom::number::streaming::{be_u32, be_u8}; +use nom::number::streaming::{be_u16, be_u32, be_u8}; use std::fmt; #[repr(u8)] @@ -187,11 +187,55 @@ named!(pub http2_parse_frame_windowupdate, ) ); -//TODO HTTP2FrameSettings -/*pub struct HTTP2FrameSettings { -id: u16, -value: u32, -}*/ +#[repr(u16)] +#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)] +pub enum HTTP2SettingsId { + SETTINGSHEADERTABLESIZE = 1, + SETTINGSENABLEPUSH = 2, + SETTINGSMAXCONCURRENTSTREAMS = 3, + SETTINGSINITIALWINDOWSIZE = 4, + SETTINGSMAXFRAMESIZE = 5, + SETTINGSMAXHEADERLISTSIZE = 6, +} + +impl fmt::Display for HTTP2SettingsId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2SettingsId { + type Err = String; + + fn from_str(s: &str) -> Result { + let su = s.to_uppercase(); + let su_slice: &str = &*su; + match su_slice { + "SETTINGS_HEADER_TABLE_SIZE" => Ok(HTTP2SettingsId::SETTINGSHEADERTABLESIZE), + "SETTINGS_ENABLE_PUSH" => Ok(HTTP2SettingsId::SETTINGSENABLEPUSH), + "SETTINGS_MAX_CONCURRENT_STREAMS" => Ok(HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS), + "SETTINGS_INITIAL_WINDOW_SIZE" => Ok(HTTP2SettingsId::SETTINGSINITIALWINDOWSIZE), + "SETTINGS_MAX_FRAME_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXFRAMESIZE), + "SETTINGS_MAX_HEADER_LIST_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXHEADERLISTSIZE), + _ => Err(format!("'{}' is not a valid value for HTTP2SettingsId", s)), + } + } +} + +#[derive(Clone, Copy)] +pub struct HTTP2FrameSettings { + pub id: HTTP2SettingsId, + pub value: u32, +} + +named!(pub http2_parse_frame_settings, + do_parse!( + id: map_opt!( be_u16, + num::FromPrimitive::from_u16 ) >> + value: be_u32 >> + (HTTP2FrameSettings{id, value}) + ) +); #[cfg(test)] mod tests { diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 8405b41607c3..68c5521da482 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -171,6 +171,7 @@ enum DetectKeywordId { DETECT_HTTP2_ERRORCODE, DETECT_HTTP2_PRIORITY, DETECT_HTTP2_WINDOW, + DETECT_HTTP2_SETTINGS, DETECT_DCE_IFACE, DETECT_DCE_OPNUM, diff --git a/src/detect-http2.c b/src/detect-http2.c index 88c81863c07e..4a8ffc152c87 100644 --- a/src/detect-http2.c +++ b/src/detect-http2.c @@ -58,6 +58,12 @@ static int DetectHTTP2windowMatch(DetectEngineThreadCtx *det_ctx, static int DetectHTTP2windowSetup (DetectEngineCtx *, Signature *, const char *); void DetectHTTP2windowFree (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 (void *); + #ifdef UNITTESTS void DetectHTTP2RegisterTests (void); #endif @@ -123,6 +129,17 @@ void DetectHttp2Register(void) sigmatch_table[DETECT_HTTP2_WINDOW].RegisterTests = DetectHTTP2windowRegisterTests; #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 = DOC_URL DOC_VERSION "/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 + DetectAppLayerInspectEngineRegister("http2", ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0, DetectEngineInspectHTTP2); @@ -244,6 +261,7 @@ static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx, //no value, no match return 0; } + //TODO handle negation rules return *detect == (uint32_t) value; } @@ -444,6 +462,143 @@ void DetectHTTP2windowFree(void *ptr) SCFree(ptr); } +typedef struct DetectHTTP2settingsSigCtx_ { + uint16_t id; /**identifier*/ + DetectU32Data *value; /** optional value*/ +} DetectHTTP2settingsSigCtx; + +/** + * \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) + +{ + int id = rs_http2_tx_get_settingsid(txv, flags); + if (id < 0) { + //no settings, no match + return 0; + } + + const DetectHTTP2settingsSigCtx *setctx = (const DetectHTTP2settingsSigCtx *)ctx; + if (setctx->id != id) { + return 0; + } else if (setctx->value == NULL) { + //no value to match + return 1; + } else { + int value = rs_http2_tx_get_settingsvalue(txv, flags); + if (value < 0) { + return 0; + } + return DetectU32Match(value, setctx->value); + } +} + +static int DetectHTTP2FuncParseSettingsId(const char *str, uint16_t *id) +{ + // first parse numeric value + if (ByteExtractStringUint16(id, 10, strlen(str), str) >= 0) { + return 1; + } + + // it it failed so far, parse string value from enumeration + int r = rs_http2_parse_settingsid(str); + if (r >= 0) { + *id = r; + return 1; + } + + return 0; +} + +#define HTTP2_MAX_SETTINGS_ID_LEN 64 + +/** + * \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; + + const char * space = strchr(str, ' '); + + DetectHTTP2settingsSigCtx *http2set = SCCalloc(1, sizeof(DetectHTTP2settingsSigCtx)); + if (http2set == NULL) + return -1; + + if (space) { + // a space separates identifier and value + + // copy and isolate first part of string + char str_first[HTTP2_MAX_SETTINGS_ID_LEN]; + if (HTTP2_MAX_SETTINGS_ID_LEN <= space - str) { + SCFree(http2set); + return -1; + } + strncpy(str_first, str, space - str); + //TODO better no copy, and pass a length argument next ? + + if (!DetectHTTP2FuncParseSettingsId(str_first, &http2set->id)) { + SCLogError(SC_ERR_INVALID_SIGNATURE, + "Invalid argument \"%s\" supplied to http2.settings keyword.", str); + SCFree(http2set); + return -1; + } + + http2set->value = DetectU32Parse(str+1); + if (http2set->value == NULL) { + SCFree(http2set); + return -1; + } + } else { + // no space means only id with no value + if (!DetectHTTP2FuncParseSettingsId(str, &http2set->id)) { + SCLogError(SC_ERR_INVALID_SIGNATURE, + "Invalid argument \"%s\" supplied to http2.settings keyword.", str); + SCFree(http2set); + return -1; + } + } + + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) { + DetectHTTP2settingsFree(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 DetectHTTP2settingsSigCtx + * + * \param ptr pointer to DetectHTTP2settingsSigCtx + */ +void DetectHTTP2settingsFree(void *ptr) +{ + DetectHTTP2settingsSigCtx *http2set = (DetectHTTP2settingsSigCtx *) ptr; + SCFree(http2set->value); + SCFree(http2set); +} + #ifdef UNITTESTS #include "tests/detect-http2.c" #endif diff --git a/src/tests/detect-http2.c b/src/tests/detect-http2.c index 7d43c203f19a..6386eae6773a 100644 --- a/src/tests/detect-http2.c +++ b/src/tests/detect-http2.c @@ -114,3 +114,25 @@ 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); +}