Coverage Report

Created: 2026-09-06 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/detect/datasets.rs
Line
Count
Source
1
/* Copyright (C) 2025 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
// Author: Shivani Bhardwaj <shivani@oisf.net>
19
20
//! This module exposes items from the datasets C code to Rust.
21
22
use crate::ffi::hashing::{SC_MD5_LEN, SC_SHA256_LEN};
23
use base64::{self, Engine};
24
use std::ffi::{c_char, CStr};
25
use std::fs::{File, OpenOptions};
26
use std::io::{self, BufRead};
27
use std::mem::transmute;
28
use std::net::{Ipv4Addr, Ipv6Addr};
29
use std::path::Path;
30
use std::str::FromStr;
31
32
/// Opaque Dataset type defined in C
33
#[derive(Copy, Clone)]
34
pub enum Dataset {}
35
36
// Simple C type converted to Rust
37
#[derive(Debug, PartialEq)]
38
#[repr(C)]
39
pub struct DataRepType {
40
    pub value: u16,
41
}
42
43
#[derive(Debug)]
44
#[repr(C)]
45
pub enum DatasetType {
46
    DSString = 0,
47
    DSMd5,
48
    DSSha256,
49
    DSIpv4,
50
    DSIpv6,
51
}
52
53
// Extern fns operating on the opaque Dataset type above
54
/// cbindgen:ignore
55
extern "C" {
56
    pub fn DatasetAdd(set: &Dataset, data: *const u8, len: u32) -> i32;
57
    pub fn DatasetAddwRep(set: &Dataset, data: *const u8, len: u32, rep: *const DataRepType)
58
        -> i32;
59
}
60
61
#[no_mangle]
62
1.70k
pub unsafe extern "C" fn ParseDatasets(
63
1.70k
    set: &Dataset, name: *const c_char, fname: *const c_char, fmode: *const c_char,
64
1.70k
    dstype: DatasetType,
65
1.70k
) -> i32 {
66
1.70k
    let file_string = unwrap_or_return!(CStr::from_ptr(fname).to_str(), -2);
67
1.70k
    let mode = unwrap_or_return!(CStr::from_ptr(fmode).to_str(), -2);
68
1.70k
    let set_name = unwrap_or_return!(CStr::from_ptr(name).to_str(), -2);
69
1.70k
    let filename = Path::new(file_string);
70
1.70k
    let mut no_rep = false;
71
1.70k
    let mut with_rep = false;
72
1.70k
    let lines = match read_or_create_file(filename, mode) {
73
1
        Ok(fp) => fp,
74
1.70k
        Err(_) => return -1,
75
    };
76
1
    for line in lines.map_while(Result::ok) {
77
0
        let v: Vec<&str> = line.split(',').collect();
78
        // Ignore empty and invalid lines in dataset/rep file
79
0
        if v.is_empty() || v.len() > 2 {
80
0
            continue;
81
0
        }
82
83
0
        if v.len() == 1 {
84
0
            if with_rep {
85
0
                SCLogError!(
86
0
                    "Cannot mix dataset and datarep values for set {} in {}",
87
                    set_name,
88
0
                    filename.display()
89
                );
90
0
                return -2;
91
0
            }
92
            // Dataset
93
0
            no_rep = true;
94
        } else {
95
0
            if no_rep {
96
0
                SCLogError!(
97
0
                    "Cannot mix dataset and datarep values for set {} in {}",
98
                    set_name,
99
0
                    filename.display()
100
                );
101
0
                return -2;
102
0
            }
103
            // Datarep
104
0
            with_rep = true;
105
        }
106
0
        match dstype {
107
            DatasetType::DSString => {
108
0
                if process_string_set(set, v, set_name, filename, no_rep) == -1 {
109
0
                    continue;
110
0
                }
111
            }
112
            DatasetType::DSMd5 => {
113
0
                if process_md5_set(set, v, set_name, filename, no_rep) == -1 {
114
0
                    continue;
115
0
                }
116
            }
117
            DatasetType::DSSha256 => {
118
0
                if process_sha256_set(set, v, set_name, filename, no_rep) == -1 {
119
0
                    continue;
120
0
                }
121
            }
122
            DatasetType::DSIpv4 => {
123
0
                if process_ipv4_set(set, v, set_name, filename, no_rep) == -1 {
124
0
                    continue;
125
0
                }
126
            }
127
            DatasetType::DSIpv6 => {
128
0
                if process_ipv6_set(set, v, set_name, filename, no_rep) == -1 {
129
0
                    continue;
130
0
                }
131
            }
132
        }
133
    }
134
135
1
    0
136
1.70k
}
137
138
0
unsafe fn process_string_set(
139
0
    set: &Dataset, v: Vec<&str>, set_name: &str, filename: &Path, no_rep: bool,
140
0
) -> i32 {
141
0
    let mut decoded: Vec<u8> = vec![];
142
0
    if base64::engine::general_purpose::STANDARD
143
0
        .decode_vec(v[0], &mut decoded)
144
0
        .is_err()
145
    {
146
0
        SCFatalErrorOnInit!("bad base64 encoding {} in {}", set_name, filename.display());
147
0
        return -1;
148
0
    }
149
0
    if no_rep {
150
0
        DatasetAdd(set, decoded.as_ptr(), decoded.len() as u32);
151
0
    } else if let Ok(val) = v[1].to_string().parse::<u16>() {
152
0
        let rep: DataRepType = DataRepType { value: val };
153
0
        DatasetAddwRep(set, decoded.as_ptr(), decoded.len() as u32, &rep);
154
0
    } else {
155
0
        SCFatalErrorOnInit!(
156
0
            "invalid datarep value {} in {}",
157
            set_name,
158
0
            filename.display()
159
        );
160
0
        return -1;
161
    }
162
0
    0
163
0
}
164
165
0
unsafe fn process_md5_set(
166
0
    set: &Dataset, v: Vec<&str>, set_name: &str, filename: &Path, no_rep: bool,
167
0
) -> i32 {
168
0
    let md5_string = match hex::decode(v[0]) {
169
0
        Ok(rs) => rs,
170
0
        Err(_) => return -1,
171
    };
172
0
    if md5_string.len() != SC_MD5_LEN {
173
0
        return -1;
174
0
    }
175
176
0
    if no_rep {
177
0
        DatasetAdd(set, md5_string.as_ptr(), SC_MD5_LEN as u32);
178
0
    } else if let Ok(val) = v[1].to_string().parse::<u16>() {
179
0
        let rep: DataRepType = DataRepType { value: val };
180
0
        DatasetAddwRep(set, md5_string.as_ptr(), SC_MD5_LEN as u32, &rep);
181
0
    } else {
182
0
        SCFatalErrorOnInit!(
183
0
            "invalid datarep value {} in {}",
184
            set_name,
185
0
            filename.display()
186
        );
187
0
        return -1;
188
    }
189
0
    0
190
0
}
191
192
0
unsafe fn process_sha256_set(
193
0
    set: &Dataset, v: Vec<&str>, set_name: &str, filename: &Path, no_rep: bool,
194
0
) -> i32 {
195
0
    let sha256_string = match hex::decode(v[0]) {
196
0
        Ok(rs) => rs,
197
0
        Err(_) => return -1,
198
    };
199
0
    if sha256_string.len() != SC_SHA256_LEN {
200
0
        return -1;
201
0
    }
202
203
0
    if no_rep {
204
0
        DatasetAdd(set, sha256_string.as_ptr(), SC_SHA256_LEN as u32);
205
0
    } else if let Ok(val) = v[1].to_string().parse::<u16>() {
206
0
        let rep: DataRepType = DataRepType { value: val };
207
0
        DatasetAddwRep(set, sha256_string.as_ptr(), SC_SHA256_LEN as u32, &rep);
208
0
    } else {
209
0
        SCFatalErrorOnInit!(
210
0
            "invalid datarep value {} in {}",
211
            set_name,
212
0
            filename.display()
213
        );
214
0
        return -1;
215
    }
216
0
    0
217
0
}
218
219
0
unsafe fn process_ipv4_set(
220
0
    set: &Dataset, v: Vec<&str>, set_name: &str, filename: &Path, no_rep: bool,
221
0
) -> i32 {
222
0
    let ipv4 = match Ipv4Addr::from_str(v[0]) {
223
0
        Ok(a) => a,
224
        Err(_) => {
225
0
            SCFatalErrorOnInit!("invalid Ipv4 value {} in {}", set_name, filename.display());
226
0
            return -1;
227
        }
228
    };
229
0
    if no_rep {
230
0
        DatasetAdd(set, ipv4.octets().as_ptr(), 4);
231
0
    } else if let Ok(val) = v[1].to_string().parse::<u16>() {
232
0
        let rep: DataRepType = DataRepType { value: val };
233
0
        DatasetAddwRep(set, ipv4.octets().as_ptr(), 4, &rep);
234
0
    } else {
235
0
        SCFatalErrorOnInit!(
236
0
            "invalid datarep value {} in {}",
237
            set_name,
238
0
            filename.display()
239
        );
240
0
        return -1;
241
    }
242
0
    0
243
0
}
244
245
0
unsafe fn process_ipv6_set(
246
0
    set: &Dataset, v: Vec<&str>, set_name: &str, filename: &Path, no_rep: bool,
247
0
) -> i32 {
248
0
    let ipv6 = match Ipv6Addr::from_str(v[0]) {
249
0
        Ok(a) => a,
250
        Err(_) => {
251
0
            SCFatalErrorOnInit!("invalid Ipv6 value {} in {}", set_name, filename.display());
252
0
            return -1;
253
        }
254
    };
255
0
    let mut fin_ipv6 = ipv6;
256
257
0
    if ipv6.to_ipv4_mapped().is_some() {
258
0
        let ipv6_octets = ipv6.octets();
259
0
        let mut internal_ipv6: [u8; 16] = [0; 16];
260
0
        internal_ipv6[0] = ipv6_octets[12];
261
0
        internal_ipv6[1] = ipv6_octets[13];
262
0
        internal_ipv6[2] = ipv6_octets[14];
263
0
        internal_ipv6[3] = ipv6_octets[15];
264
0
265
0
        // [u8; 16] is always safe to transmute to [u16; 8]
266
0
        let [s0, s1, s2, s3, s4, s5, s6, s7] =
267
0
            unsafe { transmute::<[u8; 16], [u16; 8]>(internal_ipv6) };
268
0
        fin_ipv6 = [
269
0
            u16::from_be(s0),
270
0
            u16::from_be(s1),
271
0
            u16::from_be(s2),
272
0
            u16::from_be(s3),
273
0
            u16::from_be(s4),
274
0
            u16::from_be(s5),
275
0
            u16::from_be(s6),
276
0
            u16::from_be(s7),
277
0
        ]
278
0
        .into();
279
0
    }
280
0
    if no_rep {
281
0
        DatasetAdd(set, fin_ipv6.octets().as_ptr(), 16);
282
0
    } else if let Ok(val) = v[1].to_string().parse::<u16>() {
283
0
        let rep: DataRepType = DataRepType { value: val };
284
0
        DatasetAddwRep(set, fin_ipv6.octets().as_ptr(), 16, &rep);
285
0
    } else {
286
0
        SCFatalErrorOnInit!(
287
0
            "invalid datarep value {} in {}",
288
            set_name,
289
0
            filename.display()
290
        );
291
0
        return -1;
292
    }
293
0
    0
294
0
}
295
296
1.70k
fn read_or_create_file<P>(filename: P, fmode: &str) -> io::Result<io::Lines<io::BufReader<File>>>
297
1.70k
where
298
1.70k
    P: AsRef<Path>,
299
{
300
1.70k
    let file: File = if fmode == "r" {
301
1.64k
        File::open(filename)?
302
    } else {
303
63
        OpenOptions::new()
304
63
            .append(true)
305
63
            .create(true)
306
63
            .read(true)
307
63
            .open(filename)?
308
    };
309
1
    Ok(io::BufReader::new(file).lines())
310
1.70k
}