Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/nfs/nfs4.rs
Line
Count
Source
1
/* Copyright (C) 2018-2020 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 Victor Julien
19
20
use nom7::bytes::streaming::take;
21
use nom7::number::streaming::be_u32;
22
use nom7::{Err, IResult};
23
24
use crate::core::*;
25
use crate::nfs::nfs::*;
26
use crate::nfs::nfs4_records::*;
27
use crate::nfs::nfs_records::*;
28
use crate::nfs::rpc_records::*;
29
use crate::nfs::types::*;
30
31
use crate::kerberos::{parse_kerberos5_request, Kerberos5Ticket, SecBlobError};
32
33
52.4k
fn parse_req_gssapi(i: &[u8]) -> IResult<&[u8], Kerberos5Ticket, SecBlobError> {
34
52.4k
    let (i, len) = be_u32(i)?;
35
52.1k
    let (i, buf) = take(len as usize)(i)?;
36
51.3k
    let (_, ap) = parse_kerberos5_request(buf)?;
37
0
    Ok((i, ap))
38
52.4k
}
39
40
impl NFSState {
41
    /* normal write: PUTFH (file handle), WRITE (write opts/data). File handle
42
     * is not part of the write record itself so we pass it in here. */
43
6.52k
    fn write_v4<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs4RequestWrite<'b>, fh: &'b [u8]) {
44
        // for now assume that stable FILE_SYNC flags means a single chunk
45
6.52k
        let is_last = w.stable == 2;
46
        SCLogDebug!("is_last {}", is_last);
47
48
6.52k
        let mut fill_bytes = 0;
49
6.52k
        let pad = w.write_len % 4;
50
6.52k
        if pad != 0 {
51
5.86k
            fill_bytes = 4 - pad;
52
5.86k
        }
53
54
        // linux defines a max of 1mb. Allow several multiples.
55
6.52k
        if w.write_len == 0 || w.write_len > 16777216 {
56
512
            return;
57
6.01k
        }
58
59
6.01k
        let file_handle = fh.to_vec();
60
6.01k
        let file_name = if let Some(name) = self.namemap.get(fh) {
61
            SCLogDebug!("WRITE name {:?}", name);
62
248
            name.to_vec()
63
        } else {
64
            SCLogDebug!("WRITE object {:?} not found", w.stateid.data);
65
5.76k
            Vec::new()
66
        };
67
68
6.01k
        let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
69
3.11k
            Some(tx) => {
70
3.11k
                if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
71
3.11k
                    filetracker_newchunk(&mut tdf.file_tracker,
72
3.11k
                            &file_name, w.data, w.offset,
73
3.11k
                            w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
74
3.11k
                    tdf.chunk_count += 1;
75
3.11k
                    if is_last {
76
2.20k
                        tdf.file_last_xid = r.hdr.xid;
77
2.20k
                        tx.is_last = true;
78
2.20k
                        tx.response_done = true;
79
2.20k
                    }
80
0
                }
81
3.11k
                true
82
            }
83
2.90k
            None => false,
84
        };
85
6.01k
        if !found {
86
2.90k
            let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer);
87
2.90k
            if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
88
2.90k
                filetracker_newchunk(&mut tdf.file_tracker,
89
2.90k
                        &file_name, w.data, w.offset,
90
2.90k
                        w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
91
2.90k
                tx.procedure = NFSPROC4_WRITE;
92
2.90k
                tx.xid = r.hdr.xid;
93
2.90k
                tx.is_first = true;
94
2.90k
                tx.nfs_version = r.progver as u16;
95
2.90k
                if is_last {
96
2.01k
                    tdf.file_last_xid = r.hdr.xid;
97
2.01k
                    tx.is_last = true;
98
2.01k
                    tx.request_done = true;
99
2.01k
                    tx.is_file_closed = true;
100
2.01k
                }
101
0
            }
102
3.11k
        }
103
6.01k
        self.ts_chunk_xid = r.hdr.xid;
104
6.01k
        debug_validate_bug_on!(w.data.len() as u32 > w.write_len);
105
6.01k
        self.ts_chunk_left = w.write_len - w.data.len()  as u32;
106
6.52k
    }
107
108
942
    fn close_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
109
942
        self.commit_v4(r, fh)
110
942
    }
111
112
2.85k
    fn commit_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
113
        SCLogDebug!("COMMIT, closing shop");
114
115
2.85k
        let file_handle = fh.to_vec();
116
2.85k
        if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
117
1.87k
            if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
118
1.87k
                filetracker_close(&mut tdf.file_tracker);
119
1.87k
                tdf.file_last_xid = r.hdr.xid;
120
1.87k
                tx.is_last = true;
121
1.87k
                tx.request_done = true;
122
1.87k
                tx.is_file_closed = true;
123
1.87k
            }
