Coverage Report

Created: 2026-03-31 07:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}