• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OISF / suricata / 22550934724

01 Mar 2026 07:34PM UTC coverage: 75.853% (+2.2%) from 73.687%
22550934724

Pull #14923

github

web-flow
github-actions: bump github/codeql-action from 4.32.3 to 4.32.4

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.3 to 4.32.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v4.32.3...v4.32.4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #14923: github-actions: bump github/codeql-action from 4.32.3 to 4.32.4

240804 of 317461 relevant lines covered (75.85%)

2441195.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.49
/rust/src/ssh/ssh.rs
1
/* Copyright (C) 2020-2025 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17

18
use super::parser;
19
use crate::applayer::*;
20
use crate::core::*;
21
use crate::direction::Direction;
22
use crate::encryption::EncryptionHandling;
23
use crate::flow::Flow;
24
use crate::frames::Frame;
25
use nom8::Err;
26
use std::ffi::CString;
27
use std::sync::atomic::{AtomicBool, Ordering};
28
use suricata_sys::sys::{
29
    AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
30
    SCAppLayerParserRegisterLogger, SCAppLayerParserStateSetFlag,
31
    SCAppLayerProtoDetectConfProtoDetectionEnabled,
32
};
33

34
pub(super) static mut ALPROTO_SSH: AppProto = ALPROTO_UNKNOWN;
35
static HASSH_ENABLED: AtomicBool = AtomicBool::new(false);
36
static HASSH_DISABLED: AtomicBool = AtomicBool::new(false);
37

38
static mut ENCRYPTION_BYPASS_ENABLED: EncryptionHandling =
39
    EncryptionHandling::ENCRYPTION_HANDLING_TRACK_ONLY;
40

41
fn hassh_is_enabled() -> bool {
21,673✔
42
    HASSH_ENABLED.load(Ordering::Relaxed)
21,673✔
43
}
21,673✔
44

45
fn encryption_bypass_mode() -> EncryptionHandling {
46✔
46
    unsafe { ENCRYPTION_BYPASS_ENABLED }
46✔
47
}
46✔
48

49
#[derive(AppLayerFrameType)]
50
pub enum SshFrameType {
51
    RecordHdr,
52
    RecordData,
53
    RecordPdu,
54
}
55

56
#[derive(AppLayerEvent)]
374✔
57
pub enum SSHEvent {
58
    InvalidBanner,
59
    LongBanner,
60
    InvalidRecord,
61
    LongKexRecord,
62
}
63

64
#[repr(u8)]
65
#[derive(AppLayerState, Copy, Clone, PartialOrd, PartialEq, Eq)]
66
#[suricata(alstate_strip_prefix = "SshState")]
67
pub enum SSHConnectionState {
68
    SshStateInProgress = 0,
69
    SshStateBannerWaitEol = 1,
70
    SshStateBannerDone = 2,
71
    SshStateFinished = 3,
72
}
73

74
pub const SSH_MAX_BANNER_LEN: usize = 256;
75
const SSH_RECORD_HEADER_LEN: usize = 6;
76
const SSH_MAX_REASSEMBLED_RECORD_LEN: usize = 65535;
77

78
pub struct SshHeader {
79
    record_left: u32,
80
    record_left_msg: parser::MessageCode,
81

82
    flags: SSHConnectionState,
83
    pub protover: Vec<u8>,
84
    pub swver: Vec<u8>,
85

86
    pub hassh: Vec<u8>,
87
    pub hassh_string: Vec<u8>,
88
}
89

90
impl Default for SshHeader {
91
    fn default() -> Self {
406✔
92
        Self::new()
406✔
93
    }
406✔
94
}
95

96
impl SshHeader {
97
    pub fn new() -> SshHeader {
406✔
98
        Self {
406✔
99
            record_left: 0,
406✔
100
            record_left_msg: parser::MessageCode::Undefined(0),
406✔
101

406✔
102
            flags: SSHConnectionState::SshStateInProgress,
406✔
103
            protover: Vec::new(),
406✔
104
            swver: Vec::new(),
406✔
105

406✔
106
            hassh: Vec::new(),
406✔
107
            hassh_string: Vec::new(),
406✔
108
        }
406✔
109
    }
406✔
110
}
111

112
#[derive(Default)]
113
pub struct SSHTransaction {
114
    pub srv_hdr: SshHeader,
115
    pub cli_hdr: SshHeader,
116

117
    tx_data: AppLayerTxData,
118
}
119

120
#[derive(Default)]
121
pub struct SSHState {
122
    state_data: AppLayerStateData,
123
    transaction: SSHTransaction,
124
}
125

126
impl SSHState {
127
    pub fn new() -> Self {
203✔
128
        Default::default()
203✔
129
    }
203✔
130

131
    fn set_event(&mut self, event: SSHEvent) {
17✔
132
        self.transaction.tx_data.set_event(event as u8);
17✔
133
    }
17✔
134

135
    fn parse_record(
1,194✔
136
        &mut self, mut input: &[u8], resp: bool, pstate: *mut AppLayerParserState,
1,194✔
137
        flow: *mut Flow, stream_slice: &StreamSlice,
1,194✔
138
    ) -> AppLayerResult {
1,194✔
139
        let (hdr, ohdr) = if !resp {
1,194✔
140
            (&mut self.transaction.cli_hdr, &self.transaction.srv_hdr)
547✔
141
        } else {
142
            (&mut self.transaction.srv_hdr, &self.transaction.cli_hdr)
647✔
143
        };
144
        let il = input.len();
1,194✔
145
        //first skip record left bytes
1,194✔
146
        if hdr.record_left > 0 {
1,194✔
147
            //should we check for overflow ?
148
            let ilen = input.len() as u32;
398✔
149
            if hdr.record_left > ilen {
398✔
150
                hdr.record_left -= ilen;
363✔
151
                return AppLayerResult::ok();
363✔
152
            } else {
153
                let start = hdr.record_left as usize;
35✔
154
                match hdr.record_left_msg {
29✔
155
                    // parse reassembled tcp segments
156
                    parser::MessageCode::Kexinit if hassh_is_enabled() => {
29✔
157
                        if let Ok((_rem, key_exchange)) =
20✔
158
                            parser::ssh_parse_key_exchange(&input[..start])
29✔
159
                        {
20✔
160
                            key_exchange.generate_hassh(
20✔
161
                                &mut hdr.hassh_string,
20✔
162
                                &mut hdr.hassh,
20✔
163
                                &resp,
20✔
164
                            );
20✔
165
                        }
20✔
166
                        hdr.record_left_msg = parser::MessageCode::Undefined(0);
29✔
167
                    }
168
                    _ => {}
6✔
169
                }
170
                input = &input[start..];
35✔
171
                hdr.record_left = 0;
35✔
172
            }
173
        }
796✔
174
        //parse records out of input
175
        while !input.is_empty() {
1,341✔
176
            match parser::ssh_parse_record(input) {
702✔
177
                Ok((rem, head)) => {
510✔
178
                    let _pdu = Frame::new(
510✔
179
                        flow,
510✔
180
                        stream_slice,
510✔
181
                        input,
510✔
182
                        SSH_RECORD_HEADER_LEN as i64,
510✔
183
                        SshFrameType::RecordHdr as u8,
510✔
184
                        Some(0),
510✔
185
                    );
510✔
186
                    let _pdu = Frame::new(
510✔
187
                        flow,
510✔
188
                        stream_slice,
510✔
189
                        &input[SSH_RECORD_HEADER_LEN..],
510✔
190
                        (head.pkt_len - 2) as i64,
510✔
191
                        SshFrameType::RecordData as u8,
510✔
192
                        Some(0),
510✔
193
                    );
510✔
194
                    let _pdu = Frame::new(
510✔
195
                        flow,
510✔
196
                        stream_slice,
510✔
197
                        input,
510✔
198
                        (head.pkt_len + 4) as i64,
510✔
199
                        SshFrameType::RecordPdu as u8,
510✔
200
                        Some(0),
510✔
201
                    );
510✔
202
                    SCLogDebug!("SSH valid record {}", head);
203
                    match head.msg_code {
175✔
204
                        parser::MessageCode::Kexinit if hassh_is_enabled() => {
150✔
205
                            //let endkex = SSH_RECORD_HEADER_LEN + head.pkt_len - 2;
150✔
206
                            let endkex = input.len() - rem.len();
150✔
207
                            if let Ok((_, key_exchange)) = parser::ssh_parse_key_exchange(
150✔
208
                                &input[SSH_RECORD_HEADER_LEN..endkex],
150✔
209
                            ) {
150✔
210
                                key_exchange.generate_hassh(
126✔
211
                                    &mut hdr.hassh_string,
126✔
212
                                    &mut hdr.hassh,
126✔
213
                                    &resp,
126✔
214
                                );
126✔
215
                            }
127✔
216
                        }
217
                        parser::MessageCode::NewKeys => {
218
                            hdr.flags = SSHConnectionState::SshStateFinished;
129✔
219
                            if ohdr.flags >= SSHConnectionState::SshStateFinished {
129✔
220
                                let mut flags = 0;
46✔
221

46✔
222
                                match encryption_bypass_mode() {
46✔
223
                                    EncryptionHandling::ENCRYPTION_HANDLING_BYPASS => {
×
224
                                        flags |= APP_LAYER_PARSER_NO_INSPECTION
×
225
                                            | APP_LAYER_PARSER_NO_REASSEMBLY
×
226
                                            | APP_LAYER_PARSER_BYPASS_READY;
×
227
                                    }
×
228
                                    EncryptionHandling::ENCRYPTION_HANDLING_TRACK_ONLY => {
46✔
229
                                        flags |= APP_LAYER_PARSER_NO_INSPECTION;
46✔
230
                                    }
46✔
231
                                    _ => {}
×
232
                                }
233

234
                                if flags != 0 {
46✔
235
                                    unsafe {
46✔
236
                                        SCAppLayerParserStateSetFlag(pstate, flags);
46✔
237
                                    }
46✔
238
                                }
×
239
                            }
83✔
240
                        }
241
                        _ => {}
231✔
242
                    }
243

244
                    input = rem;
510✔
245
                    //header and complete data (not returned)
246
                }
247
                Err(Err::Incomplete(_)) => {
248
                    match parser::ssh_parse_record_header(input) {
192✔
249
                        Ok((rem, head)) => {
171✔
250
                            let _pdu = Frame::new(
171✔
251
                                flow,
171✔
252
                                stream_slice,
171✔
253
                                input,
171✔
254
                                SSH_RECORD_HEADER_LEN as i64,
171✔
255
                                SshFrameType::RecordHdr as u8,
171✔
256
                                Some(0),
171✔
257
                            );
171✔
258
                            let _pdu = Frame::new(
171✔
259
                                flow,
171✔
260
                                stream_slice,
171✔
261
                                &input[SSH_RECORD_HEADER_LEN..],
171✔
262
                                (head.pkt_len - 2) as i64,
171✔
263
                                SshFrameType::RecordData as u8,
171✔
264
                                Some(0),
171✔
265
                            );
171✔
266
                            let _pdu = Frame::new(
171✔
267
                                flow,
171✔
268
                                stream_slice,
171✔
269
                                input,
171✔
270
                                // cast first to avoid unsigned integer overflow
171✔
271
                                (head.pkt_len as u64 + 4) as i64,
171✔
272
                                SshFrameType::RecordPdu as u8,
171✔
273
                                Some(0),
171✔
274
                            );
171✔
275
                            SCLogDebug!("SSH valid record header {}", head);
171✔
276
                            let remlen = rem.len() as u32;
171✔
277
                            hdr.record_left = head.pkt_len - 2 - remlen;
171✔
278
                            //header with rem as incomplete data
279
                            match head.msg_code {
46✔
280
                                parser::MessageCode::NewKeys => {
9✔
281
                                    hdr.flags = SSHConnectionState::SshStateFinished;
9✔
282
                                }
9✔
283
                                parser::MessageCode::Kexinit if hassh_is_enabled() => {
44✔
284
                                    // check if buffer is bigger than maximum reassembled packet size
44✔
285
                                    hdr.record_left = head.pkt_len - 2;
44✔
286
                                    if hdr.record_left < SSH_MAX_REASSEMBLED_RECORD_LEN as u32 {
44✔
287
                                        // saving type of incomplete kex message
288
                                        hdr.record_left_msg = parser::MessageCode::Kexinit;
43✔
289
                                        return AppLayerResult::incomplete(
43✔
290
                                            (il - rem.len()) as u32,
43✔
291
                                            head.pkt_len - 2,
43✔
292
                                        );
43✔
293
                                    } else {
1✔
294
                                        SCLogDebug!("SSH buffer is bigger than maximum reassembled packet size");
1✔
295
                                        self.set_event(SSHEvent::LongKexRecord);
1✔
296
                                    }
1✔
297
                                }
298
                                _ => {}
118✔
299
                            }
300
                            return AppLayerResult::ok();
128✔
301
                        }
302
                        Err(Err::Incomplete(_)) => {
303
                            //we may have consumed data from previous records
304
                            debug_validate_bug_on!(input.len() >= SSH_RECORD_HEADER_LEN);
305
                            //do not trust nom incomplete value
306
                            return AppLayerResult::incomplete(
21✔
307
                                (il - input.len()) as u32,
21✔
308
                                SSH_RECORD_HEADER_LEN as u32,
21✔
309
                            );
21✔
310
                        }
311
                        Err(_e) => {
×
312
                            SCLogDebug!("SSH invalid record header {}", _e);
×
313
                            self.set_event(SSHEvent::InvalidRecord);
×
314
                            return AppLayerResult::err();
×
315
                        }
316
                    }
317
                }
318
                Err(_e) => {
×
319
                    SCLogDebug!("SSH invalid record {}", _e);
×
320
                    self.set_event(SSHEvent::InvalidRecord);
×
321
                    return AppLayerResult::err();
×
322
                }
323
            }
324
        }
325
        return AppLayerResult::ok();
639✔
326
    }
1,194✔
327

328
    fn parse_banner(
381✔
329
        &mut self, input: &[u8], resp: bool, pstate: *mut AppLayerParserState, flow: *mut Flow,
381✔
330
        stream_slice: &StreamSlice,
381✔
331
    ) -> AppLayerResult {
381✔
332
        let hdr = if !resp {
381✔
333
            &mut self.transaction.cli_hdr
189✔
334
        } else {
335
            &mut self.transaction.srv_hdr
192✔
336
        };
337
        if hdr.flags == SSHConnectionState::SshStateBannerWaitEol {
381✔
338
            match parser::ssh_parse_line(input) {
2✔
339
                Ok((rem, _)) => {
2✔
340
                    let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
2✔
341
                    if r.is_incomplete() {
2✔
342
                        //adds bytes consumed by banner to incomplete result
×
343
                        r.consumed += (input.len() - rem.len()) as u32;
×
344
                    } else if r.is_ok() {
2✔
345
                        let mut dir = Direction::ToServer as i32;
2✔
346
                        if resp {
2✔
347
                            dir = Direction::ToClient as i32;
2✔
348
                        }
2✔
349
                        sc_app_layer_parser_trigger_raw_stream_inspection(flow, dir);
2✔
350
                    }
×
351
                    return r;
2✔
352
                }
353
                Err(Err::Incomplete(_)) => {
354
                    // we do not need to retain these bytes
355
                    // we parsed them, we skip them
356
                    return AppLayerResult::ok();
×
357
                }
358
                Err(_e) => {
×
359
                    SCLogDebug!("SSH invalid banner {}", _e);
×
360
                    self.set_event(SSHEvent::InvalidBanner);
×
361
                    return AppLayerResult::err();
×
362
                }
363
            }
364
        }
379✔
365
        match parser::ssh_parse_line(input) {
379✔
366
            Ok((rem, line)) => {
344✔
367
                if let Ok((_, banner)) = parser::ssh_parse_banner(line) {
344✔
368
                    hdr.protover.extend(banner.protover);
335✔
369
                    if !banner.swver.is_empty() {
335✔
370
                        hdr.swver.extend(banner.swver);
334✔
371
                    }
334✔
372
                    hdr.flags = SSHConnectionState::SshStateBannerDone;
335✔
373
                } else {
374
                    SCLogDebug!("SSH invalid banner");
375
                    self.set_event(SSHEvent::InvalidBanner);
9✔
376
                    return AppLayerResult::err();
9✔
377
                }
378
                if line.len() >= SSH_MAX_BANNER_LEN {
335✔
379
                    SCLogDebug!(
2✔
380
                        "SSH banner too long {} vs {}",
2✔
381
                        line.len(),
2✔
382
                        SSH_MAX_BANNER_LEN
2✔
383
                    );
2✔
384
                    self.set_event(SSHEvent::LongBanner);
2✔
385
                }
333✔
386
                let mut r = self.parse_record(rem, resp, pstate, flow, stream_slice);
335✔
387
                if r.is_incomplete() {
335✔
388
                    //adds bytes consumed by banner to incomplete result
60✔
389
                    r.consumed += (input.len() - rem.len()) as u32;
60✔
390
                } else if r.is_ok() {
275✔
391
                    let mut dir = Direction::ToServer as i32;
275✔
392
                    if resp {
275✔
393
                        dir = Direction::ToClient as i32;
154✔
394
                    }
155✔
395
                    sc_app_layer_parser_trigger_raw_stream_inspection(flow, dir);
275✔
396
                }
×
397
                return r;
335✔
398
            }
399
            Err(Err::Incomplete(_)) => {
400
                // see https://github.com/rust-lang/rust-clippy/issues/15158
401
                #[allow(clippy::collapsible_else_if)]
402
                if input.len() < SSH_MAX_BANNER_LEN {
35✔
403
                    //0 consumed, needs at least one more byte
404
                    return AppLayerResult::incomplete(0_u32, (input.len() + 1) as u32);
30✔
405
                } else {
406
                    SCLogDebug!(
407
                        "SSH banner too long {} vs {} and waiting for eol",
408
                        input.len(),
409
                        SSH_MAX_BANNER_LEN
410
                    );
411
                    if let Ok((_, banner)) = parser::ssh_parse_banner(input) {
5✔
412
                        hdr.protover.extend(banner.protover);
3✔
413
                        if !banner.swver.is_empty() {
3✔
414
                            hdr.swver.extend(banner.swver);
3✔
415
                        }
3✔
416
                        hdr.flags = SSHConnectionState::SshStateBannerWaitEol;
3✔
417
                        self.set_event(SSHEvent::LongBanner);
3✔
418
                        return AppLayerResult::ok();
3✔
419
                    } else {
420
                        self.set_event(SSHEvent::InvalidBanner);
2✔
421
                        return AppLayerResult::err();
2✔
422
                    }
423
                }
424
            }
425
            Err(_e) => {
×
426
                SCLogDebug!("SSH invalid banner {}", _e);
×
427
                self.set_event(SSHEvent::InvalidBanner);
×
428
                return AppLayerResult::err();
×
429
            }
430
        }
431
    }
381✔
432
}
433

434
// C exports.
435

436
export_tx_data_get!(ssh_get_tx_data, SSHTransaction);
437
export_state_data_get!(ssh_get_state_data, SSHState);
438

439
extern "C" fn ssh_state_new(
203✔
440
    _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
203✔
441
) -> *mut std::os::raw::c_void {
203✔
442
    let state = SSHState::new();
203✔
443
    let boxed = Box::new(state);
203✔
444
    return Box::into_raw(boxed) as *mut _;
203✔
445
}
203✔
446

447
unsafe extern "C" fn ssh_state_free(state: *mut std::os::raw::c_void) {
203✔
448
    std::mem::drop(Box::from_raw(state as *mut SSHState));
203✔
449
}
203✔
450

451
extern "C" fn ssh_state_tx_free(_state: *mut std::os::raw::c_void, _tx_id: u64) {
33✔
452
    //do nothing
33✔
453
}
33✔
454

455
unsafe extern "C" fn ssh_parse_request(
567✔
456
    flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
567✔
457
    stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
567✔
458
) -> AppLayerResult {
567✔
459
    let state = &mut cast_pointer!(state, SSHState);
567✔
460
    let buf = stream_slice.as_slice();
567✔
461
    let hdr = &mut state.transaction.cli_hdr;
567✔
462
    state.transaction.tx_data.0.updated_ts = true;
567✔
463
    if hdr.flags < SSHConnectionState::SshStateBannerDone {
567✔
464
        return state.parse_banner(buf, false, pstate, flow, &stream_slice);
189✔
465
    } else {
466
        return state.parse_record(buf, false, pstate, flow, &stream_slice);
378✔
467
    }
468
}
567✔
469

470
unsafe extern "C" fn ssh_parse_response(
671✔
471
    flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
671✔
472
    stream_slice: StreamSlice, _data: *mut std::os::raw::c_void,
671✔
473
) -> AppLayerResult {
671✔
474
    let state = &mut cast_pointer!(state, SSHState);
671✔
475
    let buf = stream_slice.as_slice();
671✔
476
    let hdr = &mut state.transaction.srv_hdr;
671✔
477
    state.transaction.tx_data.0.updated_tc = true;
671✔
478
    if hdr.flags < SSHConnectionState::SshStateBannerDone {
671✔
479
        return state.parse_banner(buf, true, pstate, flow, &stream_slice);
192✔
480
    } else {
481
        return state.parse_record(buf, true, pstate, flow, &stream_slice);
479✔
482
    }
483
}
671✔
484

485
#[no_mangle]
486
pub unsafe extern "C" fn SCSshStateGetTx(
7,154✔
487
    state: *mut std::os::raw::c_void, _tx_id: u64,
7,154✔
488
) -> *mut std::os::raw::c_void {
7,154✔
489
    let state = cast_pointer!(state, SSHState);
7,154✔
490
    return &state.transaction as *const _ as *mut _;
7,154✔
491
}
7,154✔
492

493
extern "C" fn ssh_state_get_tx_count(_state: *mut std::os::raw::c_void) -> u64 {
11,280✔
494
    return 1;
11,280✔
495
}
11,280✔
496

497
#[no_mangle]
498
pub unsafe extern "C" fn SCSshTxGetFlags(
14✔
499
    tx: *mut std::os::raw::c_void, direction: u8,
14✔
500
) -> SSHConnectionState {
14✔
501
    let tx = cast_pointer!(tx, SSHTransaction);
14✔
502
    if direction == u8::from(Direction::ToServer) {
14✔
503
        return tx.cli_hdr.flags;
7✔
504
    } else {
505
        return tx.srv_hdr.flags;
7✔
506
    }
507
}
14✔
508

509
#[no_mangle]
510
pub unsafe extern "C" fn SCSshTxGetAlStateProgress(
9,111✔
511
    tx: *mut std::os::raw::c_void, direction: u8,
9,111✔
512
) -> std::os::raw::c_int {
9,111✔
513
    let tx = cast_pointer!(tx, SSHTransaction);
9,111✔
514

9,111✔
515
    if tx.cli_hdr.flags >= SSHConnectionState::SshStateFinished
9,111✔
516
        && tx.srv_hdr.flags >= SSHConnectionState::SshStateFinished
2,854✔
517
    {
518
        return SSHConnectionState::SshStateFinished as i32;
324✔
519
    }
8,787✔
520

8,787✔
521
    if direction == u8::from(Direction::ToServer) {
8,787✔
522
        if tx.cli_hdr.flags >= SSHConnectionState::SshStateBannerDone {
3,628✔
523
            return SSHConnectionState::SshStateBannerDone as i32;
3,214✔
524
        }
414✔
525
    } else if tx.srv_hdr.flags >= SSHConnectionState::SshStateBannerDone {
5,159✔
526
        return SSHConnectionState::SshStateBannerDone as i32;
4,520✔
527
    }
639✔
528

529
    return SSHConnectionState::SshStateInProgress as i32;
1,053✔
530
}
9,111✔
531

532
// Parser name as a C style string.
533
const PARSER_NAME: &[u8] = b"ssh\0";
534

535
#[no_mangle]
536
pub unsafe extern "C" fn SCRegisterSshParser() {
17✔
537
    let parser = RustParser {
17✔
538
        name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
17✔
539
        default_port: std::ptr::null(),
17✔
540
        ipproto: IPPROTO_TCP,
17✔
541
        //simple patterns, no probing
17✔
542
        probe_ts: None,
17✔
543
        probe_tc: None,
17✔
544
        min_depth: 0,
17✔
545
        max_depth: 0,
17✔
546
        state_new: ssh_state_new,
17✔
547
        state_free: ssh_state_free,
17✔
548
        tx_free: ssh_state_tx_free,
17✔
549
        parse_ts: ssh_parse_request,
17✔
550
        parse_tc: ssh_parse_response,
17✔
551
        get_tx_count: ssh_state_get_tx_count,
17✔
552
        get_tx: SCSshStateGetTx,
17✔
553
        tx_comp_st_ts: SSHConnectionState::SshStateFinished as i32,
17✔
554
        tx_comp_st_tc: SSHConnectionState::SshStateFinished as i32,
17✔
555
        tx_get_progress: SCSshTxGetAlStateProgress,
17✔
556
        get_eventinfo: Some(SSHEvent::get_event_info),
17✔
557
        get_eventinfo_byid: Some(SSHEvent::get_event_info_by_id),
17✔
558
        localstorage_new: None,
17✔
559
        localstorage_free: None,
17✔
560
        get_tx_files: None,
17✔
561
        get_tx_iterator: None,
17✔
562
        get_tx_data: ssh_get_tx_data,
17✔
563
        get_state_data: ssh_get_state_data,
17✔
564
        apply_tx_config: None,
17✔
565
        flags: 0,
17✔
566
        get_frame_id_by_name: Some(SshFrameType::ffi_id_from_name),
17✔
567
        get_frame_name_by_id: Some(SshFrameType::ffi_name_from_id),
17✔
568
        get_state_id_by_name: Some(SSHConnectionState::ffi_id_from_name),
17✔
569
        get_state_name_by_id: Some(SSHConnectionState::ffi_name_from_id),
17✔
570
    };
17✔
571

17✔
572
    let ip_proto_str = CString::new("tcp").unwrap();
17✔
573

17✔
574
    if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
17✔
575
        let alproto = applayer_register_protocol_detection(&parser, 1);
17✔
576
        ALPROTO_SSH = alproto;
17✔
577
        if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
17✔
578
            let _ = AppLayerRegisterParser(&parser, alproto);
17✔
579
        }
17✔
580
        SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SSH);
17✔
581
        SCLogDebug!("Rust ssh parser registered.");
582
    } else {
583
        SCLogNotice!("Protocol detector and parser disabled for SSH.");
×
584
    }
585
}
17✔
586

587
#[no_mangle]
588
pub extern "C" fn SCSshEnableHassh() {
19,706✔
589
    if !HASSH_DISABLED.load(Ordering::Relaxed) {
19,706✔
590
        HASSH_ENABLED.store(true, Ordering::Relaxed)
19,706✔
591
    }
×
592
}
19,706✔
593

594
#[no_mangle]
595
pub extern "C" fn SCSshHasshIsEnabled() -> bool {
21,423✔
596
    hassh_is_enabled()
21,423✔
597
}
21,423✔
598

599
#[no_mangle]
600
pub extern "C" fn SCSshDisableHassh() {
×
601
    HASSH_DISABLED.store(true, Ordering::Relaxed)
×
602
}
×
603

604
#[no_mangle]
605
pub extern "C" fn SCSshEnableBypass(mode: EncryptionHandling) {
×
606
    unsafe {
×
607
        ENCRYPTION_BYPASS_ENABLED = mode;
×
608
    }
×
609
}
×
610

611
#[no_mangle]
612
pub unsafe extern "C" fn SCSshTxGetLogCondition(tx: *mut std::os::raw::c_void) -> bool {
1,720✔
613
    let tx = cast_pointer!(tx, SSHTransaction);
1,720✔
614

1,720✔
615
    if SCSshHasshIsEnabled() {
1,720✔
616
        if tx.cli_hdr.flags == SSHConnectionState::SshStateFinished
1,669✔
617
            && tx.srv_hdr.flags == SSHConnectionState::SshStateFinished
566✔
618
        {
619
            return true;
×
620
        }
1,669✔
621
    } else if tx.cli_hdr.flags == SSHConnectionState::SshStateBannerDone
51✔
622
        && tx.srv_hdr.flags == SSHConnectionState::SshStateBannerDone
39✔
623
    {
624
        return true;
17✔
625
    }
34✔
626
    return false;
1,703✔
627
}
1,720✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc