/src/suricata7/rust/src/tftp/tftp.rs
Line | Count | Source |
1 | | /* Copyright (C) 2017-2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | // written by Clément Galland <clement.galland@epita.fr> |
19 | | |
20 | | use std::str; |
21 | | use std; |
22 | | use nom7::IResult; |
23 | | use nom7::combinator::map_res; |
24 | | use nom7::bytes::streaming::{tag, take_while}; |
25 | | use nom7::number::streaming::be_u8; |
26 | | |
27 | | use crate::applayer::{AppLayerTxData,AppLayerStateData}; |
28 | | |
29 | | const READREQUEST: u8 = 1; |
30 | | const WRITEREQUEST: u8 = 2; |
31 | | const DATA: u8 = 3; |
32 | | const ACK: u8 = 4; |
33 | | const ERROR: u8 = 5; |
34 | | |
35 | | #[derive(Debug, PartialEq, Eq)] |
36 | | pub struct TFTPTransaction { |
37 | | pub opcode : u8, |
38 | | pub filename : String, |
39 | | pub mode : String, |
40 | | id: u64, |
41 | | tx_data: AppLayerTxData, |
42 | | } |
43 | | |
44 | | pub struct TFTPState { |
45 | | state_data: AppLayerStateData, |
46 | | pub transactions : Vec<TFTPTransaction>, |
47 | | /// tx counter for assigning incrementing id's to tx's |
48 | | tx_id: u64, |
49 | | } |
50 | | |
51 | | impl TFTPState { |
52 | 478k | fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&TFTPTransaction> { |
53 | 504k | self.transactions.iter().find(|&tx| tx.id == tx_id + 1) |
54 | 478k | } |
55 | | |
56 | 475k | fn free_tx(&mut self, tx_id: u64) { |
57 | 475k | let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); |
58 | 475k | debug_assert!(tx.is_some()); |
59 | 475k | if let Some(idx) = tx { |
60 | 475k | let _ = self.transactions.remove(idx); |
61 | 475k | } |
62 | 475k | } |
63 | | } |
64 | | |
65 | | impl TFTPTransaction { |
66 | 478k | pub fn new(opcode : u8, filename : String, mode : String) -> TFTPTransaction { |
67 | 478k | TFTPTransaction { |
68 | 478k | opcode, |
69 | 478k | filename, |
70 | 478k | mode : mode.to_lowercase(), |
71 | 478k | id : 0, |
72 | 478k | tx_data: AppLayerTxData::new(), |
73 | 478k | } |
74 | 478k | } |
75 | 478k | pub fn is_mode_ok(&self) -> bool { |
76 | 478k | match self.mode.as_str() { |
77 | 478k | "netascii" | "mail" | "octet" => true, |
78 | 2.37k | _ => false |
79 | | } |
80 | 478k | } |
81 | 475k | pub fn is_opcode_ok(&self) -> bool { |
82 | 475k | match self.opcode { |
83 | 475k | READREQUEST | WRITEREQUEST | ACK | DATA | ERROR => true, |
84 | 20 | _ => false |
85 | | } |
86 | 475k | } |
87 | | } |
88 | | |
89 | | #[no_mangle] |
90 | 735 | pub extern "C" fn rs_tftp_state_alloc() -> *mut std::os::raw::c_void { |
91 | 735 | let state = TFTPState { state_data: AppLayerStateData::new(), transactions : Vec::new(), tx_id: 0, }; |
92 | 735 | let boxed = Box::new(state); |
93 | 735 | return Box::into_raw(boxed) as *mut _; |
94 | 735 | } |
95 | | |
96 | | #[no_mangle] |
97 | 735 | pub extern "C" fn rs_tftp_state_free(state: *mut std::os::raw::c_void) { |
98 | 735 | std::mem::drop(unsafe { Box::from_raw(state as *mut TFTPState) }); |
99 | 735 | } |
100 | | |
101 | | #[no_mangle] |
102 | 475k | pub extern "C" fn rs_tftp_state_tx_free(state: &mut TFTPState, |
103 | 475k | tx_id: u64) { |
104 | 475k | state.free_tx(tx_id); |
105 | 475k | } |
106 | | |
107 | | #[no_mangle] |
108 | 478k | pub extern "C" fn rs_tftp_get_tx(state: &mut TFTPState, |
109 | 478k | tx_id: u64) -> *mut std::os::raw::c_void { |
110 | 478k | match state.get_tx_by_id(tx_id) { |
111 | 478k | Some(tx) => tx as *const _ as *mut _, |
112 | 0 | None => std::ptr::null_mut(), |
113 | | } |
114 | 478k | } |
115 | | |
116 | | #[no_mangle] |
117 | 1.45M | pub extern "C" fn rs_tftp_get_tx_cnt(state: &mut TFTPState) -> u64 { |
118 | 1.45M | return state.tx_id; |
119 | 1.45M | } |
120 | | |
121 | 957k | fn getstr(i: &[u8]) -> IResult<&[u8], &str> { |
122 | 957k | map_res( |
123 | 8.78M | take_while(|c| c != 0), |
124 | | str::from_utf8 |
125 | 957k | )(i) |
126 | 957k | } |
127 | | |
128 | 479k | fn tftp_request(slice: &[u8]) -> IResult<&[u8], TFTPTransaction> { |
129 | 479k | let (i, _) = tag([0])(slice)?; |
130 | 479k | let (i, opcode) = be_u8(i)?; |
131 | 479k | let (i, filename) = getstr(i)?; |
132 | 478k | let (i, _) = tag([0])(i)?; |
133 | 478k | let (i, mode) = getstr(i)?; |
134 | 478k | Ok((i, |
135 | 478k | TFTPTransaction::new(opcode, String::from(filename), String::from(mode)) |
136 | 478k | ) |
137 | 478k | ) |
138 | 479k | } |
139 | | |
140 | 479k | fn parse_tftp_request(input: &[u8]) -> Option<TFTPTransaction> { |
141 | 479k | match tftp_request(input) { |
142 | 478k | Ok((_, tx)) => { |
143 | 478k | if !tx.is_mode_ok() { |
144 | 2.37k | return None; |
145 | 475k | } |
146 | 475k | if !tx.is_opcode_ok() { |
147 | 20 | return None; |
148 | 475k | } |
149 | 475k | return Some(tx); |
150 | | } |
151 | | Err(_) => { |
152 | 1.06k | return None; |
153 | | } |
154 | | } |
155 | 479k | } |
156 | | |
157 | | #[no_mangle] |
158 | 479k | pub unsafe extern "C" fn rs_tftp_request(state: &mut TFTPState, |
159 | 479k | input: *const u8, |
160 | 479k | len: u32) -> i64 { |
161 | 479k | let buf = std::slice::from_raw_parts(input, len as usize); |
162 | 479k | match parse_tftp_request(buf) { |
163 | 475k | Some(mut tx) => { |
164 | 475k | state.tx_id += 1; |
165 | 475k | tx.id = state.tx_id; |
166 | 475k | state.transactions.push(tx); |
167 | 475k | 0 |
168 | | }, |
169 | | None => { |
170 | 3.45k | -1 |
171 | | } |
172 | | } |
173 | 479k | } |
174 | | |
175 | | #[no_mangle] |
176 | 478k | pub unsafe extern "C" fn rs_tftp_get_tx_data( |
177 | 478k | tx: *mut std::os::raw::c_void) |
178 | 478k | -> *mut AppLayerTxData |
179 | | { |
180 | 478k | let tx = cast_pointer!(tx, TFTPTransaction); |
181 | 478k | return &mut tx.tx_data; |
182 | 478k | } |
183 | | |
184 | | #[no_mangle] |
185 | 201 | pub unsafe extern "C" fn rs_tftp_get_state_data( |
186 | 201 | state: *mut std::os::raw::c_void) |
187 | 201 | -> *mut AppLayerStateData |
188 | | { |
189 | 201 | let state = cast_pointer!(state, TFTPState); |
190 | 201 | return &mut state.state_data; |
191 | 201 | } |
192 | | |
193 | | #[cfg(test)] |
194 | | mod test { |
195 | | use super::*; |
196 | | static READ_REQUEST: [u8; 20] = [ |
197 | | 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, |
198 | | ]; |
199 | | /* filename not terminated */ |
200 | | static READ_REQUEST_INVALID_1: [u8; 20] = [ |
201 | | 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x6e, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, |
202 | | ]; |
203 | | /* garbage */ |
204 | | static READ_REQUEST_INVALID_2: [u8; 3] = [ |
205 | | 0xff, 0xff, 0xff, |
206 | | ]; |
207 | | static WRITE_REQUEST: [u8; 20] = [ |
208 | | 0x00, 0x02, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, |
209 | | ]; |
210 | | /* filename not terminated */ |
211 | | static INVALID_OPCODE: [u8; 20] = [ |
212 | | 0x00, 0x06, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x6e, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, |
213 | | ]; |
214 | | static INVALID_MODE: [u8; 20] = [ |
215 | | 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, |
216 | | ]; |
217 | | |
218 | | #[test] |
219 | | pub fn test_parse_tftp_read_request_1() { |
220 | | let tx = TFTPTransaction { |
221 | | opcode: READREQUEST, |
222 | | filename: String::from("rfc1350.txt"), |
223 | | mode: String::from("octet"), |
224 | | id: 0, |
225 | | tx_data: AppLayerTxData::new(), |
226 | | }; |
227 | | |
228 | | let txp = parse_tftp_request(&READ_REQUEST[..]).unwrap(); |
229 | | assert_eq!(tx, txp); |
230 | | } |
231 | | |
232 | | #[test] |
233 | | pub fn test_parse_tftp_write_request_1() { |
234 | | let tx = TFTPTransaction { |
235 | | opcode: WRITEREQUEST, |
236 | | filename: String::from("rfc1350.txt"), |
237 | | mode: String::from("octet"), |
238 | | id: 0, |
239 | | tx_data: AppLayerTxData::new(), |
240 | | }; |
241 | | |
242 | | let txp = parse_tftp_request(&WRITE_REQUEST[..]).unwrap(); |
243 | | assert_eq!(tx, txp); |
244 | | } |
245 | | |
246 | | // Invalid request: filename not terminated |
247 | | #[test] |
248 | | pub fn test_parse_tftp_read_request_2() { |
249 | | assert_eq!(None, parse_tftp_request(&READ_REQUEST_INVALID_1[..])); |
250 | | } |
251 | | |
252 | | // Invalid request: garbage input |
253 | | #[test] |
254 | | pub fn test_parse_tftp_read_request_3() { |
255 | | assert_eq!(None, parse_tftp_request(&READ_REQUEST_INVALID_2[..])); |
256 | | } |
257 | | |
258 | | #[test] |
259 | | pub fn test_parse_tftp_invalid_opcode_1() { |
260 | | assert_eq!(None, parse_tftp_request(&INVALID_OPCODE[..])); |
261 | | } |
262 | | |
263 | | #[test] |
264 | | pub fn test_parse_tftp_invalid_mode() { |
265 | | |
266 | | assert_eq!(None, parse_tftp_request(&INVALID_MODE[..])); |
267 | | } |
268 | | } |