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