Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata/rust/src/smb/smb2.rs
Line
Count
Source
1
/* Copyright (C) 2017-2026 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 nom8::Err;
19
20
use crate::direction::Direction;
21
use crate::smb::dcerpc::*;
22
use crate::smb::events::*;
23
use crate::smb::files::*;
24
use crate::smb::smb::*;
25
use crate::smb::smb2_ioctl::*;
26
use crate::smb::smb2_records::*;
27
use crate::smb::smb2_session::*;
28
use crate::smb::smb_status::*;
29
30
pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0;
31
pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1;
32
pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2;
33
pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3;
34
pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4;
35
pub const SMB2_COMMAND_CREATE: u16 = 5;
36
pub const SMB2_COMMAND_CLOSE: u16 = 6;
37
pub const SMB2_COMMAND_FLUSH: u16 = 7;
38
pub const SMB2_COMMAND_READ: u16 = 8;
39
pub const SMB2_COMMAND_WRITE: u16 = 9;
40
pub const SMB2_COMMAND_LOCK: u16 = 10;
41
pub const SMB2_COMMAND_IOCTL: u16 = 11;
42
pub const SMB2_COMMAND_CANCEL: u16 = 12;
43
pub const SMB2_COMMAND_KEEPALIVE: u16 = 13;
44
pub const SMB2_COMMAND_FIND: u16 = 14;
45
pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15;
46
pub const SMB2_COMMAND_GET_INFO: u16 = 16;
47
pub const SMB2_COMMAND_SET_INFO: u16 = 17;
48
pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18;
49
50
71.7k
pub fn smb2_command_string(c: u16) -> String {
51
71.7k
    match c {
52
1.69k
        SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL",
53
2.52k
        SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP",
54
340
        SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF",
55
1.56k
        SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT",
56
782
        SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT",
57
20.8k
        SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE",
58
18.9k
        SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE",
59
2.91k
        SMB2_COMMAND_READ => "SMB2_COMMAND_READ",
60
238
        SMB2_COMMAND_FLUSH => "SMB2_COMMAND_FLUSH",
61
2.56k
        SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE",
62
0
        SMB2_COMMAND_LOCK => "SMB2_COMMAND_LOCK",
63
8.85k
        SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL",
64
0
        SMB2_COMMAND_CANCEL => "SMB2_COMMAND_CANCEL",
65
2.74k
        SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE",
66
6.50k
        SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND",
67
393
        SMB2_COMMAND_CHANGE_NOTIFY => "SMB2_COMMAND_CHANGE_NOTIFY",
68
0
        SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO",
69
274
        SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO",
70
491
        SMB2_COMMAND_OPLOCK_BREAK => "SMB2_COMMAND_OPLOCK_BREAK",
71
        _ => {
72
45
            return (c).to_string();
73
        }
74
    }
75
71.7k
    .to_string()
76
71.7k
}
77
78
138k
pub fn smb2_dialect_string(d: u16) -> String {
79
138k
    match d {
80
16.8k
        0x0202 => "2.02",
81
7.20k
        0x0210 => "2.10",
82
96
        0x0222 => "2.22",
83
153
        0x0224 => "2.24",
84
2.63k
        0x02ff => "2.??",
85
1.67k
        0x0300 => "3.00",
86
51.4k
        0x0302 => "3.02",
87
155
        0x0310 => "3.10",
88
1.44k
        0x0311 => "3.11",
89
        _ => {
90
56.8k
            return (d).to_string();
91
        }
92
    }
93
81.6k
    .to_string()
94
138k
}
95
96
// later we'll use this to determine if we need to
97
// track a ssn per type
98
115k
fn smb2_create_new_tx(cmd: u16) -> bool {
99
115k
    match cmd {
100
        SMB2_COMMAND_READ | SMB2_COMMAND_WRITE | SMB2_COMMAND_GET_INFO | SMB2_COMMAND_SET_INFO => {
101
72.3k
            false
102
        }
103
42.9k
        _ => true,
104
    }
105
115k
}
106
107
11.0k
fn smb2_read_response_record_generic(state: &mut SMBState, r: &Smb2Record) {
108
11.0k
    if smb2_create_new_tx(r.command) {
109
0
        let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
110
0
        let tx = state.get_generic_tx(2, r.command, &tx_hdr);
111
0
        if let Some(tx) = tx {
112
0
            tx.set_status(r.nt_status, false);
113
0
            tx.response_done = true;
114
0
        }
115
11.0k
    }
116
11.0k
}
117
118
11.0k
pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
119
11.0k
    smb2_read_response_record_generic(state, r);
120
121
11.0k
    match parse_smb2_response_read(r.data) {
122
10.1k
        Ok((_, rd)) => {
123
10.1k
            if rd.len - rd.data.len() as u32 > nbss_remaining {
124
                // Record claims more bytes than are in NBSS record...
125
1.49k
                state.set_event(SMBEvent::ReadResponseTooLarge);
126
                // Skip the remaining bytes of the record.
127
1.49k
                state.set_skip(Direction::ToClient, nbss_remaining);
128
1.49k
                return;
129
8.66k
            }
130
8.66k
            if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW {
131
3.41k
                SCLogDebug!("SMBv2/READ: incomplete record, expecting a follow up");
132
3.41k
                // fall through
133
5.24k
            } else if r.nt_status != SMB_NTSTATUS_SUCCESS {
134
                SCLogDebug!("SMBv2: read response error code received: skip record");
135
4
                state.set_skip(Direction::ToClient, nbss_remaining);
136
4
                return;
137
5.24k
            }
138
139
8.66k
            if (state.max_read_size != 0 && rd.len > state.max_read_size)
140
8.65k
                || (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.len })
141
            {
142
5
                state.set_event(SMBEvent::ReadResponseTooLarge);
143
5
                state.set_skip(Direction::ToClient, nbss_remaining);
144
5
                return;
145
8.65k
            }
146
147
            SCLogDebug!("SMBv2: read response => {:?}", rd);
148
149
            // get the request info. If we don't have it, there is nothing
150
            // we can do except skip this record.
151
8.65k
            let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET);
152
8.65k
            let (offset, file_guid) = match state.read_offset_cache.pop(&guid_key) {
153
7.38k
                Some(o) => (o.offset, o.guid),
154
                None => {
155
                    SCLogDebug!("SMBv2 READ response: reply to unknown request {:?}", rd);
156
1.27k
                    state.set_skip(Direction::ToClient, nbss_remaining);
157
1.27k
                    return;
158
                }
159
            };
160
            SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset);
161
162
7.38k
            let mut set_event_fileoverlap = false;
163
            // set_file_left must not claim bytes a skip already consumed
164
7.38k
            let mut rejected = false;
165
            // look up existing tracker and if we have it update it
166
7.38k
            let found = if let Some(tx) =
167
7.38k
                state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToClient)
168
            {
169
1.60k
                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
170
1.60k
                    let file_id: u32 = tx.id as u32;
171
1.60k
                    if offset < tdf.file_tracker.tracked {
172
695
                        set_event_fileoverlap = true;
173
908
                    }
174
0
                    if let Some(ev) =
175
1.60k
                        smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true)
176
0
                    {
177
0
                        // Attach to the affected file tx, not the newest tx.
178
0
                        tx.set_event(ev);
179
0
                        tx.tx_data.0.updated_ts = true;
180
0
                        tx.tx_data.0.updated_tc = true;
181
0
                        state.set_skip(Direction::ToClient, nbss_remaining);
182
0
                        rejected = true;
183
1.60k
                    } else {
184
1.60k
                        filetracker_newchunk(
185
1.60k
                            &mut tdf.file_tracker,
186
1.60k
                            &tdf.file_name,
187
1.60k
                            rd.data,
188
1.60k
                            offset,
189
1.60k
                            rd.len,
190
1.60k
                            false,
191
1.60k
                            &file_id,
192
1.60k
                        );
193
1.60k
                    }
194
0
                }
195
1.60k
                true
196
            } else {
197
5.77k
                false
198
            };
199
            SCLogDebug!("existing file tx? {}", found);
200
7.38k
            if !found {
201
5.77k
                let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
202
5.77k
                let (share_name, mut is_pipe) = match state.ssn2tree_cache.get(&tree_key) {
203
4.66k
                    Some(n) => (n.name.to_vec(), n.is_pipe),
204
1.11k
                    _ => (Vec::new(), false),
205
                };
206
5.77k
                let mut is_dcerpc = if is_pipe || share_name.is_empty() {
207
4.42k
                    state.get_service_for_guid(&file_guid).1
208
                } else {
209
1.35k
                    false
210
                };
211
                SCLogDebug!(
212
                    "SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}",
213
                    share_name,
214
                    is_pipe,
215
                    is_dcerpc
216
                );
217
218
5.77k
                if share_name.is_empty() && !is_pipe {
219
                    SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe");
220
221
1.12k
                    if smb_dcerpc_probe(rd.data) {
222
                        SCLogDebug!("SMBv2/READ: looks like dcerpc");
223
                        // insert fake tree to assist in follow up lookups
224
444
                        let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true);
225
444
                        state.ssn2tree_cache.put(tree_key, tree);
226
444
                        if !is_dcerpc {
227
384
                            _ = state
228
384
                                .guid2name_cache
229
384
                                .put(file_guid.to_vec(), b"suricata::dcerpc".to_vec());
230
384
                        }
231
444
                        is_pipe = true;
232
444
                        is_dcerpc = true;
233
681
                    } else {
234
681
                        SCLogDebug!("SMBv2/READ: not DCERPC");
235
681
                    }
236
4.65k
                }
237
238
5.77k
                if is_pipe && is_dcerpc {
239
3.49k
                    SCLogDebug!("SMBv2 DCERPC read");
240
3.49k
                    let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
241
3.49k
                    let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_READ, r.nt_status);
242
3.49k
                    smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data);
243
3.49k
                    rejected = true;
244
3.49k
                } else if is_pipe {
245
244
                    SCLogDebug!("non-DCERPC pipe");
246
244
                    state.set_skip(Direction::ToClient, nbss_remaining);
247
244
                    rejected = true;
248
244
                } else {
249
2.03k
                    let file_name = match state.guid2name_cache.get(&file_guid) {
250
1.25k
                        Some(n) => n.to_vec(),
251
781
                        None => b"<unknown>".to_vec(),
252
                    };
253
254
2.03k
                    let Some(tx) = state.new_file_tx(&file_guid, &file_name, Direction::ToClient)
255
                    else {
256
0
                        return;
257
                    };
258
2.03k
                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ);
259
2.03k
                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, r.session_id, r.tree_id, 0); // TODO move into new_file_tx
260
2.03k
                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
261
2.03k
                        tdf.share_name = share_name;
262
2.03k
                        let file_id: u32 = tx.id as u32;
263
2.03k
                        if offset < tdf.file_tracker.tracked {
264
0
                            set_event_fileoverlap = true;
265
2.03k
                        }
266
0
                        if let Some(ev) =
267
2.03k
                            smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true)
268
0
                        {
269
0
                            // tx just created: set the event on it directly.
270
0
                            tx.set_event(ev);
271
0
                            tx.tx_data.0.updated_ts = true;
272
0
                            tx.tx_data.0.updated_tc = true;
273
0
                            // Chunk rejected before the file was opened: complete + close the
274
0
                            // tx and zero files_opened, else it dangles until teardown.
275
0
                            tx.request_done = true;
276
0
                            tx.response_done = true;
277
0
                            tx.tx_data.0.files_opened = 0;
278
0
                            state.set_skip(Direction::ToClient, nbss_remaining);
279
0
                            rejected = true;
280
2.03k
                        } else {
281
2.03k
                            filetracker_newchunk(
282
2.03k
                                &mut tdf.file_tracker,
283
2.03k
                                &file_name,
284
2.03k
                                rd.data,
285
2.03k
                                offset,
286
2.03k
                                rd.len,
287
2.03k
                                false,
288
2.03k
                                &file_id,
289
2.03k
                            );
290
2.03k
                        }
291
0
                    }
292
                }
293
1.60k
            }
294
295
7.38k
            if set_event_fileoverlap {
296
695
                state.set_event(SMBEvent::FileOverlap);
297
6.68k
            }
298
7.38k
            if !rejected {
299
3.63k
                state.set_file_left(
300
3.63k
                    Direction::ToClient,
301
3.63k
                    rd.len,
302
3.63k
                    rd.data.len() as u32,
303
3.63k
                    file_guid.to_vec(),
304
3.63k
                );
305
3.74k
            }
306
        }
307
877
        _ => {
308
877
            SCLogDebug!("SMBv2: failed to parse read response");
309
877
            state.set_event(SMBEvent::MalformedData);
310
877
        }
311
    }
312
11.0k
}
313
314
35.4k
pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
315
    SCLogDebug!("SMBv2/WRITE: request record");
316
35.4k
    if smb2_create_new_tx(r.command) {
317
0
        let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
318
0
        let Some(tx) = state.new_generic_tx(2, r.command, tx_key) else {
319
0
            return;
320
        };
321
0
        tx.request_done = true;
322
35.4k
    }
323
35.4k
    match parse_smb2_request_write(r.data) {
324
34.0k
        Ok((_, wr)) => {
325
34.0k
            if wr.wr_len - wr.data.len() as u32 > nbss_remaining {
326
                // Record claims more bytes than are in NBSS record...
327
5.10k
                state.set_event(SMBEvent::WriteRequestTooLarge);
328
                // Skip the remaining bytes of the record.
329
5.10k
                state.set_skip(Direction::ToServer, nbss_remaining);
330
5.10k
                return;
331
28.9k
            }
332
28.9k
            if (state.max_write_size != 0 && wr.wr_len > state.max_write_size)
333
28.5k
                || (unsafe { SMB_CFG_MAX_WRITE_SIZE != 0 && SMB_CFG_MAX_WRITE_SIZE < wr.wr_len })
334
            {
335
373
                state.set_event(SMBEvent::WriteRequestTooLarge);
336
373
                state.set_skip(Direction::ToServer, nbss_remaining);
337
373
                return;
338
28.5k
            }
339
340
            /* update key-guid map */