124
972
        }
125
2.85k
    }
126
127
12.4k
    fn new_tx_v4(
128
12.4k
        &mut self, r: &RpcPacket, xidmap: &NFSRequestXidMap, procedure: u32,
129
12.4k
        _aux_opcodes: &[u32],
130
12.4k
    ) {
131
12.4k
        let mut tx = self.new_tx();
132
12.4k
        tx.xid = r.hdr.xid;
133
12.4k
        tx.procedure = procedure;
134
12.4k
        tx.request_done = true;
135
12.4k
        tx.file_name = xidmap.file_name.to_vec();
136
12.4k
        tx.nfs_version = r.progver as u16;
137
12.4k
        tx.file_handle = xidmap.file_handle.to_vec();
138
139
12.4k
        tx.auth_type = r.creds_flavor;
140
        #[allow(clippy::single_match)]
141
12.4k
        match r.creds {
142
67
            RpcRequestCreds::Unix(ref u) => {
143
67
                tx.request_machine_name = u.machine_name_buf.to_vec();
144
67
                tx.request_uid = u.uid;
145
67
                tx.request_gid = u.gid;
146
67
            }
147
12.3k
            _ => {}
148
        }
149
        SCLogDebug!(
150
            "NFSv4: TX created: ID {} XID {} PROCEDURE {}",
151
            tx.id,
152
            tx.xid,
153
            tx.procedure
154
        );
155
12.4k
        self.transactions.push(tx);
156
12.4k
    }
157
158
    /* A normal READ request looks like: PUTFH (file handle) READ (read opts).
159
     * We need the file handle for the READ.
160
     */
161
19.5k
    fn compound_request<'b>(
162
19.5k
        &mut self, r: &RpcPacket<'b>, cr: &Nfs4RequestCompoundRecord<'b>,
163
19.5k
        xidmap: &mut NFSRequestXidMap,
164
19.5k
    ) {
165
19.5k
        let mut last_putfh: Option<&'b [u8]> = None;
166
19.5k
        let mut main_opcode: u32 = 0;
167
19.5k
        let mut aux_opcodes: Vec<u32> = Vec::new();
168
169
67.1k
        for c in &cr.commands {
170
            SCLogDebug!("c {:?}", c);
171
47.5k
            match *c {
172
10.3k
                Nfs4RequestContent::PutFH(ref rd) => {
173
10.3k
                    last_putfh = Some(rd.value);
174
10.3k
                    aux_opcodes.push(NFSPROC4_PUTFH);
175
10.3k
                }
176
478
                Nfs4RequestContent::Read(ref rd) => {
177
                    SCLogDebug!("READv4: {:?}", rd);
178
478
                    if let Some(fh) = last_putfh {
179
244
                        xidmap.chunk_offset = rd.offset;
180
244
                        xidmap.file_handle = fh.to_vec();
181
244
                        self.xidmap_handle2name(xidmap);
182
244
                    }
183
                }
184
788
                Nfs4RequestContent::Open(ref rd) => {
185
788
                    SCLogDebug!("OPENv4: {}", String::from_utf8_lossy(rd.filename));
186
788
                    xidmap.file_name = rd.filename.to_vec();
187
788
                }
188
9.03k
                Nfs4RequestContent::Lookup(ref rd) => {
189
9.03k
                    SCLogDebug!("LOOKUPv4: {}", String::from_utf8_lossy(rd.filename));
190
9.03k
                    xidmap.file_name = rd.filename.to_vec();
191
9.03k
                }
192
6.82k
                Nfs4RequestContent::Write(ref rd) => {
193
                    SCLogDebug!("WRITEv4: {:?}", rd);
194
6.82k
                    if let Some(fh) = last_putfh {
195
6.52k
                        self.write_v4(r, rd, fh);
196
6.52k
                    }
197
                }
198
                Nfs4RequestContent::Commit => {
199
                    SCLogDebug!("COMMITv4");
200
2.30k
                    if let Some(fh) = last_putfh {
201
1.90k
                        self.commit_v4(r, fh);
202
1.90k
                    }
203
                }
204
1.44k
                Nfs4RequestContent::Close(ref _rd) => {
205
                    SCLogDebug!("CLOSEv4: {:?}", _rd);
206
1.44k
                    if let Some(fh) = last_putfh {
207
942
                        self.close_v4(r, fh);
208
942
                    }
209
                }
210
1.43k
                Nfs4RequestContent::Create(ref rd) => {
211
                    SCLogDebug!("CREATEv4: {:?}", rd);
212
1.43k
                    if let Some(fh) = last_putfh {
213
684
                        xidmap.file_handle = fh.to_vec();
214
750
                    }
215
1.43k
                    xidmap.file_name = rd.filename.to_vec();
216
1.43k
                    main_opcode = NFSPROC4_CREATE;
217
                }
218
12.3k
                Nfs4RequestContent::Remove(rd) => {
219
12.3k
                    SCLogDebug!("REMOVEv4: {:?}", rd);
220
12.3k
                    xidmap.file_name = rd.to_vec();
221
12.3k
                    main_opcode = NFSPROC4_REMOVE;
222
12.3k
                }
223
1
                Nfs4RequestContent::SetClientId(ref _rd) => {
224
1
                    SCLogDebug!(
225
1
                        "SETCLIENTIDv4: client id {} r_netid {} r_addr {}",
226
1
                        String::from_utf8_lossy(_rd.client_id),
227
1
                        String::from_utf8_lossy(_rd.r_netid),
228
1
                        String::from_utf8_lossy(_rd.r_addr)
229
1
                    );
230
1
                }
231
2.54k
                _ => {}
232
            }
233
        }
234
235
19.5k
        if main_opcode != 0 {
236
12.4k
            self.new_tx_v4(r, xidmap, main_opcode, &aux_opcodes);
237
12.4k
        }
238
19.5k
    }
