Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/snmp/snmp.rs
Line
Count
Source
1
/* Copyright (C) 2017-2021 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
// written by Pierre Chifflier  <chifflier@wzdftpd.net>
19
20
use crate::snmp::snmp_parser::*;
21
use crate::core::{self, *};
22
use crate::applayer::{self, *};
23
use std;
24
use std::ffi::CString;
25
26
use asn1_rs::Oid;
27
use der_parser::ber::BerObjectContent;
28
use der_parser::der::parse_der_sequence;
29
use nom7::{Err, IResult};
30
use nom7::error::{ErrorKind, make_error};
31
32
#[derive(AppLayerEvent)]
33
pub enum SNMPEvent {
34
    MalformedData,
35
    UnknownSecurityModel,
36
    VersionMismatch,
37
}
38
39
#[derive(Default)]
40
pub struct SNMPState<'a> {
41
    state_data: AppLayerStateData,
42
43
    /// SNMP protocol version
44
    pub version: u32,
45
46
    /// List of transactions for this session
47
    transactions: Vec<SNMPTransaction<'a>>,
48
49
    /// tx counter for assigning incrementing id's to tx's
50
    tx_id: u64,
51
}
52
53
pub struct SNMPPduInfo<'a> {
54
    pub pdu_type: PduType,
55
56
    pub err: ErrorStatus,
57
58
    pub trap_type: Option<(TrapType,Oid<'a>,NetworkAddress)>,
59
60
    pub vars: Vec<Oid<'a>>,
61
}
62
63
pub struct SNMPTransaction<'a> {
64
    /// PDU version
65
    pub version: u32,
66
67
    /// PDU info, if present (and cleartext)
68
    pub info: Option<SNMPPduInfo<'a>>,
69
70
    /// Community, if present (SNMPv2)
71
    pub community: Option<String>,
72
73
    /// USM info, if present (SNMPv3)
74
    pub usm: Option<String>,
75
76
    /// True if transaction was encrypted
77
    pub encrypted: bool,
78
79
    /// The internal transaction id
80
    id: u64,
81
82
    tx_data: applayer::AppLayerTxData,
83
}
84
85
impl Transaction for SNMPTransaction<'_> {
86
303k
    fn id(&self) -> u64 {
87
303k
        self.id
88
303k
    }
89
}
90
91
impl<'a> SNMPState<'a> {
92
5.88k
    pub fn new() -> SNMPState<'a> {
93
5.88k
        Default::default()
94
5.88k
    }
95
}
96
97
impl<'a> Default for SNMPPduInfo<'a> {
98
137k
    fn default() -> SNMPPduInfo<'a> {
99
137k
        SNMPPduInfo{
100
137k
            pdu_type: PduType(0),
101
137k
            err: ErrorStatus::NoError,
102
137k
            trap_type: None,
103
137k
            vars: Vec::new()
104
137k
        }
105
137k
    }
106
}
107
108
impl<'a> State<SNMPTransaction<'a>> for SNMPState<'a> {
109
179k
    fn get_transaction_count(&self) -> usize {
110
179k
        self.transactions.len()
111
179k
    }
112
113
151k
    fn get_transaction_by_index(&self, index: usize) -> Option<&SNMPTransaction<'a>> {
114
151k
        self.transactions.get(index)
115
151k
    }
116
}
117
118
impl<'a> SNMPState<'a> {
119
137k
    fn add_pdu_info(&mut self, pdu: &SnmpPdu<'a>, tx: &mut SNMPTransaction<'a>) {
120
137k
        let mut pdu_info = SNMPPduInfo {
121
137k
            pdu_type: pdu.pdu_type(),
122
137k
            ..Default::default()
123
137k
        };
124
137k
        match *pdu {
125
129k
            SnmpPdu::Generic(ref pdu) => {
126
129k
                pdu_info.err = pdu.err;
127
129k
            },
128
7.31k
            SnmpPdu::Bulk(_) => {
129
7.31k
            },
130
928
            SnmpPdu::TrapV1(ref t)    => {
131
928
                pdu_info.trap_type = Some((t.generic_trap,t.enterprise.clone(),t.agent_addr));
132
928
            }
133
        }
134
135
137k
        for var in pdu.vars_iter() {
136
24.3k
            pdu_info.vars.push(var.oid.to_owned());
137
24.3k
        }
138
137k
        tx.info = Some(pdu_info);
139
137k
    }
140
141
129k
    fn handle_snmp_v12(&mut self, msg: SnmpMessage<'a>, _direction: Direction) -> i32 {
142
129k
        let mut tx = self.new_tx(_direction);
143
        // in the message, version is encoded as 0 (version 1) or 1 (version 2)
144
129k
        if self.version != msg.version + 1 {
145
128k
            SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version+1);
146
128k
            self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch);
147
128k
        }
148
129k
        self.add_pdu_info(&msg.pdu, &mut tx);
149
129k
        tx.community = Some(msg.community);
150
129k
        self.transactions.push(tx);
151
129k
        0
152
129k
    }
153
154
11.0k
    fn handle_snmp_v3(&mut self, msg: SnmpV3Message<'a>, _direction: Direction) -> i32 {
155
11.0k
        let mut tx = self.new_tx(_direction);
156
11.0k
        if self.version != msg.version {
157
2.99k
            SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version);
158
2.99k
            self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch);
159
8.03k
        }
160
11.0k
        match msg.data {
161
7.94k
            ScopedPduData::Plaintext(pdu) => {
162
7.94k
                self.add_pdu_info(&pdu.data, &mut tx);
163
7.94k
            },
164
3.07k
            _                             => {
165
3.07k
                tx.encrypted = true;
166
3.07k
            }
167
        }
168
11.0k
        match msg.security_params {
169
7.58k
            SecurityParameters::USM(usm) => {
170
7.58k
                tx.usm = Some(usm.msg_user_name);
171
7.58k
            },
172
3.44k
            _                            => {
173
3.44k
                self.set_event_tx(&mut tx, SNMPEvent::UnknownSecurityModel);
174
3.44k
            }
175
        }
176
11.0k
        self.transactions.push(tx);
177
11.0k
        0
178
11.0k
    }
179
180
    /// Parse an SNMP request message
181
    ///
182
    /// Returns 0 if successful, or -1 on error
183
156k
    fn parse(&mut self, i: &'a [u8], direction: Direction) -> i32 {
184
156k
        if self.version == 0 {
185
136k
            if let Ok((_, x)) = parse_pdu_envelope_version(i) {
186
691
                self.version = x;
187
135k
            }
188
20.0k
        }
189
156k
        match parse_snmp_generic_message(i) {
190
112k
            Ok((_rem,SnmpGenericMessage::V1(msg))) |
191
129k
            Ok((_rem,SnmpGenericMessage::V2(msg))) => self.handle_snmp_v12(msg, direction),
192
11.0k
            Ok((_rem,SnmpGenericMessage::V3(msg))) => self.handle_snmp_v3(msg, direction),
193
15.6k
            Err(_e) => {
194
                SCLogDebug!("parse_snmp failed: {:?}", _e);
195
15.6k
                self.set_event(SNMPEvent::MalformedData);
196
15.6k
                -1
197
            },
198
        }
199
156k
    }
200
201
5.88k
    fn free(&mut self) {
202
        // All transactions are freed when the `transactions` object is freed.
203
        // But let's be explicit
204
5.88k
        self.transactions.clear();
205
5.88k
    }
206
207
140k
    fn new_tx(&mut self, direction: Direction) -> SNMPTransaction<'a> {
208
140k
        self.tx_id += 1;
209
140k
        SNMPTransaction::new(direction, self.version, self.tx_id)
210
140k
    }
211
212
32
    fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SNMPTransaction<'_>> {
213
32
        self.transactions.iter().rev().find(|&tx| tx.id == tx_id + 1)
214
32
    }
215
216
140k
    fn free_tx(&mut self, tx_id: u64) {
217
140k
        let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1);
218
140k
        debug_assert!(tx.is_some());
219
140k
        if let Some(idx) = tx {
220
140k
            let _ = self.transactions.remove(idx);
221
140k
        }
222
140k
    }
223
224
    /// Set an event. The event is set on the most recent transaction.
225
15.6k
    fn set_event(&mut self, event: SNMPEvent) {
226
15.6k
        if let Some(tx) = self.transactions.last_mut() {
227
0
            tx.tx_data.set_event(event as u8);
228
15.6k
        }
229
15.6k
    }
230
231
    /// Set an event on a specific transaction.
232
134k
    fn set_event_tx(&self, tx: &mut SNMPTransaction, event: SNMPEvent) {
233
134k
        tx.tx_data.set_event(event as u8);
234
134k
    }
235
}
236
237
impl<'a> SNMPTransaction<'a> {
238
140k
    pub fn new(direction: Direction, version: u32, id: u64) -> SNMPTransaction<'a> {
239
140k
        SNMPTransaction {
240
140k
            version,
241
140k
            info: None,
242
140k
            community: None,
243
140k
            usm: None,
244
140k
            encrypted: false,
245
140k
            id,
246
140k
            tx_data: applayer::AppLayerTxData::for_direction(direction),
247
140k
        }
248
140k
    }
249
}
250
251
/// Returns *mut SNMPState
252
#[no_mangle]
253
5.88k
pub extern "C" fn rs_snmp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void {
254
5.88k
    let state = SNMPState::new();
255
5.88k
    let boxed = Box::new(state);
256
5.88k
    return Box::into_raw(boxed) as *mut _;
257
5.88k
}
258
259
/// Params:
260
/// - state: *mut SNMPState as void pointer
261
#[no_mangle]
262
5.88k
pub extern "C" fn rs_snmp_state_free(state: *mut std::os::raw::c_void) {
263
5.88k
    let mut snmp_state = unsafe{ Box::from_raw(state as *mut SNMPState) };
264
5.88k
    snmp_state.free();
265
5.88k
}
266
267
#[no_mangle]
268
83.7k
pub unsafe extern "C" fn rs_snmp_parse_request(_flow: *const core::Flow,
269
83.7k
                                       state: *mut std::os::raw::c_void,
270
83.7k
                                       _pstate: *mut std::os::raw::c_void,
271
83.7k
                                       stream_slice: StreamSlice,
272
83.7k
                                       _data: *const std::os::raw::c_void,
273
83.7k
                                       ) -> AppLayerResult {
274
83.7k
    let state = cast_pointer!(state,SNMPState);
275
83.7k
    state.parse(stream_slice.as_slice(), Direction::ToServer).into()
276
83.7k
}
277
278
#[no_mangle]
279
72.6k
pub unsafe extern "C" fn rs_snmp_parse_response(_flow: *const core::Flow,
280
72.6k
                                       state: *mut std::os::raw::c_void,
281
72.6k
                                       _pstate: *mut std::os::raw::c_void,
282
72.6k
                                       stream_slice: StreamSlice,
283
72.6k
                                       _data: *const std::os::raw::c_void,
284
72.6k
                                       ) -> AppLayerResult {
285
72.6k
    let state = cast_pointer!(state,SNMPState);
286
72.6k
    state.parse(stream_slice.as_slice(), Direction::ToClient).into()
287
72.6k
}
288
289
#[no_mangle]
290
32
pub unsafe extern "C" fn rs_snmp_state_get_tx(state: *mut std::os::raw::c_void,
291
32
                                      tx_id: u64)
292
32
                                      -> *mut std::os::raw::c_void
293
{
294
32
    let state = cast_pointer!(state,SNMPState);
295
32
    match state.get_tx_by_id(tx_id) {
296
28
        Some(tx) => tx as *const _ as *mut _,
297
4
        None     => std::ptr::null_mut(),
298
    }
299
32
}
300
301
#[no_mangle]
302
476k
pub unsafe extern "C" fn rs_snmp_state_get_tx_count(state: *mut std::os::raw::c_void)
303
476k
                                            -> u64
304
{
305
476k
    let state = cast_pointer!(state,SNMPState);
306
476k
    state.tx_id
307
476k
}
308
309
#[no_mangle]
310
140k
pub unsafe extern "C" fn rs_snmp_state_tx_free(state: *mut std::os::raw::c_void,
311
140k
                                       tx_id: u64)
312
{
313
140k
    let state = cast_pointer!(state,SNMPState);
314
140k
    state.free_tx(tx_id);
315
140k
}
316
317
#[no_mangle]
318
300k
pub extern "C" fn rs_snmp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void,
319
300k
                                                 _direction: u8)
320
300k
                                                 -> std::os::raw::c_int
321
{
322
300k
    1
323
300k
}
324
325
static mut ALPROTO_SNMP : AppProto = ALPROTO_UNKNOWN;
326
327
// Read PDU sequence and extract version, if similar to SNMP definition
328
139k
fn parse_pdu_envelope_version(i:&[u8]) -> IResult<&[u8],u32> {
329
139k
    match parse_der_sequence(i) {
330
85.0k
        Ok((_,x))     => {
331
            #[allow(clippy::single_match)]
332
85.0k
            match x.content {
333
85.0k
                BerObjectContent::Sequence(ref v) => {
334
85.0k
                    if v.len() == 3 {
335
1.85k
                        match v[0].as_u32()  {
336
176
                            Ok(0) => { return Ok((i,1)); }, // possibly SNMPv1
337
465
                            Ok(1) => { return Ok((i,2)); }, // possibly SNMPv2c
338
1.21k
                            _     => ()
339
                        }
340
83.2k
                    } else if v.len() == 4 && v[0].as_u32() == Ok(3) {
341
807
                        return Ok((i,3)); // possibly SNMPv3
342
82.4k
                    }
343
                },
344
0
                _ => ()
345
            };
346
83.6k
            Err(Err::Error(make_error(i, ErrorKind::Verify)))
347
        },
348
490
        Err(Err::Incomplete(i)) => Err(Err::Incomplete(i)),
349
        Err(Err::Failure(_)) |
350
53.9k
        Err(Err::Error(_))      => Err(Err::Error(make_error(i,ErrorKind::Verify)))
351
    }
352
139k
}
353
354
#[no_mangle]
355
3.11k
pub unsafe extern "C" fn rs_snmp_probing_parser(_flow: *const Flow,
356
3.11k
                                         _direction: u8,
357
3.11k
                                         input:*const u8,
358
3.11k
                                         input_len: u32,
359
3.11k
                                         _rdir: *mut u8) -> AppProto {
360
3.11k
    if input.is_null() {
361
0
        return ALPROTO_UNKNOWN;
362
3.11k
    }
363
3.11k
    let slice = build_slice!(input,input_len as usize);
364
3.11k
    let alproto = ALPROTO_SNMP;
365
3.11k
    if slice.len() < 4 { return ALPROTO_UNKNOWN; }
366
3.11k
    match parse_pdu_envelope_version(slice) {
367
757
        Ok((_,_))               => alproto,
368
127
        Err(Err::Incomplete(_)) => ALPROTO_UNKNOWN,
369
2.23k
        _                       => ALPROTO_FAILED,
370
    }
371
3.11k
}
372
373
export_tx_data_get!(rs_snmp_get_tx_data, SNMPTransaction);
374
export_state_data_get!(rs_snmp_get_state_data, SNMPState);
375
376
const PARSER_NAME : &[u8] = b"snmp\0";
377
378
#[no_mangle]
379
34
pub unsafe extern "C" fn rs_register_snmp_parser() {
380
34
    let default_port = CString::new("161").unwrap();
381
34
    let mut parser = RustParser {
382
34
        name               : PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
383
34
        default_port       : default_port.as_ptr(),
384
34
        ipproto            : core::IPPROTO_UDP,
385
34
        probe_ts           : Some(rs_snmp_probing_parser),
386
34
        probe_tc           : Some(rs_snmp_probing_parser),
387
34
        min_depth          : 0,
388
34
        max_depth          : 16,
389
34
        state_new          : rs_snmp_state_new,
390
34
        state_free         : rs_snmp_state_free,
391
34
        tx_free            : rs_snmp_state_tx_free,
392
34
        parse_ts           : rs_snmp_parse_request,
393
34
        parse_tc           : rs_snmp_parse_response,
394
34
        get_tx_count       : rs_snmp_state_get_tx_count,
395
34
        get_tx             : rs_snmp_state_get_tx,
396
34
        tx_comp_st_ts      : 1,
397
34
        tx_comp_st_tc      : 1,
398
34
        tx_get_progress    : rs_snmp_tx_get_alstate_progress,
399
34
        get_eventinfo      : Some(SNMPEvent::get_event_info),
400
34
        get_eventinfo_byid : Some(SNMPEvent::get_event_info_by_id),
401
34
        localstorage_new   : None,
402
34
        localstorage_free  : None,
403
34
        get_tx_files       : None,
404
34
        get_tx_iterator    : Some(applayer::state_get_tx_iterator::<SNMPState, SNMPTransaction>),
405
34
        get_tx_data        : rs_snmp_get_tx_data,
406
34
        get_state_data     : rs_snmp_get_state_data,
407
34
        apply_tx_config    : None,
408
34
        flags              : 0,
409
34
        truncate           : None,
410
34
        get_frame_id_by_name: None,
411
34
        get_frame_name_by_id: None,
412
34
    };
413
34
    let ip_proto_str = CString::new("udp").unwrap();
414
34
    if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
415
        // port 161
416
34
        let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
417
        // store the allocated ID for the probe function
418
34
        ALPROTO_SNMP = alproto;
419
34
        if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
420
34
            let _ = AppLayerRegisterParser(&parser, alproto);
421
34
        }
422
        // port 162
423
34
        let default_port_traps = CString::new("162").unwrap();
424
34
        parser.default_port = default_port_traps.as_ptr();
425
34
        let _ = AppLayerRegisterProtocolDetection(&parser, 1);
426
34
        if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
427
34
            let _ = AppLayerRegisterParser(&parser, alproto);
428
34
        }
429
0
    } else {
430
0
        SCLogDebug!("Protocol detector and parser disabled for SNMP.");
431
0
    }
432
34
}