341
28.5k
            let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID);
342
28.5k
            state.ssn2vec_cache.put(guid_key, wr.guid.to_vec());
343
344
28.5k
            let file_guid = wr.guid.to_vec();
345
28.5k
            let file_name = match state.guid2name_cache.get(&file_guid) {
346
16.3k
                Some(n) => n.to_vec(),
347
12.2k
                None => Vec::new(),
348
            };
349
350
28.5k
            let mut set_event_fileoverlap = false;
351
            // set_file_left must not claim bytes a skip already consumed
352
28.5k
            let mut rejected = false;
353
28.5k
            let found = if let Some(tx) =
354
28.5k
                state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToServer)
355
            {
356
7.37k
                if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
357
7.37k
                    let file_id: u32 = tx.id as u32;
358
7.37k
                    if wr.wr_offset < tdf.file_tracker.tracked {
359
480
                        set_event_fileoverlap = true;
360
6.89k
                    }
361
7.37k
                    if let Some(ev) = smb_queue_limit_event(
362
7.37k
                        &tdf.file_tracker,
363
7.37k
                        wr.wr_offset,
364
7.37k
                        wr.wr_len as u64,
365
7.37k
                        false,
366
7.37k
                    ) {
367
985
                        // Attach to the affected file tx, not the newest tx.
368
985
                        tx.set_event(ev);
369
985
                        tx.tx_data.0.updated_ts = true;
370
985
                        tx.tx_data.0.updated_tc = true;
371
985
                        state.set_skip(Direction::ToServer, nbss_remaining);
372
985
                        rejected = true;
373
6.39k
                    } else {
374
6.39k
                        filetracker_newchunk(
375
6.39k
                            &mut tdf.file_tracker,
376
6.39k
                            &file_name,
377
6.39k
                            wr.data,
378
6.39k
                            wr.wr_offset,
379
6.39k
                            wr.wr_len,
380
6.39k
                            false,
381
6.39k
                            &file_id,
382
6.39k
                        );
383
6.39k
                    }
384
0
                }
