Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/http range/v29 #6355

Closed
wants to merge 18 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion rules/http-events.rules
Original file line number Diff line number Diff line change
@@ -83,4 +83,6 @@ alert http any any -> any any (msg:"SURICATA HTTP compression bomb"; flow:establ

alert http any any -> any any (msg:"SURICATA HTTP too many warnings"; flow:established; app-layer-event:http.too_many_warnings; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221050; rev:1;)

# next sid 2221051
alert http any any -> any any (msg:"SURICATA HTTP invalid Range header value"; flow:established; app-layer-event:http.range_invalid; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221051; rev:1;)

# next sid 2221052
1 change: 1 addition & 0 deletions rules/http2-events.rules
Original file line number Diff line number Diff line change
@@ -15,3 +15,4 @@ alert http2 any any -> any any (msg:"SURICATA HTTP2 too long frame data"; flow:e
alert http2 any any -> any any (msg:"SURICATA HTTP2 stream identifier reuse"; flow:established; app-layer-event:http2.stream_id_reuse; classtype:protocol-command-decode; sid:2290007; rev:1;)
alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid HTTP1 settings during upgrade"; flow:established; app-layer-event:http2.invalid_http1_settings; classtype:protocol-command-decode; sid:2290008; rev:1;)
alert http2 any any -> any any (msg:"SURICATA HTTP2 failed decompression"; flow:established; app-layer-event:http2.failed_decompression; classtype:protocol-command-decode; sid:2290009; rev:1;)
alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid range header"; flow:established; app-layer-event:http2.invalid_range; classtype:protocol-command-decode; sid:2290010; rev:1;)
2 changes: 2 additions & 0 deletions rust/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -87,6 +87,8 @@ exclude = [
"CLuaState",
"DetectEngineState",
"Flow",
"StreamingBufferConfig",
"HttpRangeContainerBlock",
"FileContainer",
"JsonT",
"IKEState",
22 changes: 18 additions & 4 deletions rust/src/core.rs
Original file line number Diff line number Diff line change
@@ -97,11 +97,22 @@ pub type AppLayerDecoderEventsSetEventRawFunc =
pub type AppLayerDecoderEventsFreeEventsFunc =
extern "C" fn (events: *mut *mut AppLayerDecoderEvents);

pub enum SuricataStreamingBufferConfig {}

pub enum StreamingBufferConfig {}

// Opaque flow type (defined in C)
pub enum HttpRangeContainerBlock {}

pub type SCHttpRangeFreeBlock = extern "C" fn (
c: *mut HttpRangeContainerBlock);
pub type SCHTPFileCloseHandleRange = extern "C" fn (
fc: *mut FileContainer,
flags: u16,
c: *mut HttpRangeContainerBlock,
data: *const u8,
data_len: u32);
pub type SCFileOpenFileWithId = extern "C" fn (
file_container: &FileContainer,
sbcfg: &SuricataStreamingBufferConfig,
sbcfg: &StreamingBufferConfig,
track_id: u32,
name: *const u8, name_len: u16,
data: *const u8, data_len: u32,
@@ -144,6 +155,9 @@ pub struct SuricataContext {
AppLayerDecoderEventsFreeEvents: AppLayerDecoderEventsFreeEventsFunc,
pub AppLayerParserTriggerRawStreamReassembly: AppLayerParserTriggerRawStreamReassemblyFunc,

pub HttpRangeFreeBlock: SCHttpRangeFreeBlock,
pub HTPFileCloseHandleRange: SCHTPFileCloseHandleRange,

pub FileOpenFile: SCFileOpenFileWithId,
pub FileCloseFile: SCFileCloseFileById,
pub FileAppendData: SCFileAppendDataById,
@@ -158,7 +172,7 @@ pub struct SuricataContext {
#[allow(non_snake_case)]
#[repr(C)]
pub struct SuricataFileContext {
pub files_sbcfg: &'static SuricataStreamingBufferConfig,
pub files_sbcfg: &'static StreamingBufferConfig,
}

extern {
4 changes: 2 additions & 2 deletions rust/src/http2/decompression.rs
Original file line number Diff line number Diff line change
@@ -187,7 +187,7 @@ impl HTTP2DecoderHalf {
}

pub fn decompress<'a>(
&'a mut self, input: &'a [u8], output: &'a mut Vec<u8>,
&mut self, input: &'a [u8], output: &'a mut Vec<u8>,
) -> io::Result<&'a [u8]> {
match self.decoder {
HTTP2Decompresser::GZIP(ref mut gzip_decoder) => {
@@ -249,7 +249,7 @@ impl HTTP2Decoder {
}

pub fn decompress<'a>(
&'a mut self, input: &'a [u8], output: &'a mut Vec<u8>, dir: u8,
&mut self, input: &'a [u8], output: &'a mut Vec<u8>, dir: u8,
) -> io::Result<&'a [u8]> {
if dir == STREAM_TOCLIENT {
return self.decoder_tc.decompress(input, output);
38 changes: 38 additions & 0 deletions rust/src/http2/detect.rs
Original file line number Diff line number Diff line change
@@ -484,6 +484,44 @@ fn http2_frames_get_header_firstvalue<'a>(
return Err(());
}

// same as http2_frames_get_header_value but returns a new Vec
// instead of using the transation to store the result slice
pub fn http2_frames_get_header_value_vec(
tx: &HTTP2Transaction, direction: u8, name: &str,
) -> Result<Vec<u8>, ()> {
let mut found = 0;
let mut vec = Vec::new();
let frames = if direction == STREAM_TOSERVER {
&tx.frames_ts
} else {
&tx.frames_tc
};
for i in 0..frames.len() {
if let Some(blocks) = http2_header_blocks(&frames[i]) {
for block in blocks.iter() {
if block.name == name.as_bytes().to_vec() {
if found == 0 {
vec.extend_from_slice(&block.value);
found = 1;
} else if found == 1 {
vec.extend_from_slice(&[b',', b' ']);
vec.extend_from_slice(&block.value);
found = 2;
} else {
vec.extend_from_slice(&[b',', b' ']);
vec.extend_from_slice(&block.value);
}
}
}
}
}
if found == 0 {
return Err(());
} else {
return Ok(vec);
}
}

fn http2_frames_get_header_value<'a>(
tx: &'a mut HTTP2Transaction, direction: u8, name: &str,
) -> Result<&'a [u8], ()> {
65 changes: 61 additions & 4 deletions rust/src/http2/http2.rs
Original file line number Diff line number Diff line change
@@ -16,11 +16,14 @@
*/

use super::decompression;
use super::detect;
use super::parser;
use super::range;

use crate::applayer::{self, *};
use crate::core::{
self, AppProto, Flow, SuricataFileContext, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP,
STREAM_TOCLIENT, STREAM_TOSERVER,
self, AppProto, Flow, HttpRangeContainerBlock, SuricataFileContext, ALPROTO_FAILED,
ALPROTO_UNKNOWN, IPPROTO_TCP, SC, STREAM_TOCLIENT, STREAM_TOSERVER,
};
use crate::filecontainer::*;
use crate::filetracker::*;
@@ -129,11 +132,12 @@ pub struct HTTP2Transaction {
pub frames_ts: Vec<HTTP2Frame>,

decoder: decompression::HTTP2Decoder,
pub file_range: *mut HttpRangeContainerBlock,

de_state: Option<*mut core::DetectEngineState>,
events: *mut core::AppLayerDecoderEvents,
tx_data: AppLayerTxData,
ft_tc: FileTransferTracker,
pub ft_tc: FileTransferTracker,
ft_ts: FileTransferTracker,

//temporary escaped header for detection
@@ -151,6 +155,7 @@ impl HTTP2Transaction {
frames_tc: Vec::new(),
frames_ts: Vec::new(),
decoder: decompression::HTTP2Decoder::new(),
file_range: std::ptr::null_mut(),
de_state: None,
events: std::ptr::null_mut(),
tx_data: AppLayerTxData::new(),
@@ -167,6 +172,27 @@ impl HTTP2Transaction {
if let Some(state) = self.de_state {
core::sc_detect_engine_state_free(state);
}
if self.file_range != std::ptr::null_mut() {
match unsafe { SC } {
None => panic!("BUG no suricata_config"),
Some(c) => {
//TODO get a file container instead of NULL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: address or remove comment if its not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be much easier when we have file per tx

Right now, I should do something like state.files.get(STREAM_TOCLIENT) but I do not have an easy reference to state in HTTP2Transaction::free

So, what should we do ?

(c.HTPFileCloseHandleRange)(
std::ptr::null_mut(),
0,
self.file_range,
std::ptr::null_mut(),
0,
);
(c.HttpRangeFreeBlock)(self.file_range);
}
}
}
}

pub fn set_event(&mut self, event: HTTP2Event) {
let ev = event as u8;
core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, ev);
}

fn handle_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: u8) {
@@ -179,7 +205,7 @@ impl HTTP2Transaction {

fn decompress<'a>(
&'a mut self, input: &'a [u8], dir: u8, sfcm: &'static SuricataFileContext, over: bool,
files: &mut FileContainer, flags: u16,
files: &mut FileContainer, flags: u16, flow: *const Flow,
) -> io::Result<()> {
let mut output = Vec::with_capacity(decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE);
let decompressed = self.decoder.decompress(input, &mut output, dir)?;
@@ -190,6 +216,31 @@ impl HTTP2Transaction {
// we are now sure that new_chunk will open a file
// even if it may close it right afterwards
self.tx_data.incr_files_opened();
if let Ok(value) = detect::http2_frames_get_header_value_vec(
self,
STREAM_TOCLIENT,
"content-range",
) {
match range::http2_parse_content_range(&value) {
Ok((_, v)) => {
range::http2_range_open(self, &v, flow, sfcm, flags, decompressed);
if over {
range::http2_range_close(self, files, flags, &[])
}
}
_ => {
self.set_event(HTTP2Event::InvalidRange);
}
}
}
} else {
if self.file_range != std::ptr::null_mut() {
if over {
range::http2_range_close(self, files, flags, decompressed)
} else {
range::http2_range_append(self.file_range, decompressed)
}
}
}
self.ft_tc.new_chunk(
sfcm,
@@ -321,6 +372,7 @@ pub enum HTTP2Event {
StreamIdReuse,
InvalidHTTP1Settings,
FailedDecompression,
InvalidRange,
}

pub struct HTTP2DynTable {
@@ -350,6 +402,7 @@ pub struct HTTP2State {
transactions: Vec<HTTP2Transaction>,
progress: HTTP2ConnectionState,
pub files: Files,
flow: *const Flow,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally we're not adding this if we can just pass it around as a function argument

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right

}

impl HTTP2State {
@@ -366,6 +419,7 @@ impl HTTP2State {
transactions: Vec::new(),
progress: HTTP2ConnectionState::Http2StateInit,
files: Files::default(),
flow: std::ptr::null_mut(),
}
}

@@ -825,6 +879,7 @@ impl HTTP2State {
over,
files,
flags,
self.flow,
) {
Err(_e) => {
self.set_event(HTTP2Event::FailedDecompression);
@@ -1025,6 +1080,7 @@ pub unsafe extern "C" fn rs_http2_parse_ts(

state.files.flags_ts = FileFlowToFlags(flow, STREAM_TOSERVER);
state.files.flags_ts = state.files.flags_ts | FILE_USE_DETECT;
state.flow = flow;
return state.parse_ts(buf);
}

@@ -1037,6 +1093,7 @@ pub unsafe extern "C" fn rs_http2_parse_tc(
let buf = build_slice!(input, input_len as usize);
state.files.flags_tc = FileFlowToFlags(flow, STREAM_TOCLIENT);
state.files.flags_tc = state.files.flags_tc | FILE_USE_DETECT;
state.flow = flow;
return state.parse_tc(buf);
}

1 change: 1 addition & 0 deletions rust/src/http2/mod.rs
Original file line number Diff line number Diff line change
@@ -21,3 +21,4 @@ pub mod http2;
mod huffman;
pub mod logger;
mod parser;
mod range;
Loading