239
240
    /// complete request record
241
194k
    pub fn process_request_record_v4(&mut self, r: &RpcPacket) {
242
        SCLogDebug!(
243
            "NFSv4 REQUEST {} procedure {} ({}) blob size {}",
244
            r.hdr.xid,
245
            r.procedure,
246
            self.requestmap.len(),
247
            r.prog_data.len()
248
        );
249
250
194k
        let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
251
252
194k
        if r.procedure == NFSPROC4_NULL {
253
53.8k
            if let RpcRequestCreds::GssApi(ref creds) = r.creds {
254
53.1k
                if creds.procedure == 1 {
255
52.4k
                    let _x = parse_req_gssapi(r.prog_data);
256
52.4k
                    SCLogDebug!("RPCSEC_GSS_INIT {:?}", _x);
257
52.4k
                }
258
750
            }
259
140k
        } else if r.procedure == NFSPROC4_COMPOUND {
260
138k
            let mut data = r.prog_data;
261
262
138k
            if let RpcRequestCreds::GssApi(ref creds) = r.creds {
263
3.56k
                if creds.procedure == 0 && creds.service == 2 {
264
                    SCLogDebug!("GSS INTEGRITY: {:?}", creds);
265
2.67k
                    match parse_rpc_gssapi_integrity(r.prog_data) {
266
1.51k
                        Ok((_rem, rec)) => {
267
1.51k
                            SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
268
1.51k
                            data = rec.data;
269
1.51k
                            // store proc and serv for the reply
270
1.51k
                            xidmap.gssapi_proc = creds.procedure;
271
1.51k
                            xidmap.gssapi_service = creds.service;
272
1.51k
                        }
273
692
                        Err(Err::Incomplete(_n)) => {
274
                            SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
275
692
                            self.set_event(NFSEvent::MalformedData);
276
692
                            return;
277
                        }
278
468
                        Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
279
                            SCLogDebug!(
280
                                "NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}",
281
                                _e
282
                            );
283
468
                            self.set_event(NFSEvent::MalformedData);
284
468
                            return;
285
                        }
286
                    }
287
892
                }
288
134k
            }
289
290
137k
            match parse_nfs4_request_compound(data) {
291
19.5k
                Ok((_, rd)) => {
292
19.5k
                    SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd);
293
19.5k
                    self.compound_request(r, &rd, &mut xidmap);
294
19.5k
                }
295
86.0k
                Err(Err::Incomplete(_n)) => {
296
86.0k
                    SCLogDebug!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n);
297
86.0k
                    self.set_event(NFSEvent::MalformedData);
298
86.0k
                }
299
31.8k
                Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
300
31.8k
                    SCLogDebug!("NFSPROC4_COMPOUND: Parsing failed: {:?}", _e);
301
31.8k
                    self.set_event(NFSEvent::MalformedData);
302
31.8k
                }
303
            };
304
1.67k
        }
305
306
192k
        self.requestmap.insert(r.hdr.xid, xidmap);
307
194k
    }
308
309
10.9k
    fn compound_response<'b>(
310
10.9k
        &mut self, r: &RpcReplyPacket<'b>, cr: &Nfs4ResponseCompoundRecord<'b>,
311
10.9k
        xidmap: &mut NFSRequestXidMap,