385
7.37k
                true
386
            } else {
387
21.2k
                false
388
            };
389
28.5k
            if !found {
390
21.2k
                let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
391
21.2k
                let (share_name, mut is_pipe) = match state.ssn2tree_cache.get(&tree_key) {
392
17.1k
                    Some(n) => (n.name.to_vec(), n.is_pipe),
393
4.09k
                    _ => (Vec::new(), false),
394
                };
395
21.2k
                let mut is_dcerpc = if is_pipe || share_name.is_empty() {
396
20.2k
                    state.get_service_for_guid(wr.guid).1
397
                } else {
398
971
                    false
399
                };
400
                SCLogDebug!(
401
                    "SMBv2/WRITE: share_name {:?} is_pipe {} is_dcerpc {}",
402
                    share_name,
403
                    is_pipe,
404
                    is_dcerpc
405
                );
406
407
                // if we missed the TREE connect we can't be sure if 'is_dcerpc' is correct
408
21.2k
                if share_name.is_empty() && !is_pipe {
409
                    SCLogDebug!(
410
                        "SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe"
411
                    );
412
413
4.13k
                    if smb_dcerpc_probe(wr.data) {
414
                        SCLogDebug!("SMBv2/WRITE: looks like we have dcerpc");
415
416
776
                        let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true);
417
776
                        state.ssn2tree_cache.put(tree_key, tree);
418
776
                        if !is_dcerpc {
419
627
                            _ = state
420
627
                                .guid2name_cache
421
627
                                .put(file_guid.to_vec(), b"suricata::dcerpc".to_vec());
422
627
                        }
423
776
                        is_pipe = true;
424
776
                        is_dcerpc = true;
425
3.35k
                    } else {
426
3.35k
                        SCLogDebug!("SMBv2/WRITE: not DCERPC");
427
3.35k
                    }
428
17.0k
                }
