/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 | | } |