mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1943 lines
74 KiB
Rust
1943 lines
74 KiB
Rust
/* Copyright (C) 2020-2022 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::decompression;
|
|
use super::detect;
|
|
use super::parser;
|
|
use super::range;
|
|
|
|
use super::range::{SCHTPFileCloseHandleRange, SCHttpRangeFreeBlock};
|
|
use crate::applayer::{self, *};
|
|
use crate::conf::{conf_get, get_memval};
|
|
use crate::core::*;
|
|
use crate::direction::Direction;
|
|
use crate::dns::dns::DnsVariant;
|
|
use crate::filecontainer::FileContainerWrapper;
|
|
use crate::filetracker::*;
|
|
use crate::flow::Flow;
|
|
use crate::frames::Frame;
|
|
|
|
use crate::dns::dns::{dns_parse_request, dns_parse_response, DNSTransaction};
|
|
|
|
use nom7::Err;
|
|
use std;
|
|
use std::collections::VecDeque;
|
|
use std::ffi::CString;
|
|
use std::fmt;
|
|
use std::io;
|
|
use suricata_sys::sys::AppProtoEnum::ALPROTO_HTTP1;
|
|
use suricata_sys::sys::{
|
|
AppLayerParserState, AppProto, HttpRangeContainerBlock, SCAppLayerForceProtocolChange,
|
|
SCAppLayerParserConfParserEnabled, SCAppLayerParserRegisterGetTxSubStateFuncs,
|
|
SCAppLayerParserRegisterLogger, SCAppLayerProtoDetectConfProtoDetectionEnabled,
|
|
SCFileFlowFlagsToFlags, SCHTTP2MimicHttp1Request,
|
|
};
|
|
|
|
static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN;
|
|
static mut ALPROTO_DOH2: AppProto = ALPROTO_UNKNOWN;
|
|
|
|
const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384;
|
|
const HTTP2_MAX_HANDLED_FRAME_SIZE: usize = 65536;
|
|
const HTTP2_MIN_HANDLED_FRAME_SIZE: usize = 256;
|
|
|
|
pub static mut SURICATA_HTTP2_FILE_CONFIG: Option<&'static SuricataFileContext> = None;
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn SCHttp2Init(context: &'static mut SuricataFileContext) {
|
|
unsafe {
|
|
SURICATA_HTTP2_FILE_CONFIG = Some(context);
|
|
}
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq)]
|
|
pub enum HTTP2ConnectionState {
|
|
Http2StateInit = 0,
|
|
Http2StateMagicDone = 1,
|
|
}
|
|
|
|
const HTTP2_FRAME_HEADER_LEN: usize = 9;
|
|
const HTTP2_MAGIC_LEN: usize = 24;
|
|
const HTTP2_FRAME_GOAWAY_LEN: usize = 8;
|
|
const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4;
|
|
const HTTP2_FRAME_PRIORITY_LEN: usize = 5;
|
|
const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4;
|
|
pub static mut HTTP2_MAX_TABLESIZE: u32 = 65536; // 0x10000
|
|
// maximum size of reassembly for header + continuation
|
|
static mut HTTP2_MAX_REASS: usize = 102400;
|
|
static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000
|
|
static mut HTTP2_MAX_FRAMES: usize = 65536;
|
|
pub(super) static mut HTTP2_COMPRESSION_BOMB_LIMIT: u64 = 1_048_576;
|
|
|
|
#[repr(u8)]
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
|
|
pub enum HTTP2TxType {
|
|
HTTP2TxTypeStream = 1,
|
|
HTTP2TxTypeGlobal = 2,
|
|
}
|
|
|
|
#[derive(AppLayerFrameType)]
|
|
pub enum Http2FrameType {
|
|
Hdr,
|
|
Data,
|
|
Pdu,
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
|
|
pub enum HTTP2FrameUnhandledReason {
|
|
UnknownType = 0,
|
|
TooLong = 1,
|
|
ParsingError = 2,
|
|
Incomplete = 3,
|
|
}
|
|
|
|
impl fmt::Display for HTTP2FrameUnhandledReason {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{:?}", self)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HTTP2FrameUnhandled {
|
|
pub reason: HTTP2FrameUnhandledReason,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum HTTP2FrameTypeData {
|
|
PRIORITY(parser::HTTP2FramePriority),
|
|
GOAWAY(parser::HTTP2FrameGoAway),
|
|
RSTSTREAM(parser::HTTP2FrameRstStream),
|
|
SETTINGS(Vec<parser::HTTP2FrameSettings>),
|
|
WINDOWUPDATE(parser::HTTP2FrameWindowUpdate),
|
|
HEADERS(parser::HTTP2FrameHeaders),
|
|
PUSHPROMISE(parser::HTTP2FramePushPromise),
|
|
CONTINUATION(parser::HTTP2FrameContinuation),
|
|
PING,
|
|
DATA,
|
|
//not a defined frame
|
|
UNHANDLED(HTTP2FrameUnhandled),
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
|
|
#[suricata(alstate_strip_prefix = "HTTP2Prog")]
|
|
pub enum HTTP2TxProgress {
|
|
HTTP2ProgStarted = 0,
|
|
HTTP2ProgHeaders = 1,
|
|
HTTP2ProgData = 2,
|
|
HTTP2ProgClosed = 3,
|
|
HTTP2ProgComplete = 4, // complete is a pseudo state set only when both sides are closed
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq, Debug)]
|
|
#[suricata(alstate_strip_prefix = "HTTP2ProgGlobal")]
|
|
pub enum HTTP2TxGlobalProgress {
|
|
HTTP2ProgGlobalStarted = 0,
|
|
HTTP2ProgGlobalComplete = 1,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HTTP2Frame {
|
|
pub header: parser::HTTP2FrameHeader,
|
|
pub data: HTTP2FrameTypeData,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
/// Dns Over HTTP2 Data inside a HTTP2 transaction
|
|
pub struct DohHttp2Tx {
|
|
/// wether the HTTP2 data is DNS, for both directions
|
|
is_doh_data: [bool; 2],
|
|
/// http2 data buffer to parse as DNS on completion
|
|
pub data_buf: [Vec<u8>; 2],
|
|
/// dns request transation
|
|
pub dns_request_tx: Option<DNSTransaction>,
|
|
/// dns response transation
|
|
pub dns_response_tx: Option<DNSTransaction>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HTTP2StreamProgress {
|
|
pub progress_ts: HTTP2TxProgress,
|
|
pub progress_tc: HTTP2TxProgress,
|
|
}
|
|
|
|
impl HTTP2StreamProgress {
|
|
fn init() -> Self {
|
|
Self {
|
|
progress_ts: HTTP2TxProgress::HTTP2ProgStarted,
|
|
progress_tc: HTTP2TxProgress::HTTP2ProgStarted,
|
|
}
|
|
}
|
|
fn complete() -> Self {
|
|
Self {
|
|
progress_ts: HTTP2TxProgress::HTTP2ProgComplete,
|
|
progress_tc: HTTP2TxProgress::HTTP2ProgComplete,
|
|
}
|
|
}
|
|
fn is_complete(&self) -> bool {
|
|
self.progress_ts >= HTTP2TxProgress::HTTP2ProgComplete
|
|
&& self.progress_tc >= HTTP2TxProgress::HTTP2ProgComplete
|
|
}
|
|
fn is_complete_for_direction(&self, direction: u8) -> bool {
|
|
if direction & Direction::ToServer as u8 != 0 {
|
|
self.progress_ts >= HTTP2TxProgress::HTTP2ProgComplete
|
|
} else {
|
|
self.progress_tc >= HTTP2TxProgress::HTTP2ProgComplete
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HTTP2GlobalProgress {
|
|
pub progress_ts: HTTP2TxGlobalProgress,
|
|
pub progress_tc: HTTP2TxGlobalProgress,
|
|
}
|
|
|
|
impl HTTP2GlobalProgress {
|
|
fn _init() -> Self {
|
|
Self {
|
|
progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalStarted,
|
|
progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalStarted,
|
|
}
|
|
}
|
|
fn complete() -> Self {
|
|
Self {
|
|
progress_ts: HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete,
|
|
progress_tc: HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete,
|
|
}
|
|
}
|
|
fn is_complete(&self) -> bool {
|
|
self.progress_ts >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete
|
|
&& self.progress_tc >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete
|
|
}
|
|
fn is_complete_for_direction(&self, direction: u8) -> bool {
|
|
if direction & Direction::ToServer as u8 != 0 {
|
|
self.progress_ts >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete
|
|
} else {
|
|
self.progress_tc >= HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum HTTP2Progress {
|
|
STREAM(HTTP2StreamProgress),
|
|
GLOBAL(HTTP2GlobalProgress),
|
|
}
|
|
|
|
impl HTTP2Progress {
|
|
fn get(&self, direction: u8) -> i32 {
|
|
if let HTTP2Progress::STREAM(ref s) = self {
|
|
if direction == STREAM_TOSERVER {
|
|
return s.progress_ts as i32;
|
|
} else {
|
|
return s.progress_tc as i32;
|
|
}
|
|
} else if let HTTP2Progress::GLOBAL(ref g) = self {
|
|
if direction == STREAM_TOSERVER {
|
|
return g.progress_ts as i32;
|
|
} else {
|
|
return g.progress_tc as i32;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
fn is_complete(&self) -> bool {
|
|
let complete = if let HTTP2Progress::STREAM(ref s) = self {
|
|
s.is_complete()
|
|
} else if let HTTP2Progress::GLOBAL(ref g) = self {
|
|
g.is_complete()
|
|
} else {
|
|
false
|
|
};
|
|
complete
|
|
}
|
|
|
|
pub fn is_complete_for_direction(&self, direction: u8) -> bool {
|
|
let complete = if let HTTP2Progress::STREAM(ref s) = self {
|
|
s.is_complete_for_direction(direction)
|
|
} else if let HTTP2Progress::GLOBAL(ref g) = self {
|
|
g.is_complete_for_direction(direction)
|
|
} else {
|
|
false
|
|
};
|
|
complete
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HTTP2Transaction {
|
|
tx_id: u64,
|
|
pub stream_id: u32,
|
|
pub progress: HTTP2Progress,
|
|
to_drop: bool,
|
|
child_stream_id: u32,
|
|
|
|
pub frames_tc: Vec<HTTP2Frame>,
|
|
pub frames_ts: Vec<HTTP2Frame>,
|
|
|
|
decoder: decompression::HTTP2Decoder,
|
|
pub file_range: *mut HttpRangeContainerBlock,
|
|
|
|
pub tx_data: AppLayerTxData,
|
|
pub ft_tc: FileTransferTracker,
|
|
pub ft_ts: FileTransferTracker,
|
|
|
|
pub req_line: Vec<u8>,
|
|
pub resp_line: Vec<u8>,
|
|
|
|
pub doh: Option<DohHttp2Tx>,
|
|
}
|
|
|
|
impl Transaction for HTTP2Transaction {
|
|
fn id(&self) -> u64 {
|
|
self.tx_id
|
|
}
|
|
}
|
|
|
|
impl Default for HTTP2Transaction {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl HTTP2Transaction {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
tx_id: 0,
|
|
stream_id: 0,
|
|
child_stream_id: 0,
|
|
progress: HTTP2Progress::STREAM(HTTP2StreamProgress::init()),
|
|
to_drop: false,
|
|
frames_tc: Vec::new(),
|
|
frames_ts: Vec::new(),
|
|
decoder: decompression::HTTP2Decoder::new(),
|
|
file_range: std::ptr::null_mut(),
|
|
tx_data: AppLayerTxData::new(),
|
|
ft_tc: FileTransferTracker::new(),
|
|
ft_ts: FileTransferTracker::new(),
|
|
req_line: Vec::new(),
|
|
resp_line: Vec::new(),
|
|
doh: None,
|
|
}
|
|
}
|
|
|
|
pub fn free(&mut self) {
|
|
SCLogDebug!("free: tx_id {} stream_id {}", self.tx_id, self.stream_id);
|
|
if !self.file_range.is_null() {
|
|
if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } {
|
|
//TODO get a file container instead of NULL
|
|
unsafe {
|
|
SCHTPFileCloseHandleRange(
|
|
sfcm.files_sbcfg,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
self.file_range,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
);
|
|
SCHttpRangeFreeBlock(self.file_range);
|
|
}
|
|
self.file_range = std::ptr::null_mut();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_event(&mut self, event: HTTP2Event) {
|
|
self.tx_data.set_event(event as u8);
|
|
}
|
|
|
|
fn handle_headers(
|
|
&mut self, blocks: &[parser::HTTP2FrameHeaderBlock], dir: Direction,
|
|
) -> Option<Vec<u8>> {
|
|
let mut authority = None;
|
|
let mut path = None;
|
|
let mut doh = false;
|
|
let mut host = None;
|
|
for block in blocks {
|
|
if block.name.as_ref() == b"content-encoding" {
|
|
self.decoder.http2_encoding_fromvec(&block.value, dir);
|
|
} else if block.name.as_ref() == b"accept" {
|
|
//TODO? faster pattern matching
|
|
if block.value.as_ref() == b"application/dns-message" {
|
|
doh = true;
|
|
}
|
|
} else if block.name.as_ref() == b"content-type" {
|
|
if block.value.as_ref() == b"application/dns-message" {
|
|
if let Some(doh) = &mut self.doh {
|
|
doh.is_doh_data[dir.index()] = true;
|
|
} else {
|
|
let mut doh = DohHttp2Tx::default();
|
|
doh.is_doh_data[dir.index()] = true;
|
|
self.doh = Some(doh);
|
|
}
|
|
}
|
|
} else if block.name.as_ref() == b":path" {
|
|
path = Some(&block.value);
|
|
} else if block.name.eq_ignore_ascii_case(b":authority") {
|
|
authority = Some(&block.value);
|
|
if block.value.contains(&b'@') {
|
|
// it is forbidden by RFC 9113 to have userinfo in this field
|
|
// when in HTTP1 we can have user:password@domain.com
|
|
self.set_event(HTTP2Event::UserinfoInUri);
|
|
}
|
|
} else if block.name.eq_ignore_ascii_case(b"host") {
|
|
host = Some(&block.value);
|
|
}
|
|
}
|
|
if let Some(a) = authority {
|
|
if let Some(h) = host {
|
|
if !a.eq_ignore_ascii_case(h) {
|
|
// The event is triggered only if both headers
|
|
// are in the same frame to avoid excessive
|
|
// complexity at runtime.
|
|
self.set_event(HTTP2Event::AuthorityHostMismatch);
|
|
}
|
|
}
|
|
}
|
|
if doh && unsafe { ALPROTO_DOH2 } != ALPROTO_UNKNOWN {
|
|
if let Some(p) = path {
|
|
if let Ok((_, dns_req)) = parser::doh_extract_request(p) {
|
|
return Some(dns_req);
|
|
}
|
|
}
|
|
}
|
|
return None;
|
|
}
|
|
|
|
pub fn update_file_flags(&mut self, flow_file_flags: u16) {
|
|
self.ft_ts.file_flags = unsafe { SCFileFlowFlagsToFlags(flow_file_flags, STREAM_TOSERVER) };
|
|
self.ft_tc.file_flags = unsafe { SCFileFlowFlagsToFlags(flow_file_flags, STREAM_TOCLIENT) };
|
|
}
|
|
|
|
fn decompress<'a>(
|
|
&'a mut self, input: &'a [u8], output: &'a mut Vec<u8>, dir: Direction,
|
|
sfcm: &'static SuricataFileContext, over: bool, flow: *const Flow,
|
|
) -> io::Result<()> {
|
|
let decompressed = self.decoder.decompress(input, output, dir)?;
|
|
let xid: u32 = self.tx_id as u32;
|
|
if dir == Direction::ToClient {
|
|
self.ft_tc.tx_id = self.tx_id - 1;
|
|
// Check that we are at the beginning of the file
|
|
if !self.ft_tc.is_initialized() {
|
|
// 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,
|
|
Direction::ToClient,
|
|
"content-range",
|
|
) {
|
|
match range::http2_parse_check_content_range(&value) {
|
|
Ok((_, v)) => {
|
|
range::http2_range_open(
|
|
self,
|
|
&v,
|
|
flow,
|
|
sfcm,
|
|
Direction::ToClient,
|
|
decompressed,
|
|
);
|
|
if over && !self.file_range.is_null() {
|
|
range::http2_range_close(self, Direction::ToClient, &[])
|
|
}
|
|
}
|
|
_ => {
|
|
self.set_event(HTTP2Event::InvalidRange);
|
|
}
|
|
}
|
|
}
|
|
} else if !self.file_range.is_null() {
|
|
if over {
|
|
range::http2_range_close(self, Direction::ToClient, decompressed)
|
|
} else {
|
|
range::http2_range_append(sfcm, self.file_range, decompressed)
|
|
}
|
|
}
|
|
self.ft_tc.new_chunk(
|
|
sfcm,
|
|
b"",
|
|
decompressed,
|
|
self.ft_tc.tracked, //offset = append
|
|
decompressed.len() as u32,
|
|
0,
|
|
over,
|
|
&xid,
|
|
);
|
|
} else {
|
|
self.ft_ts.tx_id = self.tx_id - 1;
|
|
if !self.ft_ts.file_open {
|
|
self.tx_data.incr_files_opened();
|
|
}
|
|
self.ft_ts.new_chunk(
|
|
sfcm,
|
|
b"",
|
|
decompressed,
|
|
self.ft_ts.tracked, //offset = append
|
|
decompressed.len() as u32,
|
|
0,
|
|
over,
|
|
&xid,
|
|
);
|
|
};
|
|
if unsafe { ALPROTO_DOH2 } != ALPROTO_UNKNOWN {
|
|
// we store DNS response, and process it when complete
|
|
if let Some(doh) = &mut self.doh {
|
|
if doh.is_doh_data[dir.index()] {
|
|
if doh.data_buf[dir.index()].len() + decompressed.len() <= 0xFFFF {
|
|
// a DNS message is U16_MAX
|
|
doh.data_buf[dir.index()].extend_from_slice(decompressed);
|
|
} else {
|
|
// stop processing further data
|
|
doh.is_doh_data[dir.index()] = false;
|
|
if dir == Direction::ToClient {
|
|
self.set_event(HTTP2Event::DnsResponseTooLong);
|
|
} else {
|
|
self.set_event(HTTP2Event::DnsRequestTooLong);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
fn handle_frame(
|
|
&mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction,
|
|
) -> Option<Vec<u8>> {
|
|
//handle child_stream_id changes
|
|
let mut r = None;
|
|
match data {
|
|
HTTP2FrameTypeData::PUSHPROMISE(hs) => {
|
|
if dir == Direction::ToClient {
|
|
//we could set an event if self.child_stream_id != 0
|
|
if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 {
|
|
self.child_stream_id = hs.stream_id;
|
|
}
|
|
if let HTTP2Progress::STREAM(ref mut stream_tx) = self.progress {
|
|
if stream_tx.progress_tc < HTTP2TxProgress::HTTP2ProgHeaders {
|
|
stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgHeaders;
|
|
}
|
|
}
|
|
}
|
|
r = self.handle_headers(&hs.blocks, dir);
|
|
}
|
|
HTTP2FrameTypeData::CONTINUATION(hs) => {
|
|
if dir == Direction::ToClient
|
|
&& header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0
|
|
{
|
|
self.child_stream_id = 0;
|
|
}
|
|
r = self.handle_headers(&hs.blocks, dir);
|
|
}
|
|
HTTP2FrameTypeData::HEADERS(hs) => {
|
|
if dir == Direction::ToClient {
|
|
self.child_stream_id = 0;
|
|
}
|
|
r = self.handle_headers(&hs.blocks, dir);
|
|
}
|
|
HTTP2FrameTypeData::RSTSTREAM(_) => {
|
|
self.child_stream_id = 0;
|
|
}
|
|
_ => {}
|
|
}
|
|
if let HTTP2Progress::STREAM(ref mut stream_tx) = self.progress {
|
|
//handle closing state changes
|
|
let state = if dir == Direction::ToServer {
|
|
&mut stream_tx.progress_ts
|
|
} else {
|
|
&mut stream_tx.progress_tc
|
|
};
|
|
match data {
|
|
HTTP2FrameTypeData::CONTINUATION(c) => {
|
|
if c.end_stream && *state < HTTP2TxProgress::HTTP2ProgClosed {
|
|
*state = HTTP2TxProgress::HTTP2ProgClosed;
|
|
if stream_tx.progress_ts == HTTP2TxProgress::HTTP2ProgClosed
|
|
&& stream_tx.progress_tc == HTTP2TxProgress::HTTP2ProgClosed
|
|
{
|
|
stream_tx.progress_ts = HTTP2TxProgress::HTTP2ProgComplete;
|
|
stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgComplete;
|
|
}
|
|
}
|
|
}
|
|
HTTP2FrameTypeData::HEADERS(_) => {
|
|
if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0
|
|
&& header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0
|
|
{
|
|
if *state < HTTP2TxProgress::HTTP2ProgClosed {
|
|
*state = HTTP2TxProgress::HTTP2ProgClosed;
|
|
if stream_tx.progress_ts == HTTP2TxProgress::HTTP2ProgClosed
|
|
&& stream_tx.progress_tc == HTTP2TxProgress::HTTP2ProgClosed
|
|
{
|
|
stream_tx.progress_ts = HTTP2TxProgress::HTTP2ProgComplete;
|
|
stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgComplete;
|
|
}
|
|
}
|
|
} else if *state < HTTP2TxProgress::HTTP2ProgHeaders {
|
|
*state = HTTP2TxProgress::HTTP2ProgHeaders;
|
|
}
|
|
}
|
|
HTTP2FrameTypeData::DATA => {
|
|
if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 {
|
|
if *state < HTTP2TxProgress::HTTP2ProgClosed {
|
|
*state = HTTP2TxProgress::HTTP2ProgClosed;
|
|
if stream_tx.progress_ts == HTTP2TxProgress::HTTP2ProgClosed
|
|
&& stream_tx.progress_tc == HTTP2TxProgress::HTTP2ProgClosed
|
|
{
|
|
stream_tx.progress_ts = HTTP2TxProgress::HTTP2ProgComplete;
|
|
stream_tx.progress_tc = HTTP2TxProgress::HTTP2ProgComplete;
|
|
}
|
|
}
|
|
} else if *state < HTTP2TxProgress::HTTP2ProgData {
|
|
//not end of stream
|
|
*state = HTTP2TxProgress::HTTP2ProgData;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
fn handle_dns_data(&mut self, dir: Direction, flow: *mut Flow) {
|
|
if let Some(doh) = &mut self.doh {
|
|
if !doh.data_buf[dir.index()].is_empty() {
|
|
if dir.is_to_client() {
|
|
if let Ok(mut dtx) = dns_parse_response(&doh.data_buf[dir.index()]) {
|
|
dtx.id = 1;
|
|
doh.dns_response_tx = Some(dtx);
|
|
unsafe {
|
|
SCAppLayerForceProtocolChange(flow, ALPROTO_DOH2);
|
|
}
|
|
}
|
|
} else if let Ok(mut dtx) =
|
|
dns_parse_request(&doh.data_buf[dir.index()], &DnsVariant::Dns)
|
|
{
|
|
dtx.id = 1;
|
|
doh.dns_request_tx = Some(dtx);
|
|
unsafe {
|
|
SCAppLayerForceProtocolChange(flow, ALPROTO_DOH2);
|
|
}
|
|
}
|
|
doh.data_buf[dir.index()].clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_data_frame(
|
|
&mut self, rem: &[u8], hlsafe: usize, dir: Direction, flow: *mut Flow, padded: bool,
|
|
over: bool,
|
|
) {
|
|
match unsafe { SURICATA_HTTP2_FILE_CONFIG } {
|
|
Some(sfcm) => {
|
|
if dir == Direction::ToServer {
|
|
self.ft_tc.tx_id = self.tx_id - 1;
|
|
} else {
|
|
self.ft_ts.tx_id = self.tx_id - 1;
|
|
};
|
|
let mut dinput = &rem[..hlsafe];
|
|
if padded && !rem.is_empty() && usize::from(rem[0]) < hlsafe {
|
|
dinput = &rem[1..hlsafe - usize::from(rem[0])];
|
|
}
|
|
let mut output = Vec::with_capacity(decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE);
|
|
match self.decompress(dinput, &mut output, dir, sfcm, over, flow) {
|
|
Ok(_) => {
|
|
if over {
|
|
self.handle_dns_data(dir, flow);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
if e.kind() == io::ErrorKind::OutOfMemory {
|
|
self.set_event(HTTP2Event::CompressionBomb);
|
|
} else {
|
|
self.set_event(HTTP2Event::FailedDecompression);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => panic!("no SURICATA_HTTP2_FILE_CONFIG"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for HTTP2Transaction {
|
|
fn drop(&mut self) {
|
|
if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } {
|
|
self.ft_ts.file.free(sfcm);
|
|
self.ft_tc.file.free(sfcm);
|
|
}
|
|
self.free();
|
|
}
|
|
}
|
|
|
|
#[derive(AppLayerEvent)]
|
|
pub enum HTTP2Event {
|
|
InvalidFrameHeader,
|
|
InvalidClientMagic,
|
|
InvalidFrameData,
|
|
InvalidHeader,
|
|
InvalidFrameLength,
|
|
ExtraHeaderData,
|
|
LongFrameData,
|
|
StreamIdReuse,
|
|
InvalidHttp1Settings,
|
|
FailedDecompression,
|
|
InvalidRange,
|
|
HeaderIntegerOverflow,
|
|
HeaderTableOverflow,
|
|
TooManyStreams,
|
|
AuthorityHostMismatch,
|
|
UserinfoInUri,
|
|
ReassemblyLimitReached,
|
|
DnsRequestTooLong,
|
|
DnsResponseTooLong,
|
|
DataStreamZero,
|
|
TooManyFrames,
|
|
CompressionBomb,
|
|
ContinuationWithoutHeaders,
|
|
}
|
|
|
|
pub struct HTTP2DynTable {
|
|
pub table: Vec<parser::HTTP2FrameHeaderBlock>,
|
|
pub current_size: usize,
|
|
pub max_size: usize,
|
|
pub overflow: u8,
|
|
}
|
|
|
|
impl Default for HTTP2DynTable {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl HTTP2DynTable {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
table: Vec::with_capacity(64),
|
|
current_size: 0,
|
|
max_size: 4096, //default value
|
|
overflow: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct HTTP2HeaderReassemblyBuffer {
|
|
data: Vec<u8>,
|
|
stream_id: u32,
|
|
end_stream: bool,
|
|
}
|
|
|
|
pub struct HTTP2State {
|
|
state_data: AppLayerStateData,
|
|
tx_id: u64,
|
|
request_frame_size: u32,
|
|
response_frame_size: u32,
|
|
dynamic_headers_ts: HTTP2DynTable,
|
|
dynamic_headers_tc: HTTP2DynTable,
|
|
transactions: VecDeque<HTTP2Transaction>,
|
|
progress: HTTP2ConnectionState,
|
|
|
|
comp_len: u64,
|
|
decomp_len: u64,
|
|
|
|
c2s_buf: HTTP2HeaderReassemblyBuffer,
|
|
s2c_buf: HTTP2HeaderReassemblyBuffer,
|
|
}
|
|
|
|
impl State<HTTP2Transaction> for HTTP2State {
|
|
fn get_transaction_count(&self) -> usize {
|
|
self.transactions.len()
|
|
}
|
|
|
|
fn get_transaction_by_index(&self, index: usize) -> Option<&HTTP2Transaction> {
|
|
self.transactions.get(index)
|
|
}
|
|
}
|
|
|
|
impl Default for HTTP2State {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl HTTP2State {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
state_data: AppLayerStateData::default(),
|
|
tx_id: 0,
|
|
request_frame_size: 0,
|
|
response_frame_size: 0,
|
|
// the headers are encoded on one byte
|
|
// with a fixed number of static headers, and
|
|
// a variable number of dynamic headers
|
|
dynamic_headers_ts: HTTP2DynTable::new(),
|
|
dynamic_headers_tc: HTTP2DynTable::new(),
|
|
transactions: VecDeque::new(),
|
|
progress: HTTP2ConnectionState::Http2StateInit,
|
|
comp_len: 0,
|
|
decomp_len: 0,
|
|
c2s_buf: HTTP2HeaderReassemblyBuffer::default(),
|
|
s2c_buf: HTTP2HeaderReassemblyBuffer::default(),
|
|
}
|
|
}
|
|
|
|
pub fn free(&mut self) {
|
|
// this should be in HTTP2Transaction::free
|
|
// but we need state's file container cf https://redmine.openinfosecfoundation.org/issues/4444
|
|
for tx in &mut self.transactions {
|
|
if !tx.file_range.is_null() {
|
|
if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } {
|
|
unsafe {
|
|
SCHTPFileCloseHandleRange(
|
|
sfcm.files_sbcfg,
|
|
&mut tx.ft_tc.file,
|
|
0,
|
|
tx.file_range,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
);
|
|
SCHttpRangeFreeBlock(tx.file_range);
|
|
}
|
|
tx.file_range = std::ptr::null_mut();
|
|
}
|
|
}
|
|
}
|
|
self.transactions.clear();
|
|
}
|
|
|
|
pub fn set_event(&mut self, event: HTTP2Event) {
|
|
let len = self.transactions.len();
|
|
if len == 0 {
|
|
return;
|
|
}
|
|
let tx = &mut self.transactions[len - 1];
|
|
tx.tx_data.set_event(event as u8);
|
|
}
|
|
|
|
// 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 = &mut self.transactions[i];
|
|
if tx.tx_id == tx_id + 1 {
|
|
found = true;
|
|
index = i;
|
|
// this should be in HTTP2Transaction::free
|
|
// but we need state's file container cf https://redmine.openinfosecfoundation.org/issues/4444
|
|
if !tx.file_range.is_null() {
|
|
if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } {
|
|
unsafe {
|
|
SCHTPFileCloseHandleRange(
|
|
sfcm.files_sbcfg,
|
|
&mut tx.ft_tc.file,
|
|
0,
|
|
tx.file_range,
|
|
std::ptr::null_mut(),
|
|
0,
|
|
);
|
|
SCHttpRangeFreeBlock(tx.file_range);
|
|
}
|
|
tx.file_range = std::ptr::null_mut();
|
|
}
|
|
}
|
|
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 {
|
|
tx.tx_data.update_file_flags(self.state_data.file_flags);
|
|
tx.update_file_flags(tx.tx_data.0.file_flags);
|
|
return Some(tx);
|
|
}
|
|
}
|
|
return None;
|
|
}
|
|
|
|
fn find_tx_index(&mut self, sid: u32) -> usize {
|
|
for i in 0..self.transactions.len() {
|
|
//reverse order should be faster
|
|
let idx = self.transactions.len() - 1 - i;
|
|
if sid == self.transactions[idx].stream_id {
|
|
return idx + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
fn find_child_stream_id(&mut self, sid: u32) -> u32 {
|
|
for i in 0..self.transactions.len() {
|
|
//reverse order should be faster
|
|
if sid == self.transactions[self.transactions.len() - 1 - i].stream_id {
|
|
if self.transactions[self.transactions.len() - 1 - i].child_stream_id > 0 {
|
|
return self.transactions[self.transactions.len() - 1 - i].child_stream_id;
|
|
}
|
|
return sid;
|
|
}
|
|
}
|
|
return sid;
|
|
}
|
|
|
|
fn create_global_tx(&mut self, dir: Direction) -> &mut HTTP2Transaction {
|
|
//special transaction with only one frame
|
|
//as it affects the global connection, there is no end to it
|
|
let mut tx = HTTP2Transaction::new();
|
|
tx.tx_data = AppLayerTxData::for_direction(dir);
|
|
tx.tx_data.0.tx_type = HTTP2TxType::HTTP2TxTypeGlobal as u8;
|
|
self.tx_id += 1;
|
|
tx.tx_id = self.tx_id;
|
|
tx.progress = HTTP2Progress::GLOBAL(HTTP2GlobalProgress::complete());
|
|
SCLogDebug!("global tx created {:?}", tx);
|
|
// a global tx (stream id 0) does not hold files cf RFC 9113 section 5.1.1
|
|
self.transactions.push_back(tx);
|
|
return self.transactions.back_mut().unwrap();
|
|
}
|
|
|
|
pub fn find_or_create_tx(
|
|
&mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction,
|
|
) -> Option<&mut HTTP2Transaction> {
|
|
if header.stream_id == 0 {
|
|
if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } {
|
|
for tx_old in &mut self.transactions {
|
|
if tx_old.to_drop {
|
|
// loop was already run
|
|
break;
|
|
}
|
|
tx_old.set_event(HTTP2Event::TooManyStreams);
|
|
// use a distinct state, even if we do not log it
|
|
tx_old.progress = HTTP2Progress::STREAM(HTTP2StreamProgress::complete());
|
|
tx_old.to_drop = true;
|
|
tx_old.tx_data.0.updated_tc = true;
|
|
tx_old.tx_data.0.updated_ts = true;
|
|
}
|
|
return None;
|
|
}
|
|
return Some(self.create_global_tx(dir));
|
|
}
|
|
let sid = match data {
|
|
//yes, the right stream_id for Suricata is not the header one
|
|
HTTP2FrameTypeData::PUSHPROMISE(hs) => hs.stream_id,
|
|
HTTP2FrameTypeData::CONTINUATION(_) => {
|
|
if dir == Direction::ToClient {
|
|
//continuation of a push promise
|
|
self.find_child_stream_id(header.stream_id)
|
|
} else {
|
|
header.stream_id
|
|
}
|
|
}
|
|
_ => header.stream_id,
|
|
};
|
|
let index = self.find_tx_index(sid);
|
|
if index > 0 {
|
|
if self.transactions[index - 1].progress.is_complete() {
|
|
//these frames can be received in this state for a short period
|
|
if header.ftype != parser::HTTP2FrameType::RstStream as u8
|
|
&& header.ftype != parser::HTTP2FrameType::WindowUpdate as u8
|
|
&& header.ftype != parser::HTTP2FrameType::Priority as u8
|
|
{
|
|
self.set_event(HTTP2Event::StreamIdReuse);
|
|
}
|
|
}
|
|
|
|
let tx = &mut self.transactions[index - 1];
|
|
tx.tx_data.update_file_flags(self.state_data.file_flags);
|
|
tx.update_file_flags(tx.tx_data.0.file_flags);
|
|
tx.tx_data.0.updated_tc = true;
|
|
tx.tx_data.0.updated_ts = true;
|
|
return Some(tx);
|
|
} else {
|
|
// do not use SETTINGS_MAX_CONCURRENT_STREAMS as it can grow too much
|
|
if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } {
|
|
for tx_old in &mut self.transactions {
|
|
if tx_old.to_drop {
|
|
// loop was already run
|
|
break;
|
|
}
|
|
tx_old.set_event(HTTP2Event::TooManyStreams);
|
|
// use a distinct state, even if we do not log it
|
|
tx_old.progress = HTTP2Progress::STREAM(HTTP2StreamProgress::complete());
|
|
tx_old.to_drop = true;
|
|
tx_old.tx_data.0.updated_tc = true;
|
|
tx_old.tx_data.0.updated_ts = true;
|
|
}
|
|
return None;
|
|
}
|
|
let mut tx = HTTP2Transaction::new();
|
|
self.tx_id += 1;
|
|
tx.tx_id = self.tx_id;
|
|
tx.stream_id = sid;
|
|
tx.tx_data.update_file_flags(self.state_data.file_flags);
|
|
tx.update_file_flags(tx.tx_data.0.file_flags);
|
|
tx.tx_data.0.file_tx = STREAM_TOSERVER | STREAM_TOCLIENT; // might hold files in both directions
|
|
tx.tx_data.0.tx_type = HTTP2TxType::HTTP2TxTypeStream as u8;
|
|
self.transactions.push_back(tx);
|
|
return Some(self.transactions.back_mut().unwrap());
|
|
}
|
|
}
|
|
|
|
fn process_headers(
|
|
&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: Direction,
|
|
) -> Vec<HTTP2Event> {
|
|
let mut events = Vec::new();
|
|
for block in blocks {
|
|
if block.error >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError {
|
|
events.push(HTTP2Event::InvalidHeader);
|
|
} else if block.error
|
|
== parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow
|
|
{
|
|
events.push(HTTP2Event::HeaderIntegerOverflow);
|
|
}
|
|
}
|
|
let dyn_headers = if dir == Direction::ToClient {
|
|
&mut self.dynamic_headers_tc
|
|
} else {
|
|
&mut self.dynamic_headers_ts
|
|
};
|
|
if dyn_headers.overflow == 2 {
|
|
// event only once
|
|
dyn_headers.overflow = 3;
|
|
events.push(HTTP2Event::HeaderTableOverflow);
|
|
}
|
|
return events;
|
|
}
|
|
|
|
fn parse_frame_data(
|
|
&mut self, head: &parser::HTTP2FrameHeader, input: &[u8], complete: bool, dir: Direction,
|
|
reass_limit_reached: &mut bool,
|
|
) -> (HTTP2FrameTypeData, Vec<HTTP2Event>) {
|
|
let ftype = head.ftype;
|
|
let hflags = head.flags;
|
|
let mut events = Vec::new();
|
|
match num::FromPrimitive::from_u8(ftype) {
|
|
Some(parser::HTTP2FrameType::GoAway) => {
|
|
if input.len() < HTTP2_FRAME_GOAWAY_LEN {
|
|
events.push(HTTP2Event::InvalidFrameLength);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::Incomplete,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
match parser::http2_parse_frame_goaway(input) {
|
|
Ok((_, goaway)) => {
|
|
return (HTTP2FrameTypeData::GOAWAY(goaway), events);
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::Settings) => {
|
|
match parser::http2_parse_frame_settings(input) {
|
|
Ok((_, set)) => {
|
|
for e in &set {
|
|
if e.id == parser::HTTP2SettingsId::HeaderTableSize {
|
|
//reverse order as this is what we accept from the other endpoint
|
|
let dyn_headers = if dir == Direction::ToClient {
|
|
&mut self.dynamic_headers_ts
|
|
} else {
|
|
&mut self.dynamic_headers_tc
|
|
};
|
|
dyn_headers.max_size = e.value as usize;
|
|
if e.value > unsafe { HTTP2_MAX_TABLESIZE } {
|
|
//mark potential overflow
|
|
dyn_headers.overflow = 1;
|
|
} else {
|
|
//reset in case peer set a lower value, to be tested
|
|
dyn_headers.overflow = 0;
|
|
}
|
|
}
|
|
}
|
|
//we could set an event on remaining data
|
|
return (HTTP2FrameTypeData::SETTINGS(set), events);
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
if complete {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::TooLong,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::RstStream) => {
|
|
if input.len() != HTTP2_FRAME_RSTSTREAM_LEN {
|
|
events.push(HTTP2Event::InvalidFrameLength);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::Incomplete,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
match parser::http2_parse_frame_rststream(input) {
|
|
Ok((_, rst)) => {
|
|
return (HTTP2FrameTypeData::RSTSTREAM(rst), events);
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::Priority) => {
|
|
if input.len() != HTTP2_FRAME_PRIORITY_LEN {
|
|
events.push(HTTP2Event::InvalidFrameLength);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::Incomplete,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
match parser::http2_parse_frame_priority(input) {
|
|
Ok((_, priority)) => {
|
|
return (HTTP2FrameTypeData::PRIORITY(priority), events);
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::WindowUpdate) => {
|
|
if input.len() != HTTP2_FRAME_WINDOWUPDATE_LEN {
|
|
events.push(HTTP2Event::InvalidFrameLength);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::Incomplete,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
match parser::http2_parse_frame_windowupdate(input) {
|
|
Ok((_, wu)) => {
|
|
return (HTTP2FrameTypeData::WINDOWUPDATE(wu), events);
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::PushPromise) => {
|
|
let dyn_headers = if dir == Direction::ToClient {
|
|
&mut self.dynamic_headers_tc
|
|
} else {
|
|
&mut self.dynamic_headers_ts
|
|
};
|
|
match parser::http2_parse_frame_push_promise(input, hflags, dyn_headers) {
|
|
Ok((_, hs)) => {
|
|
events.extend(self.process_headers(&hs.blocks, dir));
|
|
return (HTTP2FrameTypeData::PUSHPROMISE(hs), events);
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
if complete {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::TooLong,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::Data) => {
|
|
return (HTTP2FrameTypeData::DATA, events);
|
|
}
|
|
Some(parser::HTTP2FrameType::Continuation) => {
|
|
let buf = if dir == Direction::ToClient {
|
|
&mut self.s2c_buf
|
|
} else {
|
|
&mut self.c2s_buf
|
|
};
|
|
if head.stream_id == buf.stream_id {
|
|
let max_reass = unsafe { HTTP2_MAX_REASS };
|
|
if buf.data.len() + input.len() < max_reass {
|
|
buf.data.extend(input);
|
|
} else if buf.data.len() < max_reass {
|
|
buf.data.extend(&input[..max_reass - buf.data.len()]);
|
|
*reass_limit_reached = true;
|
|
}
|
|
if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 {
|
|
let hs = parser::HTTP2FrameContinuation {
|
|
blocks: Vec::new(),
|
|
end_stream: false,
|
|
};
|
|
return (HTTP2FrameTypeData::CONTINUATION(hs), events);
|
|
}
|
|
} else {
|
|
events.push(HTTP2Event::ContinuationWithoutHeaders);
|
|
}
|
|
// else try to parse anyways
|
|
let input_reass = if head.stream_id == buf.stream_id {
|
|
&buf.data
|
|
} else {
|
|
input
|
|
};
|
|
|
|
let dyn_headers = if dir == Direction::ToClient {
|
|
&mut self.dynamic_headers_tc
|
|
} else {
|
|
&mut self.dynamic_headers_ts
|
|
};
|
|
match parser::http2_parse_frame_continuation(input_reass, dyn_headers) {
|
|
Ok((_, mut hs)) => {
|
|
let same = head.stream_id == buf.stream_id;
|
|
if same {
|
|
buf.stream_id = 0;
|
|
buf.data.clear();
|
|
}
|
|
hs.end_stream = buf.end_stream;
|
|
if same {
|
|
buf.end_stream = false;
|
|
}
|
|
events.extend(self.process_headers(&hs.blocks, dir));
|
|
return (HTTP2FrameTypeData::CONTINUATION(hs), events);
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
if head.stream_id == buf.stream_id {
|
|
buf.stream_id = 0;
|
|
buf.data.clear();
|
|
buf.end_stream = false;
|
|
}
|
|
if complete {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::TooLong,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
if head.stream_id == buf.stream_id {
|
|
buf.stream_id = 0;
|
|
buf.data.clear();
|
|
buf.end_stream = false;
|
|
}
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::Headers) => {
|
|
if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 {
|
|
let buf = if dir == Direction::ToClient {
|
|
&mut self.s2c_buf
|
|
} else {
|
|
&mut self.c2s_buf
|
|
};
|
|
buf.data.clear();
|
|
buf.end_stream = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0;
|
|
if let Ok((rem_hpack, hs)) = parser::get_frame_headers_hpack(input, hflags) {
|
|
// store only the HPACK fragment: skip Pad Length, Priority, and trailing padding
|
|
buf.data.extend(rem_hpack);
|
|
buf.stream_id = head.stream_id;
|
|
return (HTTP2FrameTypeData::HEADERS(hs), events);
|
|
} else {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
let dyn_headers = if dir == Direction::ToClient {
|
|
&mut self.dynamic_headers_tc
|
|
} else {
|
|
&mut self.dynamic_headers_ts
|
|
};
|
|
match parser::http2_parse_frame_headers(input, hflags, dyn_headers) {
|
|
Ok((hrem, hs)) => {
|
|
events.extend(self.process_headers(&hs.blocks, dir));
|
|
if !hrem.is_empty() {
|
|
SCLogDebug!("Remaining data for HTTP2 headers");
|
|
events.push(HTTP2Event::ExtraHeaderData);
|
|
}
|
|
return (HTTP2FrameTypeData::HEADERS(hs), events);
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
if complete {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
} else {
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::TooLong,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
events.push(HTTP2Event::InvalidFrameData);
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::ParsingError,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Some(parser::HTTP2FrameType::Ping) => {
|
|
return (HTTP2FrameTypeData::PING, events);
|
|
}
|
|
_ => {
|
|
return (
|
|
HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
|
|
reason: HTTP2FrameUnhandledReason::UnknownType,
|
|
}),
|
|
events,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_frames(
|
|
&mut self, mut input: &[u8], il: usize, dir: Direction, flow: *mut Flow,
|
|
stream_slice: &StreamSlice,
|
|
) -> AppLayerResult {
|
|
while !input.is_empty() {
|
|
match parser::http2_parse_frame_header(input) {
|
|
Ok((rem, head)) => {
|
|
let hl = head.length as usize;
|
|
|
|
//we check for completeness first
|
|
if rem.len() < hl {
|
|
//but limit ourselves so as not to exhaust memory
|
|
if hl < HTTP2_MAX_HANDLED_FRAME_SIZE {
|
|
return AppLayerResult::incomplete(
|
|
(il - input.len()) as u32,
|
|
(HTTP2_FRAME_HEADER_LEN + hl) as u32,
|
|
);
|
|
} else if rem.len() < HTTP2_MIN_HANDLED_FRAME_SIZE {
|
|
return AppLayerResult::incomplete(
|
|
(il - input.len()) as u32,
|
|
(HTTP2_FRAME_HEADER_LEN + HTTP2_MIN_HANDLED_FRAME_SIZE) as u32,
|
|
);
|
|
} else {
|
|
self.set_event(HTTP2Event::LongFrameData);
|
|
if dir == Direction::ToServer {
|
|
self.request_frame_size = head.length - (rem.len() as u32);
|
|
} else {
|
|
self.response_frame_size = head.length - (rem.len() as u32);
|
|
}
|
|
}
|
|
}
|
|
|
|
//get a safe length for the buffer
|
|
let (hlsafe, complete) = if rem.len() < hl {
|
|
(rem.len(), false)
|
|
} else {
|
|
(hl, true)
|
|
};
|
|
|
|
let frame_hdr = Frame::new(
|
|
flow,
|
|
stream_slice,
|
|
input,
|
|
HTTP2_FRAME_HEADER_LEN as i64,
|
|
Http2FrameType::Hdr as u8,
|
|
None,
|
|
);
|
|
let frame_data = Frame::new(
|
|
flow,
|
|
stream_slice,
|
|
&input[HTTP2_FRAME_HEADER_LEN..],
|
|
head.length as i64,
|
|
Http2FrameType::Data as u8,
|
|
None,
|
|
);
|
|
let frame_pdu = Frame::new(
|
|
flow,
|
|
stream_slice,
|
|
input,
|
|
HTTP2_FRAME_HEADER_LEN as i64 + head.length as i64,
|
|
Http2FrameType::Pdu as u8,
|
|
None,
|
|
);
|
|
if head.length == 0 && head.ftype == parser::HTTP2FrameType::Settings as u8 {
|
|
input = &rem[hlsafe..];
|
|
continue;
|
|
}
|
|
let mut reass_limit_reached = false;
|
|
let (txdata, events) = self.parse_frame_data(
|
|
&head,
|
|
&rem[..hlsafe],
|
|
complete,
|
|
dir,
|
|
&mut reass_limit_reached,
|
|
);
|
|
|
|
let (comp_len, decomp_len) = (self.comp_len, self.decomp_len);
|
|
let tx = self.find_or_create_tx(&head, &txdata, dir);
|
|
if tx.is_none() {
|
|
return AppLayerResult::err();
|
|
}
|
|
let tx = tx.unwrap();
|
|
for event in events {
|
|
tx.set_event(event);
|
|
}
|
|
SCLogDebug!(
|
|
"tx stream_id {} tx id {} progress {:?}",
|
|
tx.stream_id,
|
|
tx.tx_id,
|
|
tx.progress
|
|
);
|
|
if let Some(frame) = frame_hdr {
|
|
frame.set_tx(flow, tx.tx_id);
|
|
}
|
|
if let Some(frame) = frame_data {
|
|
frame.set_tx(flow, tx.tx_id);
|
|
}
|
|
if let Some(frame) = frame_pdu {
|
|
frame.set_tx(flow, tx.tx_id);
|
|
}
|
|
// Validate: per RFC 9113 only SETTINGS, WINDOW_UPDATE, PING, and GOAWAY are allowed on stream 0
|
|
if head.stream_id == 0
|
|
&& head.ftype != parser::HTTP2FrameType::Settings as u8
|
|
&& head.ftype != parser::HTTP2FrameType::WindowUpdate as u8
|
|
&& head.ftype != parser::HTTP2FrameType::Ping as u8
|
|
&& head.ftype != parser::HTTP2FrameType::GoAway as u8
|
|
{
|
|
if head.ftype == parser::HTTP2FrameType::Data as u8 && head.stream_id == 0 {
|
|
tx.tx_data.set_event(HTTP2Event::DataStreamZero as u8);
|
|
} else {
|
|
tx.tx_data.set_event(HTTP2Event::InvalidFrameHeader as u8);
|
|
}
|
|
input = &rem[hlsafe..];
|
|
continue; // skip this frame, continue parsing the next
|
|
}
|
|
|
|
if let Some(doh_req_buf) = tx.handle_frame(&head, &txdata, dir) {
|
|
if let Ok(mut dtx) = dns_parse_request(&doh_req_buf, &DnsVariant::Dns) {
|
|
dtx.id = 1;
|
|
unsafe {
|
|
SCAppLayerForceProtocolChange(flow, ALPROTO_DOH2);
|
|
}
|
|
if let Some(doh) = &mut tx.doh {
|
|
doh.dns_request_tx = Some(dtx);
|
|
} else {
|
|
let doh = DohHttp2Tx {
|
|
dns_request_tx: Some(dtx),
|
|
..Default::default()
|
|
};
|
|
tx.doh = Some(doh);
|
|
}
|
|
}
|
|
}
|
|
if reass_limit_reached {
|
|
tx.tx_data
|
|
.set_event(HTTP2Event::ReassemblyLimitReached as u8);
|
|
}
|
|
let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0;
|
|
let ftype = head.ftype;
|
|
let sid = head.stream_id;
|
|
let padded = head.flags & parser::HTTP2_FLAG_HEADER_PADDED != 0;
|
|
let h2frames = if dir == Direction::ToServer {
|
|
&mut tx.frames_ts
|
|
} else {
|
|
&mut tx.frames_tc
|
|
};
|
|
if h2frames.len() < unsafe { HTTP2_MAX_FRAMES } {
|
|
h2frames.push(HTTP2Frame {
|
|
header: head,
|
|
data: txdata,
|
|
});
|
|
} else {
|
|
tx.tx_data.set_event(HTTP2Event::TooManyFrames as u8);
|
|
}
|
|
if ftype == parser::HTTP2FrameType::Data as u8 && sid > 0 {
|
|
tx.handle_data_frame(rem, hlsafe, dir, flow, padded, over);
|
|
let (il, ol) = if dir == Direction::ToClient {
|
|
(
|
|
tx.decoder.decoder_tc.input_len,
|
|
tx.decoder.decoder_tc.output_len,
|
|
)
|
|
} else {
|
|
(
|
|
tx.decoder.decoder_ts.input_len,
|
|
tx.decoder.decoder_ts.output_len,
|
|
)
|
|
};
|
|
let (il, ol) = (il + comp_len, ol + decomp_len);
|
|
if ol > decompression::DEFAULT_BOMB_RATIO * il {
|
|
if ol > unsafe { HTTP2_COMPRESSION_BOMB_LIMIT } {
|
|
tx.set_event(HTTP2Event::CompressionBomb);
|
|
return AppLayerResult::err();
|
|
}
|
|
if over {
|
|
self.comp_len += il;
|
|
self.decomp_len += ol;
|
|
}
|
|
}
|
|
}
|
|
sc_app_layer_parser_trigger_raw_stream_inspection(flow, dir as i32);
|
|
input = &rem[hlsafe..];
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
//we may have consumed data from previous records
|
|
return AppLayerResult::incomplete(
|
|
(il - input.len()) as u32,
|
|
HTTP2_FRAME_HEADER_LEN as u32,
|
|
);
|
|
}
|
|
Err(_) => {
|
|
self.set_event(HTTP2Event::InvalidFrameHeader);
|
|
return AppLayerResult::err();
|
|
}
|
|
}
|
|
}
|
|
return AppLayerResult::ok();
|
|
}
|
|
|
|
fn parse_ts(&mut self, flow: *mut Flow, stream_slice: StreamSlice) -> AppLayerResult {
|
|
//very first : skip magic
|
|
let mut input = stream_slice.as_slice();
|
|
let mut magic_consumed = 0;
|
|
if self.progress < HTTP2ConnectionState::Http2StateMagicDone {
|
|
//skip magic
|
|
if input.len() >= HTTP2_MAGIC_LEN {
|
|
//skip magic
|
|
match std::str::from_utf8(&input[..HTTP2_MAGIC_LEN]) {
|
|
Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {
|
|
input = &input[HTTP2_MAGIC_LEN..];
|
|
magic_consumed = HTTP2_MAGIC_LEN;
|
|
}
|
|
Ok(&_) => {
|
|
self.set_event(HTTP2Event::InvalidClientMagic);
|
|
}
|
|
Err(_) => {
|
|
return AppLayerResult::err();
|
|
}
|
|
}
|
|
self.progress = HTTP2ConnectionState::Http2StateMagicDone;
|
|
} else {
|
|
//still more buffer
|
|
return AppLayerResult::incomplete(0_u32, HTTP2_MAGIC_LEN as u32);
|
|
}
|
|
}
|
|
//first consume frame bytes
|
|
let il = input.len();
|
|
if self.request_frame_size > 0 {
|
|
let ilen = input.len() as u32;
|
|
if self.request_frame_size >= ilen {
|
|
self.request_frame_size -= ilen;
|
|
return AppLayerResult::ok();
|
|
} else {
|
|
let start = self.request_frame_size as usize;
|
|
input = &input[start..];
|
|
self.request_frame_size = 0;
|
|
}
|
|
}
|
|
|
|
//then parse all we can
|
|
let r = self.parse_frames(input, il, Direction::ToServer, flow, &stream_slice);
|
|
if r.status == 1 {
|
|
//adds bytes consumed by banner to incomplete result
|
|
return AppLayerResult::incomplete(r.consumed + magic_consumed as u32, r.needed);
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
fn parse_tc(&mut self, flow: *mut Flow, stream_slice: StreamSlice) -> AppLayerResult {
|
|
//first consume frame bytes
|
|
let mut input = stream_slice.as_slice();
|
|
let il = input.len();
|
|
if self.response_frame_size > 0 {
|
|
let ilen = input.len() as u32;
|
|
if self.response_frame_size >= ilen {
|
|
self.response_frame_size -= ilen;
|
|
return AppLayerResult::ok();
|
|
} else {
|
|
let start = self.response_frame_size as usize;
|
|
input = &input[start..];
|
|
self.response_frame_size = 0;
|
|
}
|
|
}
|
|
//then parse all we can
|
|
return self.parse_frames(input, il, Direction::ToClient, flow, &stream_slice);
|
|
}
|
|
}
|
|
|
|
// C exports.
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn SCDoH2GetDnsTx(
|
|
tx: &HTTP2Transaction, flags: u8,
|
|
) -> *mut std::os::raw::c_void {
|
|
if let Some(doh) = &tx.doh {
|
|
if flags & Direction::ToServer as u8 != 0 {
|
|
if let Some(ref dtx) = &doh.dns_request_tx {
|
|
return dtx as *const _ as *mut _;
|
|
}
|
|
} else if flags & Direction::ToClient as u8 != 0 {
|
|
if let Some(ref dtx) = &doh.dns_response_tx {
|
|
return dtx as *const _ as *mut _;
|
|
}
|
|
}
|
|
}
|
|
std::ptr::null_mut()
|
|
}
|
|
|
|
export_tx_data_get!(http2_get_tx_data, HTTP2Transaction);
|
|
export_state_data_get!(http2_get_state_data, HTTP2State);
|
|
|
|
/// C entry point for a probing parser.
|
|
unsafe extern "C" fn http2_probing_parser_tc(
|
|
_flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
|
|
) -> AppProto {
|
|
if !input.is_null() {
|
|
let slice = build_slice!(input, input_len as usize);
|
|
match parser::http2_parse_frame_header(slice) {
|
|
Ok((_, header)) => {
|
|
if header.reserved != 0
|
|
|| header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE
|
|
|| header.flags & 0xFE != 0
|
|
|| header.ftype != parser::HTTP2FrameType::Settings as u8
|
|
{
|
|
return ALPROTO_FAILED;
|
|
}
|
|
return ALPROTO_HTTP2;
|
|
}
|
|
Err(Err::Incomplete(_)) => {
|
|
return ALPROTO_UNKNOWN;
|
|
}
|
|
Err(_) => {
|
|
return ALPROTO_FAILED;
|
|
}
|
|
}
|
|
}
|
|
return ALPROTO_UNKNOWN;
|
|
}
|
|
|
|
// Suppress the unsafe warning here as creating a state for an app-layer
|
|
// is typically not unsafe.
|
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
|
extern "C" fn http2_state_new(
|
|
orig_state: *mut std::os::raw::c_void, orig_proto: AppProto,
|
|
) -> *mut std::os::raw::c_void {
|
|
let state = HTTP2State::new();
|
|
let boxed = Box::new(state);
|
|
let r = Box::into_raw(boxed) as *mut _;
|
|
if !orig_state.is_null() && orig_proto == ALPROTO_HTTP1 as u16 {
|
|
unsafe {
|
|
SCHTTP2MimicHttp1Request(orig_state, r);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
unsafe extern "C" fn http2_state_free(state: *mut std::os::raw::c_void) {
|
|
let mut state: Box<HTTP2State> = Box::from_raw(state as _);
|
|
state.free();
|
|
}
|
|
|
|
unsafe extern "C" fn 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);
|
|
}
|
|
|
|
unsafe extern "C" fn http2_parse_ts(
|
|
flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
|
|
stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
|
|
) -> AppLayerResult {
|
|
let state = cast_pointer!(state, HTTP2State);
|
|
return state.parse_ts(flow, stream_slice);
|
|
}
|
|
|
|
unsafe extern "C" fn http2_parse_tc(
|
|
flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
|
|
stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
|
|
) -> AppLayerResult {
|
|
let state = cast_pointer!(state, HTTP2State);
|
|
return state.parse_tc(flow, stream_slice);
|
|
}
|
|
|
|
unsafe extern "C" fn 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 tx as *const _ as *mut _;
|
|
}
|
|
None => {
|
|
return std::ptr::null_mut();
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn http2_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
|
|
let state = cast_pointer!(state, HTTP2State);
|
|
return state.tx_id;
|
|
}
|
|
|
|
unsafe extern "C" fn 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);
|
|
return tx.progress.get(direction);
|
|
}
|
|
|
|
unsafe extern "C" fn http2_getfiles(
|
|
tx: *mut std::os::raw::c_void, direction: u8,
|
|
) -> AppLayerGetFileState {
|
|
let tx = cast_pointer!(tx, HTTP2Transaction);
|
|
if let Some(sfcm) = SURICATA_HTTP2_FILE_CONFIG {
|
|
if direction & STREAM_TOSERVER != 0 {
|
|
return AppLayerGetFileState {
|
|
fc: &mut tx.ft_ts.file,
|
|
cfg: sfcm.files_sbcfg,
|
|
};
|
|
} else {
|
|
return AppLayerGetFileState {
|
|
fc: &mut tx.ft_tc.file,
|
|
cfg: sfcm.files_sbcfg,
|
|
};
|
|
}
|
|
}
|
|
AppLayerGetFileState::err()
|
|
}
|
|
|
|
// Parser name as a C style string.
|
|
const PARSER_NAME: &[u8] = b"http2\0";
|
|
|
|
#[no_mangle]
|
|
pub unsafe extern "C" fn SCRegisterHttp2Parser() {
|
|
let mut http2_enabled = false;
|
|
let default_port = CString::new("[80]").unwrap();
|
|
let mut parser = RustParser {
|
|
name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
|
|
default_port: default_port.as_ptr(),
|
|
ipproto: IPPROTO_TCP,
|
|
probe_ts: None, // big magic string should be enough
|
|
probe_tc: Some(http2_probing_parser_tc),
|
|
min_depth: HTTP2_FRAME_HEADER_LEN as u16,
|
|
max_depth: HTTP2_MAGIC_LEN as u16,
|
|
state_new: http2_state_new,
|
|
state_free: http2_state_free,
|
|
tx_free: http2_state_tx_free,
|
|
parse_ts: http2_parse_ts,
|
|
parse_tc: http2_parse_tc,
|
|
get_tx_count: http2_state_get_tx_count,
|
|
get_tx: http2_state_get_tx,
|
|
tx_comp_st_ts: HTTP2TxProgress::HTTP2ProgComplete as i32,
|
|
tx_comp_st_tc: HTTP2TxProgress::HTTP2ProgComplete as i32,
|
|
tx_get_progress: http2_tx_get_alstate_progress,
|
|
get_eventinfo: Some(HTTP2Event::get_event_info),
|
|
get_eventinfo_byid: Some(HTTP2Event::get_event_info_by_id),
|
|
localstorage_new: None,
|
|
localstorage_free: None,
|
|
get_tx_files: Some(http2_getfiles),
|
|
get_tx_iterator: Some(applayer::state_get_tx_iterator::<HTTP2State, HTTP2Transaction>),
|
|
get_tx_data: http2_get_tx_data,
|
|
get_state_data: http2_get_state_data,
|
|
apply_tx_config: None,
|
|
flags: 0,
|
|
get_frame_id_by_name: Some(Http2FrameType::ffi_id_from_name),
|
|
get_frame_name_by_id: Some(Http2FrameType::ffi_name_from_id),
|
|
get_state_id_by_name: None,
|
|
get_state_name_by_id: None,
|
|
};
|
|
|
|
let ip_proto_str = CString::new("tcp").unwrap();
|
|
|
|
if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
|
let alproto = applayer_register_protocol_detection(&parser, 1);
|
|
ALPROTO_HTTP2 = alproto;
|
|
if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
|
let _ = AppLayerRegisterParser(&parser, alproto);
|
|
http2_enabled = true;
|
|
}
|
|
if let Some(val) = conf_get("app-layer.protocols.http2.max-streams") {
|
|
if let Ok(v) = val.parse::<usize>() {
|
|
HTTP2_MAX_STREAMS = v;
|
|
} else {
|
|
SCLogError!("Invalid value for http2.max-streams");
|
|
}
|
|
}
|
|
if let Some(val) = conf_get("app-layer.protocols.http2.max-frames") {
|
|
if let Ok(v) = val.parse::<usize>() {
|
|
HTTP2_MAX_FRAMES = v;
|
|
} else {
|
|
SCLogError!("Invalid value for http2.max-frames");
|
|
}
|
|
}
|
|
if let Some(val) = conf_get("app-layer.protocols.http2.max-table-size") {
|
|
if let Ok(v) = val.parse::<u32>() {
|
|
HTTP2_MAX_TABLESIZE = v;
|
|
} else {
|
|
SCLogError!("Invalid value for http2.max-table-size");
|
|
}
|
|
}
|
|
if let Some(val) = conf_get("app-layer.protocols.http2.max-reassembly-size") {
|
|
if let Ok(v) = val.parse::<u32>() {
|
|
HTTP2_MAX_REASS = v as usize;
|
|
} else {
|
|
SCLogError!("Invalid value for http2.max-reassembly-size");
|
|
}
|
|
}
|
|
if let Some(val) = conf_get("app-layer.protocols.http2.compression-bomb-limit") {
|
|
if let Ok(v) = get_memval(val) {
|
|
HTTP2_COMPRESSION_BOMB_LIMIT = v;
|
|
} else {
|
|
SCLogWarning!("Invalid value for http2.compression-bomb-limit");
|
|
}
|
|
}
|
|
SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2);
|
|
|
|
SCAppLayerParserRegisterGetTxSubStateFuncs(
|
|
ALPROTO_HTTP2,
|
|
HTTP2TxType::HTTP2TxTypeStream as u8,
|
|
Some(HTTP2TxProgress::ffi_id_from_name),
|
|
Some(HTTP2TxProgress::ffi_name_from_id),
|
|
HTTP2TxProgress::HTTP2ProgComplete as u8,
|
|
);
|
|
SCAppLayerParserRegisterGetTxSubStateFuncs(
|
|
ALPROTO_HTTP2,
|
|
HTTP2TxType::HTTP2TxTypeGlobal as u8,
|
|
Some(HTTP2TxGlobalProgress::ffi_id_from_name),
|
|
Some(HTTP2TxGlobalProgress::ffi_name_from_id),
|
|
HTTP2TxGlobalProgress::HTTP2ProgGlobalComplete as u8,
|
|
);
|
|
|
|
SCLogDebug!("Rust http2 parser registered.");
|
|
} else {
|
|
SCLogNotice!("Protocol detector and parser disabled for HTTP2.");
|
|
}
|
|
|
|
// doh2 is just http2 wrapped in another name
|
|
parser.name = b"doh2\0".as_ptr() as *const std::os::raw::c_char;
|
|
parser.probe_tc = None;
|
|
parser.default_port = std::ptr::null();
|
|
if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
|
let alproto = applayer_register_protocol_detection(&parser, 1);
|
|
ALPROTO_DOH2 = alproto;
|
|
if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
|
|
if http2_enabled {
|
|
let _ = AppLayerRegisterParser(&parser, alproto);
|
|
} else {
|
|
SCLogWarning!("DOH2 cannot be enabled if http2 is disabled");
|
|
}
|
|
} else {
|
|
SCLogWarning!("DOH2 is not meant to be detection-only.");
|
|
}
|
|
SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DOH2);
|
|
SCLogDebug!("Rust doh2 parser registered.");
|
|
} else {
|
|
SCLogNotice!("Protocol detector and parser disabled for DOH2.");
|
|
}
|
|
}
|