429
21.2k
                if is_pipe && is_dcerpc {
430
15.7k
                    SCLogDebug!("SMBv2 DCERPC write");
431
15.7k
                    let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
432
15.7k
                    let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE);
433
15.7k
                    smb_write_dcerpc_record(state, vercmd, hdr, wr.data);
434
15.7k
                    rejected = true;
435
15.7k
                } else if is_pipe {
436
1.10k
                    SCLogDebug!("non-DCERPC pipe: skip rest of the record");
437
1.10k
                    state.set_skip(Direction::ToServer, nbss_remaining);
438
1.10k
                    rejected = true;
439
1.10k
                } else {
440
4.33k
                    let Some(tx) = state.new_file_tx(&file_guid, &file_name, Direction::ToServer)
441
                    else {
442
0
                        return;
443
                    };
444
4.33k
                    tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE);
445
4.33k
                    tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, r.session_id, r.tree_id, 0); // TODO move into new_file_tx
446
4.33k
                    if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
447
4.33k
                        let file_id: u32 = tx.id as u32;
448
4.33k
                        if wr.wr_offset < tdf.file_tracker.tracked {
449
0
                            set_event_fileoverlap = true;
450
4.33k
                        }
451
452
4.33k
                        if let Some(ev) = smb_queue_limit_event(
453
4.33k
                            &tdf.file_tracker,
454
4.33k
                            wr.wr_offset,
455
4.33k
                            wr.wr_len as u64,
456
4.33k
                            false,
457
4.33k
                        ) {
458
0
                            // tx just created: set the event on it directly.
459
0
                            tx.set_event(ev);
460
0
                            tx.tx_data.0.updated_ts = true;
461
0
                            tx.tx_data.0.updated_tc = true;
462
0
                            // Chunk rejected before the file was opened: complete + close the
463
0
                            // tx and zero files_opened, else it dangles until teardown.
464
0
                            tx.request_done = true;
465
0
                            tx.response_done = true;
466
0
                            tx.tx_data.0.files_opened = 0;
467
0
                            state.set_skip(Direction::ToServer, nbss_remaining);
468
0
                            rejected = true;
469
4.33k
                        } else {
470
4.33k
                            filetracker_newchunk(
471
4.33k
                                &mut tdf.file_tracker,
472
4.33k
                                &file_name,
473
4.33k
                                wr.data,
474
4.33k
                                wr.wr_offset,
475
4.33k
                                wr.wr_len,
476
4.33k
                                false,
477
4.33k
                                &file_id,
478
4.33k
                            );
479
4.33k
                        }
480
0
                    }
481
                }
482
7.37k
            }
483
484
28.5k
            if set_event_fileoverlap {
485
480
                state.set_event(SMBEvent::FileOverlap);
486
28.1k
            }
487
28.5k
            if !rejected {
488
10.7k
                state.set_file_left(
489
10.7k
                    Direction::ToServer,
490
10.7k
                    wr.wr_len,
491
10.7k
                    wr.data.len() as u32,
492
10.7k
                    file_guid.to_vec(),
493
10.7k
                );
494
17.8k
            }
495
        }
496
1.38k
        _ => {
497
1.38k
            state.set_event(SMBEvent::MalformedData);
498
1.38k
        }
499
    }
500
35.4k
}
501
502
201k
pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) {
503
    SCLogDebug!(
504
        "SMBv2 request record, command {} tree {} session {}",
505
        &smb2_command_string(r.command),
506
        r.tree_id,
507
        r.session_id
508
    );
509
510
201k
    let mut events: Vec<SMBEvent> = Vec::new();
511
512
201k
    let have_tx = match r.command {
513
        SMB2_COMMAND_SET_INFO => {
514
            SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", r);
515
3.75k
            let have_si_tx = match parse_smb2_request_setinfo(r.data) {
516
2.26k
                Ok((_, rd)) => {
517
                    SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", rd);
518
519
2.26k
                    match rd.data {
520
174
                        Smb2SetInfoRequestData::RENAME(ref ren) => {
521
174
                            let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
522
174
                            let mut newname = ren.name.to_vec();
523
2.66k
                            newname.retain(|&i| i != 0x00);
524
174
                            let oldname = match state.guid2name_cache.get(rd.guid) {
525
11
                                Some(n) => n.to_vec(),
526
163
                                None => b"<unknown>".to_vec(),
527
                            };
528
174
                            let Some(tx) = state.new_rename_tx(rd.guid.to_vec(), oldname, newname)
529
                            else {
530
0
                                return;
531
                            };
532
174
                            tx.hdr = tx_hdr;
533
174
                            tx.request_done = true;
534
174
                            tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO);
535
174
                            true
536
                        }
537
438
                        Smb2SetInfoRequestData::DISPOSITION(ref dis) => {
538
438
                            let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
539
438
                            let fname = match state.guid2name_cache.get(rd.guid) {
540
17
                                Some(n) => n.to_vec(),
541
                                None => {
542
                                    // try to find latest created file in case of chained commands
543
421
                                    let mut guid_key =
544
421
                                        SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME);
545
421
                                    if guid_key.msg_id == 0 {
546
0
                                        b"<unknown>".to_vec()
547
                                    } else {
548
421
                                        guid_key.msg_id -= 1;
549
421
                                        match state.ssn2vec_cache.get(&guid_key) {
550
273
                                            Some(n) => n.to_vec(),
551
148
                                            None => b"<unknown>".to_vec(),
552
                                        }
553
                                    }
554
                                }
555
                            };
556
438
                            let Some(tx) = state.new_setfileinfo_tx(
557
438
                                fname,
558
438
                                rd.guid.to_vec(),
559
438
                                rd.class as u16,
560
438
                                rd.infolvl as u16,
561
438
                                dis.delete,
562
438
                            ) else {
563
0
                                return;
564
                            };
565
438
                            tx.hdr = tx_hdr;
566
438
                            tx.request_done = true;
567
438
                            tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO);
568
438
                            true
569
                        }
570
1.65k
                        _ => false,
571
                    }
572
                }
573
1.48k
                Err(Err::Incomplete(_n)) => {
574
                    SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _n);
575
1.48k
                    events.push(SMBEvent::MalformedData);
576
1.48k
                    false
577
                }
