Skip to content
Closed
Show file tree
Hide file tree
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
79 changes: 69 additions & 10 deletions rust/src/ssh/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::core::*;
use nom7::Err;
use std::ffi::CString;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::frames::Frame;

static mut ALPROTO_SSH: AppProto = ALPROTO_UNKNOWN;
static HASSH_ENABLED: AtomicBool = AtomicBool::new(false);
Expand All @@ -29,6 +30,14 @@ fn hassh_is_enabled() -> bool {
HASSH_ENABLED.load(Ordering::Relaxed)
}

// app-layer-frame-documentation tag start: FrameType enum
#[derive(AppLayerFrameType)]
pub enum SshFrameType {
RecordHdr,
RecordData,
RecordPdu,
}

#[derive(AppLayerEvent)]
pub enum SSHEvent {
InvalidBanner,
Expand Down Expand Up @@ -109,6 +118,7 @@ impl SSHState {

fn parse_record(
&mut self, mut input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
flow: *const Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let (hdr, ohdr) = if !resp {
(&mut self.transaction.cli_hdr, &self.transaction.srv_hdr)
Expand Down Expand Up @@ -149,6 +159,30 @@ impl SSHState {
while !input.is_empty() {
match parser::ssh_parse_record(input) {
Ok((rem, head)) => {
let _pdu = Frame::new(
Copy link
Member

Choose a reason for hiding this comment

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

I think it would make sense to move this outside of Ok, as we can want the frames in malformed data as well. I suppose you would just open all 3 (RecordHdr with fixed len, the other two with -1 len), then set the final length on Ok.

In general, I am missing an explanation of the frames in the commit message.

Also should add docs for the frames, cf https://docs.suricata.io/en/latest/rules/smtp-keywords.html#frames

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it would make sense to move this outside of Ok, as we can want the frames in malformed data as well. I suppose you would just open all 3 (RecordHdr with fixed len, the other two with -1 len), then set the final length on Ok.

I am not sure it makes sense for SSH :

  • malformed data can only be "negative length" for SSH record data (in SSH, there is the anti pattern that the length includes the fixed header length), and in this case, the flow is placed into error. So it is then impossible to "set the final length on Ok."
  • for incomplete data, it is already handled in one way

Should I spend some time again to try to make a SV test that shows a different behavior with -1 len ?

In general, I am missing an explanation of the frames in the commit message.

Adding something

Also should add docs for the frames

I thought frames were automatically documented, by some rustdocs magic with // app-layer-frame-documentation tag start: FrameType enum
cc @jasonish ?

Add adding something like smtp

Copy link
Member

Choose a reason for hiding this comment

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

I thought frames were automatically documented, by some rustdocs magic with // app-layer-frame-documentation tag start: FrameType enum
cc @jasonish ?

Am not familiar with anything like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See b846269

I had interpreted this wrongly

flow,
stream_slice,
input,
SSH_RECORD_HEADER_LEN as i64,
SshFrameType::RecordHdr as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
&input[SSH_RECORD_HEADER_LEN..],
(head.pkt_len - 2) as i64,
SshFrameType::RecordData as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
input,
(head.pkt_len + 4) as i64,
SshFrameType::RecordPdu as u8,
Some(0),
);
SCLogDebug!("SSH valid record {}", head);
match head.msg_code {
parser::MessageCode::Kexinit if hassh_is_enabled() => {
Expand Down Expand Up @@ -180,6 +214,30 @@ impl SSHState {
Err(Err::Incomplete(_)) => {
match parser::ssh_parse_record_header(input) {
Ok((rem, head)) => {
let _pdu = Frame::new(
flow,
stream_slice,
input,
SSH_RECORD_HEADER_LEN as i64,
SshFrameType::RecordHdr as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
&input[SSH_RECORD_HEADER_LEN..],
(head.pkt_len - 2) as i64,
SshFrameType::RecordData as u8,
Some(0),
);
let _pdu = Frame::new(
flow,
stream_slice,
input,
(head.pkt_len + 4) as i64,
SshFrameType::RecordPdu as u8,
Some(0),
);
SCLogDebug!("SSH valid record header {}", head);
let remlen = rem.len() as u32;
hdr.record_left = head.pkt_len - 2 - remlen;
Expand Down Expand Up @@ -239,6 +297,7 @@ impl SSHState {

fn parse_banner(
&mut self, input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
flow: *const Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let hdr = if !resp {
&mut self.transaction.cli_hdr
Expand All @@ -248,7 +307,7 @@ impl SSHState {
if hdr.flags == SSHConnectionState::SshStateBannerWaitEol {
match parser::ssh_parse_line(input) {
Ok((rem, _)) => {
let mut r = self.parse_record(rem, resp, pstate);
let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
if r.is_incomplete() {
//adds bytes consumed by banner to incomplete result
r.consumed += (input.len() - rem.len()) as u32;
Expand Down Expand Up @@ -288,7 +347,7 @@ impl SSHState {
);
self.set_event(SSHEvent::LongBanner);
}
let mut r = self.parse_record(rem, resp, pstate);
let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
if r.is_incomplete() {
//adds bytes consumed by banner to incomplete result
r.consumed += (input.len() - rem.len()) as u32;
Expand Down Expand Up @@ -352,33 +411,33 @@ pub extern "C" fn rs_ssh_state_tx_free(_state: *mut std::os::raw::c_void, _tx_id

#[no_mangle]
pub unsafe extern "C" fn rs_ssh_parse_request(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice,
_data: *const std::os::raw::c_void
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = stream_slice.as_slice();
let hdr = &mut state.transaction.cli_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, false, pstate);
return state.parse_banner(buf, false, pstate, flow, &stream_slice);
} else {
return state.parse_record(buf, false, pstate);
return state.parse_record(buf, false, pstate, flow, &stream_slice);
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_ssh_parse_response(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
stream_slice: StreamSlice,
_data: *const std::os::raw::c_void
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = stream_slice.as_slice();
let hdr = &mut state.transaction.srv_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, true, pstate);
return state.parse_banner(buf, true, pstate, flow, &stream_slice);
} else {
return state.parse_record(buf, true, pstate);
return state.parse_record(buf, true, pstate, flow, &stream_slice);
}
}

Expand Down Expand Up @@ -465,8 +524,8 @@ pub unsafe extern "C" fn rs_ssh_register_parser() {
apply_tx_config: None,
flags: 0,
truncate: None,
get_frame_id_by_name: None,
get_frame_name_by_id: None,
get_frame_id_by_name: Some(SshFrameType::ffi_id_from_name),
get_frame_name_by_id: Some(SshFrameType::ffi_name_from_id),
};

let ip_proto_str = CString::new("tcp").unwrap();
Expand Down
8 changes: 7 additions & 1 deletion src/detect.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ static void DetectRun(ThreadVars *th_v,
goto end;
}
const TcpSession *ssn = p->flow->protoctx;
if (ssn && (ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) == 0) {
bool setting_nopayload = p->flow->alparser &&
AppLayerParserStateIssetFlag(
p->flow->alparser, APP_LAYER_PARSER_NO_INSPECTION) &&
!(p->flags & PKT_NOPAYLOAD_INSPECTION);
// we may be right after disabling app-layer (ssh)
if (ssn &&
((ssn->flags & STREAMTCP_FLAG_APP_LAYER_DISABLED) == 0 || setting_nopayload)) {
// PACKET_PROFILING_DETECT_START(p, PROF_DETECT_TX);
DetectRunFrames(th_v, de_ctx, det_ctx, p, pflow, &scratch);
// PACKET_PROFILING_DETECT_END(p, PROF_DETECT_TX);
Expand Down