312
10.9k
    ) {
313
10.9k
        let mut insert_filename_with_getfh = false;
314
10.9k
        let mut main_opcode_status: u32 = 0;
315
10.9k
        let mut main_opcode_status_set: bool = false;
316
317
31.9k
        for c in &cr.commands {
318
            SCLogDebug!("c {:?}", c);
319
501
            match *c {
320
1.63k
                Nfs4ResponseContent::ReadDir(_s, Some(ref rd)) => {
321
                    SCLogDebug!("READDIRv4: status {} eof {}", _s, rd.eof);
322
                    
323
                    #[allow(clippy::manual_flatten)]
324
3.04k
                    for d in &rd.listing {
325
1.41k
                        if let Some(_d) = d {
326
1.41k
                            SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(_d.name));
327
1.41k
                        }
328
                    }
329
                }
330
308
                Nfs4ResponseContent::Remove(s) => {
331
308
                    SCLogDebug!("REMOVE4: status {}", s);
332
308
                    main_opcode_status = s;
333
308
                    main_opcode_status_set = true;
334
308
                }
335
3.56k
                Nfs4ResponseContent::Create(s) => {
336
3.56k
                    SCLogDebug!("CREATE4: status {}", s);
337
3.56k
                    main_opcode_status = s;
338
3.56k
                    main_opcode_status_set = true;
339
3.56k
                }
340
79
                Nfs4ResponseContent::Read(s, Some(ref rd)) => {
341
79
                    SCLogDebug!(
342
79
                        "READ4: xidmap {:?} status {} data {}",
343
79
                        xidmap,
344
79
                        s,
345
79
                        rd.data.len()
346
79
                    );
347
79
                    // convert record to generic read reply
348
79
                    let reply = NfsReplyRead {
349
79
                        status: s,
350
79
                        attr_follows: 0,
351
79
                        attr_blob: &[],
352
79
                        count: rd.count,
353
79
                        eof: rd.eof,
354
79
                        data_len: rd.data.len() as u32,
355
79
                        data: rd.data,
356
79
                    };
357
79
                    self.process_read_record(r, &reply, Some(xidmap));
358
79
                }
359
234
                Nfs4ResponseContent::Open(_s, Some(ref _rd)) => {
360
234
                    SCLogDebug!("OPENv4: status {} opendata {:?}", _s, _rd);
361
234
                    insert_filename_with_getfh = true;
362
234
                }
363
239
                Nfs4ResponseContent::GetFH(_s, Some(ref rd)) => {
364
239
                    if insert_filename_with_getfh {
365
220
                        self.namemap
366
220
                            .insert(rd.value.to_vec(), xidmap.file_name.to_vec());
367
220
                    }
368
                }
369
2.41k
                Nfs4ResponseContent::PutRootFH(s) => {
370
2.41k
                    if s == NFS4_OK && xidmap.file_name.is_empty() {
371
1.10k
                        xidmap.file_name = b"<mount_root>".to_vec();
372
1.10k
                        SCLogDebug!("filename {:?}", xidmap.file_name);
373
1.31k
                    }
374
                }
375
12.4k
                _ => {}
376
            }
377
        }
378
379
10.9k
        if main_opcode_status_set {
380
3.56k
            let resp_handle = Vec::new();
381
3.56k
            self.mark_response_tx_done(r.hdr.xid, r.reply_state, main_opcode_status, &resp_handle);
382
7.43k
        }
383
10.9k
    }
384
385
59.4k
    pub fn process_reply_record_v4(
386
59.4k
        &mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap,
387
59.4k
    ) {
388
59.4k
        if xidmap.procedure == NFSPROC4_COMPOUND {
389
58.7k
            let mut data = r.prog_data;
390
391
58.7k
            if xidmap.gssapi_proc == 0 && xidmap.gssapi_service == 2 {
392
                SCLogDebug!("GSS INTEGRITY as set by call: {:?}", xidmap);
393
1.10k
                match parse_rpc_gssapi_integrity(r.prog_data) {
394
334
                    Ok((_rem, rec)) => {
395
334
                        SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
396
334
                        data = rec.data;
397
334
                    }
398
392
                    Err(Err::Incomplete(_n)) => {
399
                        SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
400
392
                        self.set_event(NFSEvent::MalformedData);
401
392
                        return;
402
                    }
403
375
                    Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
404
                        SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}", _e);
405
375
                        self.set_event(NFSEvent::MalformedData);
406
375
                        return;
407
                    }
408
                }
409
57.6k
            }
410
57.9k
            match parse_nfs4_response_compound(data) {
411
10.9k
                Ok((_, rd)) => {
412
10.9k
                    SCLogDebug!("COMPOUNDv4: {:?}", rd);
413
10.9k
                    self.compound_response(r, &rd, xidmap);
414
10.9k
                }
415
34.0k
                Err(Err::Incomplete(_)) => {
416
34.0k
                    self.set_event(NFSEvent::MalformedData);
417
34.0k
                }
418
12.9k
                Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
419
12.9k
                    SCLogDebug!("Parsing failed: {:?}", _e);
420
12.9k
                    self.set_event(NFSEvent::MalformedData);
421
12.9k
                }
422
            };
423
722
        }
424
59.4k
    }
425
}