578
0
                Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
579
                    SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _e);
580
0
                    events.push(SMBEvent::MalformedData);
581
0
                    false
582
                }
583
            };
584
3.75k
            have_si_tx
585
        }
586
        SMB2_COMMAND_IOCTL => {
587
14.0k
            smb2_ioctl_request_record(state, r);
588
14.0k
            true
589
        }
590
        SMB2_COMMAND_TREE_DISCONNECT => {
591
1.20k
            let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
592
1.20k
            state.ssn2tree_cache.pop(&tree_key);
593
1.20k
            false
594
        }
595
        SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
596
9.61k
            if let Ok((_, rd)) = parse_smb2_request_negotiate_protocol(r.data) {
597
6.06k
                let mut dialects: Vec<Vec<u8>> = Vec::new();
598
78.9k
                for d in rd.dialects_vec {
599
72.8k
                    SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d));
600
72.8k
                    let dvec = smb2_dialect_string(d).as_bytes().to_vec();
601
72.8k
                    dialects.push(dvec);
602
72.8k
                }
603
604
6.06k
                if state.get_negotiate_tx(2).is_none() {
605
4.51k
                    let Some(tx) = state.new_negotiate_tx(2) else {
606
0
                        return;
607
                    };
608
4.51k
                    if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
609
4.51k
                        tdn.dialects2 = dialects;
610
4.51k
                        tdn.client_guid = Some(rd.client_guid.to_vec());
611
4.51k
                    }
612
4.51k
                    tx.request_done = true;
613
1.55k
                }
614
6.06k
                true
615
            } else {
616
3.54k
                events.push(SMBEvent::MalformedData);
617
3.54k
                false
618
            }
619
        }
620
        SMB2_COMMAND_SESSION_SETUP => {
621
43.9k
            smb2_session_setup_request(state, r);
622
43.9k
            true
623
        }
624
        SMB2_COMMAND_TREE_CONNECT => {
625
5.37k
            if let Ok((_, tr)) = parse_smb2_request_tree_connect(r.data) {
626
5.28k
                let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
627
5.28k
                let mut name_val = tr.share_name.to_vec();
628
264k
                name_val.retain(|&i| i != 0x00);
629
5.28k
                if name_val.len() > 1 {
630
3.50k
                    name_val = name_val[1..].to_vec();
631
3.50k
                }
632
633
5.28k
                let Some(tx) = state.new_treeconnect_tx(name_key, name_val) else {
634
0
                    return;
635
                };
636
5.28k
                tx.request_done = true;
637
5.28k
                tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT);
638
5.28k
                true
639
            } else {
640
94
                events.push(SMBEvent::MalformedData);
641
94
                false
642
            }
643
        }
644
        SMB2_COMMAND_READ => {
645
11.0k
            if let Ok((_, rd)) = parse_smb2_request_read(r.data) {
646
9.28k
                if (state.max_read_size != 0 && rd.rd_len > state.max_read_size)
647
8.69k
                    || (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.rd_len })
648
843
                {
649
843
                    events.push(SMBEvent::ReadRequestTooLarge);
650
8.44k
                } else {
651
8.44k
                    SCLogDebug!(
652
8.44k
                        "SMBv2 READ: GUID {:?} requesting {} bytes at offset {}",
653
8.44k
                        rd.guid,
654
8.44k
                        rd.rd_len,
655
8.44k
                        rd.rd_offset
656
8.44k
                    );
657
8.44k
658
8.44k
                    // store read guid,offset in map
659
8.44k
                    let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET);
660
8.44k
                    let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset);
661
8.44k
                    state.read_offset_cache.put(guid_key, guidoff);
662
8.44k
                }
663
1.72k
            } else {
664
1.72k
                events.push(SMBEvent::MalformedData);
665
1.72k
            }
666
11.0k
            false
667
        }
668
        SMB2_COMMAND_CREATE => {
669
30.2k
            if let Ok((_, cr)) = parse_smb2_request_create(r.data) {
670
28.0k
                let del = cr.create_options & 0x0000_1000 != 0;
671
28.0k
                let dir = cr.create_options & 0x0000_0001 != 0;
672
                SCLogDebug!("create_options {:08x}", cr.create_options);
673
674
28.0k
                let name_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME);
675
28.0k
                state.ssn2vec_cache.put(name_key, cr.data.to_vec());
676
677
28.0k
                let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
678
28.0k
                let Some(tx) = state.new_create_tx(cr.data, cr.disposition, del, dir, tx_hdr)
679
                else {
680
0
                    return;
681
                };
682
28.0k
                tx.vercmd.set_smb2_cmd(r.command);
683
                SCLogDebug!("TS CREATE TX {} created", tx.id);
684
28.0k
                true
685
            } else {
686
2.21k
                events.push(SMBEvent::MalformedData);
687
2.21k
                false
688
            }
689
        }
690
        SMB2_COMMAND_WRITE => {
691
34.8k
            smb2_write_request_record(state, r, 0);
692
34.8k
            true // write handling creates both file tx and generic tx
693
        }
694
        SMB2_COMMAND_CLOSE => {
695
21.8k
            if let Ok((_, cd)) = parse_smb2_request_close(r.data) {
696
21.7k
                let _name = state.guid2name_cache.pop(cd.guid);
697
698
21.7k
                let found_ts =
699
21.7k
                    if let Some(tx) = state.get_file_tx_by_fuid(cd.guid, Direction::ToServer) {
700
1.16k
                        if !tx.request_done {
701
1.13k
                            if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
702
1.13k
                                filetracker_close(&mut tdf.file_tracker);
703
1.13k
                            }
704
26
                        }
705
1.16k
                        tx.request_done = true;
706
1.16k
                        tx.response_done = true;
707
1.16k
                        tx.set_status(SMB_NTSTATUS_SUCCESS, false);
708
1.16k
                        true
709
                    } else {
710
20.5k
                        false
711
                    };
712
21.7k
                let found_tc =
713
21.7k
                    if let Some(tx) = state.get_file_tx_by_fuid(cd.guid, Direction::ToClient) {
714
1.43k
                        if !tx.request_done {
715
1.29k
                            if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
716
1.29k
                                filetracker_close(&mut tdf.file_tracker);
717
1.29k
                            }
718
146
                        }
719
1.43k
                        tx.request_done = true;
720
1.43k
                        tx.response_done = true;
721
1.43k
                        tx.set_status(SMB_NTSTATUS_SUCCESS, false);
722
1.43k
                        true
723
                    } else {
724
20.2k
                        false
725
                    };
726
21.7k
                if !found_ts && !found_tc {
727
19.4k
                    SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid);
728
19.4k
                }
729
158
            } else {
730
158
                events.push(SMBEvent::MalformedData);
731
158
            }
732
21.8k
            false
733
        }
734
25.7k
        _ => false,
735
    };
736
    /* if we don't have a tx, create it here (maybe) */
737
201k
    if !have_tx && smb2_create_new_tx(r.command) {
738
42.9k
        let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
739
42.9k
        let Some(tx) = state.new_generic_tx(2, r.command, tx_key) else {
740
2
            return;
741
        };
742
        SCLogDebug!(
743
            "TS TX {} command {} created with session_id {} tree_id {} message_id {}",
744
            tx.id,
745
            r.command,
746
            r.session_id,
747
            r.tree_id,
748
            r.message_id
749
        );
750
42.9k
        tx.set_events(events);
751
158k
    }
752
201k
}
753
754
122k
pub fn smb2_response_record(state: &mut SMBState, r: &Smb2Record) {
755
    SCLogDebug!(
756
        "SMBv2 response record, command {} status {} tree {} session {} message {}",
757
        &smb2_command_string(r.command),
758
        r.nt_status,
759
        r.tree_id,
760
        r.session_id,
761
        r.message_id
762
    );
763
764
122k
    let mut events: Vec<SMBEvent> = Vec::new();
765
766
122k
    let have_tx = match r.command {
767
        SMB2_COMMAND_IOCTL => {
768
16.0k
            smb2_ioctl_response_record(state, r);
769
16.0k
            true
770
        }
771
        SMB2_COMMAND_SESSION_SETUP => {
772
5.12k
            smb2_session_setup_response(state, r);
773
5.12k
            true
774
        }
775
        SMB2_COMMAND_WRITE => {
776
3.30k
            if r.nt_status == SMB_NTSTATUS_SUCCESS {
777
3.18k
                if let Ok((_, _wr)) = parse_smb2_response_write(r.data) {
778
2.53k
                    SCLogDebug!("SMBv2: Write response => {:?}", _wr);
779
2.53k
780
2.53k
                    /* search key-guid map */
781
2.53k
                    let guid_key =
782
2.53k
                        SMBCommonHdr::new(SMBHDR_TYPE_GUID, r.session_id, r.tree_id, r.message_id);
783
2.53k
                    let _guid_vec = state.ssn2vec_cache.pop(&guid_key).unwrap_or_default();
784
2.53k
                    SCLogDebug!("SMBv2 write response for GUID {:?}", _guid_vec);
785
2.53k
                } else {
786
653
                    events.push(SMBEvent::MalformedData);
787
653
                }
788
121
            }
789
3.30k
            false // the request may have created a generic tx, so handle that here
790
        }
791
        SMB2_COMMAND_READ => {
792
10.6k
            if r.nt_status == SMB_NTSTATUS_SUCCESS || r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW {
793
7.63k
                smb2_read_response_record(state, r, 0);
794
7.63k
            } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE {
795
                SCLogDebug!("SMBv2: read response => EOF");
796
797
1.56k
                let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET);
798
1.56k
                let file_guid = if let Some(o) = state.read_offset_cache.pop(&guid_key) {
799
443
                    o.guid
800
                } else {
801
                    SCLogDebug!("SMBv2 READ response: reply to unknown request");
802
1.12k
                    Vec::new()
803
                };
804
1.56k
                if let Some(tx) = state.get_file_tx_by_fuid(&file_guid, Direction::ToClient) {
805
272
                    if !tx.request_done {
806
145
                        if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
807
145
                            filetracker_close(&mut tdf.file_tracker);
808
145
                        }
809
127
                    }
810
272
                    tx.set_status(r.nt_status, false);
811
272
                    tx.request_done = true;
812
1.29k
                }
813
1.40k
            } else {
814
1.40k
                SCLogDebug!("SMBv2 READ: status {}", r.nt_status);
815
1.40k
            }
816
10.6k
            false
817
        }
818
        SMB2_COMMAND_CREATE => {
819
29.4k
            if r.nt_status == SMB_NTSTATUS_SUCCESS {
820
27.6k
                if let Ok((_, cr)) = parse_smb2_response_create(r.data) {
821
                    SCLogDebug!("SMBv2: Create response => {:?}", cr);
822
823
26.4k
                    let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME);
824
26.4k
                    if let Some(mut p) = state.ssn2vec_cache.pop(&guid_key) {
825
175k
                        p.retain(|&i| i != 0x00);
826
20.0k
                        _ = state.guid2name_cache.put(cr.guid.to_vec(), p);
827
6.41k
                    } else {
828
6.41k
                        SCLogDebug!("SMBv2 response: GUID NOT FOUND");
829
6.41k
                    }
830
831
26.4k
                    let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
832
26.4k
                    if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) {
833
                        SCLogDebug!(
834
                            "tx {} with {}/{} marked as done",
835
                            tx.id,
836
                            r.command,
837
                            &smb2_command_string(r.command)
838
                        );
839
18.9k
                        tx.set_status(r.nt_status, false);
840
18.9k
                        tx.response_done = true;
841
842
18.2k
                        if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data {
843
18.2k
                            tdn.create_ts = cr.create_ts.as_unix();
844
18.2k
                            tdn.last_access_ts = cr.last_access_ts.as_unix();
845
18.2k
                            tdn.last_write_ts = cr.last_write_ts.as_unix();
846
18.2k
                            tdn.last_change_ts = cr.last_change_ts.as_unix();
847
18.2k
                            tdn.size = cr.size;
848
18.2k
                            tdn.guid = cr.guid.to_vec();
849
18.2k
                        }
850
7.48k
                    }
851
1.20k
                } else {
852
1.20k
                    events.push(SMBEvent::MalformedData);
853
1.20k
                }
854
27.6k
                true
855
            } else {
856
1.80k
                false
857
            }
858
        }
859
        SMB2_COMMAND_TREE_DISCONNECT => {
860
            // normally removed when processing request,
861
            // but in case we missed that try again here
862
613
            let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
863
613
            state.ssn2tree_cache.pop(&tree_key);
864
613
            false
865
        }
866
        SMB2_COMMAND_TREE_CONNECT => {
867
4.60k
            if r.nt_status == SMB_NTSTATUS_SUCCESS {
868
3.39k
                if let Ok((_, tr)) = parse_smb2_response_tree_connect(r.data) {
869
2.83k
                    let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
870
2.83k
                    let mut share_name = Vec::new();
871
2.83k
                    let is_pipe = tr.share_type == 2;
872
2.83k
                    let found = match state.get_treeconnect_tx(name_key) {
873
1.72k
                        Some(tx) => {
874
1.72k
                            if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) =
875
1.72k
                                tx.type_data
876
1.72k
                            {
877
1.72k
                                tdn.share_type = tr.share_type;
878
1.72k
                                tdn.is_pipe = is_pipe;
879
1.72k
                                tdn.tree_id = r.tree_id;
880
1.72k
                                share_name = tdn.share_name.to_vec();
881
1.72k
                            }
882
                            // update hdr now that we have a tree_id
883
1.72k
                            tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
884
1.72k
                            tx.response_done = true;
885
1.72k
                            tx.set_status(r.nt_status, false);
886
1.72k
                            true
887
                        }
888
1.10k
                        None => false,
889
                    };
890
2.83k
                    if found {
891
1.72k
                        let tree = SMBTree::new(share_name.to_vec(), is_pipe);
892
1.72k
                        let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
893
1.72k
                        state.ssn2tree_cache.put(tree_key, tree);
894
1.72k
                    }
895
2.83k
                    true
896
                } else {
897
569
                    events.push(SMBEvent::MalformedData);
898
569
                    false
899
                }
900
            } else {
901
1.20k
                let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
902
1.20k
                if let Some(tx) = state.get_treeconnect_tx(name_key) {
903
492
                    tx.response_done = true;
904
492
                    tx.set_status(r.nt_status, false);
905
492
                    true
906
                } else {
907
715
                    false
908
                }
909
            }
910
        }
911
        SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
912
8.57k
            let res = if r.nt_status == SMB_NTSTATUS_SUCCESS {
913
7.37k
                parse_smb2_response_negotiate_protocol(r.data)
914
            } else {
915
1.20k
                parse_smb2_response_negotiate_protocol_error(r.data)
916
            };
917
8.57k
            if let Ok((_, rd)) = res {
918
                SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect));
919
920
6.95k
                let smb_cfg_max_read_size = unsafe { SMB_CFG_MAX_READ_SIZE };
921
6.95k
                if smb_cfg_max_read_size != 0 && rd.max_read_size > smb_cfg_max_read_size {
922
2.73k
                    state.set_event(SMBEvent::NegotiateMaxReadSizeTooLarge);
923
4.21k
                }
924
6.95k
                let smb_cfg_max_write_size = unsafe { SMB_CFG_MAX_WRITE_SIZE };
925
6.95k
                if smb_cfg_max_write_size != 0 && rd.max_write_size > smb_cfg_max_write_size {
926
2.43k
                    state.set_event(SMBEvent::NegotiateMaxWriteSizeTooLarge);
927
4.51k
                }
928
929
6.95k
                state.dialect = rd.dialect;
930
6.95k
                state.max_read_size = rd.max_read_size;
931
6.95k
                state.max_write_size = rd.max_write_size;
932
933
6.95k
                let found2 = match state.get_negotiate_tx(2) {
934
1.94k
                    Some(tx) => {
935
1.94k
                        if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
936
1.94k
                            tdn.server_guid = rd.server_guid.to_vec();
937
1.94k
                        }
938
1.94k
                        tx.set_status(r.nt_status, false);
939
1.94k
                        tx.response_done = true;
940
1.94k
                        true
941
                    }
942
5.00k
                    None => false,
943
                };
944
                // SMB2 response to SMB1 request?
945
6.95k
                let found1 = !found2
946
5.00k
                    && match state.get_negotiate_tx(1) {
947
1.14k
                        Some(tx) => {
948
1.14k
                            if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) =
949
1.14k
                                tx.type_data
950
1.14k
                            {
951
1.14k
                                tdn.server_guid = rd.server_guid.to_vec();
952
1.14k
                            }
953
1.14k
                            tx.set_status(r.nt_status, false);
954
1.14k
                            tx.response_done = true;
955
1.14k
                            true
956
                        }
957
3.85k
                        None => false,
958
                    };
959
6.95k
                found1 || found2
960
            } else {
961
1.62k
                events.push(SMBEvent::MalformedData);
962
1.62k
                false
963
            }
964
        }
965
        _ => {
966
            SCLogDebug!("default case: no TX");
967
44.2k
            false
968
        }
969
    };
970
122k
    if !have_tx {
971
67.3k
        let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
972
        SCLogDebug!(
973
            "looking for TX {} with session_id {} tree_id {} message_id {}",
974
            &smb2_command_string(r.command),
975
            r.session_id,
976
            r.tree_id,
977
            r.message_id
978
        );
979
67.3k
        let _found = match state.get_generic_tx(2, r.command, &tx_hdr) {
980
27.8k
            Some(tx) => {
981
                SCLogDebug!(
982
                    "tx {} with {}/{} marked as done",
983
                    tx.id,
984
                    r.command,
985
                    &smb2_command_string(r.command)
986
                );
987
27.8k
                if r.nt_status != SMB_NTSTATUS_PENDING {
988
27.3k
                    tx.response_done = true;
989
27.3k
                }
990
27.8k
                tx.set_status(r.nt_status, false);
991
27.8k
                tx.set_events(events);
992
27.8k
                true
993
            }
994
            _ => {
995
                SCLogDebug!("no tx found for {:?}", r);
996
39.4k
                false
997
            }
998
        };
999
55.2k
    }
1000
122k
}
1001
1002
#[cfg(test)]
1003
mod tests {
1004
    use super::*;
1005
1006
    // link stub: the C file-flags helper has no Rust definition; the tests
1007
    // never rely on its value
1008
    #[no_mangle]
1009
    pub extern "C" fn SCFileFlowFlagsToFlags(_f: u16, _d: u8) -> u16 {
1010
        0
1011
    }
1012
1013
    // the FFI file ops are no-ops under cfg(test), so a default context is
1014
    // safe (the filetracker needs it set to track chunks)
1015
    static TEST_SBCFG: StreamingBufferConfig = StreamingBufferConfig {
1016
        buf_size: 2048,
1017
        max_regions: 8,
1018
        region_gap: 0,
1019
        Calloc: None,
1020
        Realloc: None,
1021
        Free: None,
1022
    };
1023
    static TEST_FC: SuricataFileContext = SuricataFileContext {
1024
        files_sbcfg: &TEST_SBCFG,
1025
    };
1026
1027
    // SMB2 write body (parse_smb2_request_write layout): skip4, len, offset,
1028
    // guid, channel, remaining, write_flags, skip4, data.
1029
    use crate::core::{StreamingBufferConfig, SuricataFileContext};
1030
1031
    fn write_body(offset: u64, guid: &[u8], data: &[u8], declared: u32) -> Vec<u8> {
1032
        let mut b = vec![0u8; 4];
1033
        b.extend(declared.to_le_bytes());
1034
        b.extend(offset.to_le_bytes());
1035
        b.extend(guid);
1036
        b.extend([0u8; 16]); // channel + remaining + write_flags + skip
1037
        b.extend(data);
1038
        b
1039
    }
1040
1041
    fn write_rec(data: &[u8], mid: u64) -> Smb2Record<'_> {
1042
        Smb2Record {
1043
            direction: 0,
1044
            header_len: 64,
1045
            nt_status: 0,
1046
            command: SMB2_COMMAND_WRITE,
1047
            message_id: mid,
1048
            tree_id: 1,
1049
            async_id: 0,
1050
            session_id: 1,
1051
            data,
1052
        }
1053
    }
1054
1055
    // A queue-limit-rejected record must not arm the trailing file-data
1056
    // promise: its remainder is consumed by the skip, so the promise would
1057
    // otherwise claim the following record's bytes.
1058
    #[test]
1059
    fn test_smb2_write_rejected_partial_arms_no_promise() {
1060
        let _lock = crate::smb::files::SMB_CFG_TEST_LOCK.lock().unwrap();
1061
        // restores the cfg statics this test mutates; leaking them made later
1062
        // write-queue-dependent tests order-dependent
1063
        struct CfgGuard;
1064
        impl Drop for CfgGuard {
1065
            fn drop(&mut self) {
1066
                unsafe {
1067
                    SURICATA_SMB_FILE_CONFIG = None;
1068
                }
1069
                unsafe {
1070
                    SMB_CFG_MAX_WRITE_QUEUE_CNT = 64;
1071
                }
1072
                unsafe {
1073
                    SMB_CFG_MAX_WRITE_QUEUE_SIZE = 67108864;
1074
                }
1075
            }
1076
        }
1077
        let _cfg_guard = CfgGuard;
1078
        unsafe {
1079
            SURICATA_SMB_FILE_CONFIG = Some(&TEST_FC);
1080
        }
1081
        let mut state = SMBState::new();
1082
        let guid: Vec<u8> = [0xAAu8, 0xBB, 0xCC, 0xDD]
1083
            .into_iter()
1084
            .chain(std::iter::repeat_n(0, 12))
1085
            .collect();
1086
        // w1 (200 declared, 100 carried, nbss 0): rejected by the NBSS guard
1087
        // (200 - 100 > 0) before the tracker is fed, so it queues nothing.
1088
        let d1: Vec<u8> = vec![b'A'; 100];
1089
        let b1 = write_body(0, &guid, &d1, 200);
1090
        smb2_write_request_record(&mut state, &write_rec(&b1, 1), 0);
1091
        // w2 (100 declared, 100 carried at offset 300): creates the file tx
1092
        // and queues one OOO chunk (inflight count 1).
1093
        let d2: Vec<u8> = vec![b'B'; 100];
1094
        let b2 = write_body(300, &guid, &d2, 100);
1095
        smb2_write_request_record(&mut state, &write_rec(&b2, 2), 0);
1096
        // w3 (700 declared, 444 carried, 256 nbss, OOO at 600): a new OOO
1097
        // entry would make the inflight count 1 + 1 > 1, so the queue count
1098
        // limit rejects it on the partial path.
1099
        unsafe {
1100
            SMB_CFG_MAX_WRITE_QUEUE_CNT = 1;
1101
        }
1102
        unsafe {
1103
            SMB_CFG_MAX_WRITE_QUEUE_SIZE = 0;
1104
        }
1105
        let d3: Vec<u8> = vec![b'X'; 444];
1106
        let b3 = write_body(600, &guid, &d3, 700);
1107
        // sentinel: a regressed guard would overwrite it with the armed 256
1108
        state.file_ts_left = 0x1234;
1109
        smb2_write_request_record(&mut state, &write_rec(&b3, 3), 256);
1110
        assert_eq!(
1111
            state.file_ts_left, 0x1234,
1112
            "rejected record must not arm the file-data promise"
1113
        );
1114
    }
1115
}