Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/rust/src/ffi/hashing.rs
Line
Count
Source
1
/* Copyright (C) 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
use digest::{Digest, Update};
19
use md5::Md5;
20
use sha1::Sha1;
21
use sha2::Sha256;
22
use std::os::raw::c_char;
23
24
pub const SC_SHA256_LEN: usize = 32;
25
pub const SC_SHA1_LEN: usize = 20;
26
pub const SC_MD5_LEN: usize = 16;
27
28
// Length of hex digests without trailing NUL.
29
pub const SC_SHA256_HEX_LEN: usize = 64;
30
pub const SC_SHA1_HEX_LEN: usize = 40;
31
pub const SC_MD5_HEX_LEN: usize = 32;
32
33
// Wrap the Rust Sha256 in a new type named SCSha256 to give this type
34
// the "SC" prefix. The one drawback is we must access the actual context
35
// with .0.
36
pub struct SCSha256(Sha256);
37
38
#[no_mangle]
39
980k
pub extern "C" fn SCSha256New() -> *mut SCSha256 {
40
980k
    let hasher = Box::new(SCSha256(Sha256::new()));
41
980k
    Box::into_raw(hasher)
42
980k
}
43
44
#[no_mangle]
45
7.29M
pub unsafe extern "C" fn SCSha256Update(hasher: &mut SCSha256, bytes: *const u8, len: u32) {
46
7.29M
    update(&mut hasher.0, bytes, len);
47
7.29M
}
48
49
#[no_mangle]
50
914k
pub unsafe extern "C" fn SCSha256Finalize(hasher: &mut SCSha256, out: *mut u8, len: u32) {
51
914k
    let hasher: Box<SCSha256> = Box::from_raw(hasher);
52
914k
    finalize(hasher.0, out, len);
53
914k
}
54
55
/// C function to finalize the Sha256 hasher to a hex string.
56
///
57
/// Notes:
58
/// - There is probably room for optimization here, by iterating the result and writing
59
///   the output directly to the output buffer.
60
///
61
/// But even given the notes, this appears to be faster than the equivalent that we
62
/// did in C using NSS.
63
#[no_mangle]
64
0
pub unsafe extern "C" fn SCSha256FinalizeToHex(
65
0
    hasher: &mut SCSha256, out: *mut c_char, len: u32,
66
0
) -> bool {
67
0
    let hasher: Box<SCSha256> = Box::from_raw(hasher);
68
0
    let result = hasher.0.finalize();
69
0
    let hex = format!("{:x}", &result);
70
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
71
0
}
72
73
/// Free an unfinalized Sha256 context.
74
#[no_mangle]
75
64.3k
pub unsafe extern "C" fn SCSha256Free(hasher: &mut SCSha256) {
76
    // Drop.
77
64.3k
    let _: Box<SCSha256> = Box::from_raw(hasher);
78
64.3k
}
79
80
#[no_mangle]
81
0
pub unsafe extern "C" fn SCSha256HashBuffer(
82
0
    buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
83
0
) -> bool {
84
0
    if len as usize != SC_SHA256_LEN {
85
0
        return false;
86
0
    }
87
0
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
88
0
    let output = std::slice::from_raw_parts_mut(out, len as usize);
89
0
    let hash = Sha256::new().chain(data).finalize();
90
0
    output.copy_from_slice(&hash);
91
0
    return true;
92
0
}
93
94
#[no_mangle]
95
0
pub unsafe extern "C" fn SCSha256HashBufferToHex(
96
0
    buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
97
0
) -> bool {
98
0
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
99
0
    let hash = Sha256::new().chain(data).finalize();
100
0
    let hex = format!("{:x}", &hash);
101
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
102
0
}
103
104
// Start of SHA1 C bindings.
105
106
pub struct SCSha1(Sha1);
107
108
#[no_mangle]
109
977k
pub extern "C" fn SCSha1New() -> *mut SCSha1 {
110
977k
    let hasher = Box::new(SCSha1(Sha1::new()));
111
977k
    Box::into_raw(hasher)
112
977k
}
113
114
#[no_mangle]
115
7.23M
pub unsafe extern "C" fn SCSha1Update(hasher: &mut SCSha1, bytes: *const u8, len: u32) {
116
7.23M
    update(&mut hasher.0, bytes, len);
117
7.23M
}
118
119
#[no_mangle]
120
912k
pub unsafe extern "C" fn SCSha1Finalize(hasher: &mut SCSha1, out: *mut u8, len: u32) {
121
912k
    let hasher: Box<SCSha1> = Box::from_raw(hasher);
122
912k
    finalize(hasher.0, out, len);
123
912k
}
124
125
#[no_mangle]
126
0
pub unsafe extern "C" fn SCSha1FinalizeToHex(
127
0
    hasher: &mut SCSha1, out: *mut c_char, len: u32,
128
0
) -> bool {
129
0
    let hasher: Box<SCSha1> = Box::from_raw(hasher);
130
0
    let result = hasher.0.finalize();
131
0
    let hex = format!("{:x}", &result);
132
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
133
0
}
134
135
/// Free an unfinalized Sha1 context.
136
#[no_mangle]
137
63.7k
pub unsafe extern "C" fn SCSha1Free(hasher: &mut SCSha1) {
138
    // Drop.
139
63.7k
    let _: Box<SCSha1> = Box::from_raw(hasher);
140
63.7k
}
141
142
#[no_mangle]
143
0
pub unsafe extern "C" fn SCSha1HashBuffer(
144
0
    buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
145
0
) -> bool {
146
0
    if len as usize != SC_SHA1_LEN {
147
0
        return false;
148
0
    }
149
0
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
150
0
    let output = std::slice::from_raw_parts_mut(out, len as usize);
151
0
    let hash = Sha1::new().chain(data).finalize();
152
0
    output.copy_from_slice(&hash);
153
0
    return true;
154
0
}
155
156
#[no_mangle]
157
0
pub unsafe extern "C" fn SCSha1HashBufferToHex(
158
0
    buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
159
0
) -> bool {
160
0
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
161
0
    let hash = Sha1::new().chain(data).finalize();
162
0
    let hex = format!("{:x}", &hash);
163
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
164
0
}
165
166
// Start of MD5 C bindings.
167
168
pub struct SCMd5(Md5);
169
170
#[no_mangle]
171
977k
pub extern "C" fn SCMd5New() -> *mut SCMd5 {
172
977k
    let hasher = Box::new(SCMd5(Md5::new()));
173
977k
    Box::into_raw(hasher)
174
977k
}
175
176
#[no_mangle]
177
7.23M
pub unsafe extern "C" fn SCMd5Update(hasher: &mut SCMd5, bytes: *const u8, len: u32) {
178
7.23M
    update(&mut hasher.0, bytes, len);
179
7.23M
}
180
181
/// Finalize the MD5 hash placing the digest in the provided out buffer.
182
///
183
/// This function consumes the SCMd5 hash context.
184
#[no_mangle]
185
912k
pub unsafe extern "C" fn SCMd5Finalize(hasher: &mut SCMd5, out: *mut u8, len: u32) {
186
912k
    let hasher: Box<SCMd5> = Box::from_raw(hasher);
187
912k
    finalize(hasher.0, out, len);
188
912k
}
189
190
/// Finalize MD5 context to a hex string.
191
///
192
/// Consumes the hash context and cannot be re-used.
193
#[no_mangle]
194
0
pub unsafe extern "C" fn SCMd5FinalizeToHex(
195
0
    hasher: &mut SCMd5, out: *mut c_char, len: u32,
196
0
) -> bool {
197
0
    let hasher: Box<SCMd5> = Box::from_raw(hasher);
198
0
    let result = hasher.0.finalize();
199
0
    let hex = format!("{:x}", &result);
200
0
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
201
0
}
202
203
/// Free an unfinalized Sha1 context.
204
#[no_mangle]
205
63.7k
pub unsafe extern "C" fn SCMd5Free(hasher: &mut SCMd5) {
206
    // Drop.
207
63.7k
    let _: Box<SCMd5> = Box::from_raw(hasher);
208
63.7k
}
209
210
#[no_mangle]
211
0
pub unsafe extern "C" fn SCMd5HashBuffer(
212
0
    buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
213
0
) -> bool {
214
0
    if len as usize != SC_MD5_LEN {
215
0
        return false;
216
0
    }
217
0
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
218
0
    let output = std::slice::from_raw_parts_mut(out, len as usize);
219
0
    let hash = Md5::new().chain(data).finalize();
220
0
    output.copy_from_slice(&hash);
221
0
    true
222
0
}
223
224
/// C binding for a function to MD5 hash a single buffer to a hex string.
225
#[no_mangle]
226
89
pub unsafe extern "C" fn SCMd5HashBufferToHex(
227
89
    buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
228
89
) -> bool {
229
89
    let data = std::slice::from_raw_parts(buf, buf_len as usize);
230
89
    let hash = Md5::new().chain(data).finalize();
231
89
    let hex = format!("{:x}", &hash);
232
89
    crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
233
89
}
234
235
// Functions that are generic over Digest. For the most part the C bindings are
236
// just wrappers around these.
237
238
21.7M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
239
21.7M
    let data = std::slice::from_raw_parts(bytes, len as usize);
240
21.7M
    digest.update(data);
241
21.7M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<digest::core_api::ct_variable::CtVariableCoreWrapper<sha2::core_api::Sha256VarCore, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, sha2::OidSha256>>>
Line
Count
Source
238
7.29M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
239
7.29M
    let data = std::slice::from_raw_parts(bytes, len as usize);
240
7.29M
    digest.update(data);
241
7.29M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<md5::Md5Core>>
Line
Count
Source
238
7.23M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
239
7.23M
    let data = std::slice::from_raw_parts(bytes, len as usize);
240
7.23M
    digest.update(data);
241
7.23M
}
suricata::ffi::hashing::update::<digest::core_api::wrapper::CoreWrapper<sha1::Sha1Core>>
Line
Count
Source
238
7.23M
unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) {
239
7.23M
    let data = std::slice::from_raw_parts(bytes, len as usize);
240
7.23M
    digest.update(data);
241
7.23M
}
242
243
2.73M
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
244
2.73M
    let result = digest.finalize();
245
2.73M
    let output = std::slice::from_raw_parts_mut(out, len as usize);
246
    // This will panic if the sizes differ.
247
2.73M
    output.copy_from_slice(&result);
248
2.73M
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<digest::core_api::ct_variable::CtVariableCoreWrapper<sha2::core_api::Sha256VarCore, typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, typenum::bit::B0>, sha2::OidSha256>>>
Line
Count
Source
243
914k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
244
914k
    let result = digest.finalize();
245
914k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
246
    // This will panic if the sizes differ.
247
914k
    output.copy_from_slice(&result);
248
914k
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<md5::Md5Core>>
Line
Count
Source
243
912k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
244
912k
    let result = digest.finalize();
245
912k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
246
    // This will panic if the sizes differ.
247
912k
    output.copy_from_slice(&result);
248
912k
}
suricata::ffi::hashing::finalize::<digest::core_api::wrapper::CoreWrapper<sha1::Sha1Core>>
Line
Count
Source
243
912k
unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) {
244
912k
    let result = digest.finalize();
245
912k
    let output = std::slice::from_raw_parts_mut(out, len as usize);
246
    // This will panic if the sizes differ.
247
912k
    output.copy_from_slice(&result);
248
912k
}
249
250
pub static mut G_DISABLE_HASHING: bool = false;
251
252
#[no_mangle]
253
0
pub unsafe extern "C" fn SCDisableHashing() {
254
0
    G_DISABLE_HASHING = true;
255
0
}
256
257
#[cfg(test)]
258
mod test {
259
    use super::*;
260
261
    // A test around SCSha256 primarily to check that the output is
262
    // correctly copied into a C string.
263
    #[test]
264
    fn test_sha256() {
265
        unsafe {
266
            let hasher = SCSha256New();
267
            assert!(!hasher.is_null());
268
            let hasher = &mut *hasher as &mut SCSha256;
269
            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
270
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
271
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
272
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
273
            SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
274
            let hex = [0_u8; SC_SHA256_HEX_LEN + 1];
275
            SCSha256FinalizeToHex(
276
                hasher,
277
                hex.as_ptr() as *mut c_char,
278
                (SC_SHA256_HEX_LEN + 1) as u32,
279
            );
280
            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char)
281
                .to_str()
282
                .unwrap();
283
            assert_eq!(
284
                string,
285
                "22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153"
286
            );
287
        }
288
    }
289
290
    // A test around SCSha256 primarily to check that the output is
291
    // correctly copied into a C string.
292
    #[test]
293
    fn test_md5() {
294
        unsafe {
295
            let hasher = SCMd5New();
296
            assert!(!hasher.is_null());
297
            let hasher = &mut *hasher as &mut SCMd5;
298
            let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41];
299
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
300
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
301
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
302
            SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
303
            let hex = [0_u8; SC_MD5_HEX_LEN + 1];
304
            SCMd5FinalizeToHex(
305
                hasher,
306
                hex.as_ptr() as *mut c_char,
307
                (SC_MD5_HEX_LEN + 1) as u32,
308
            );
309
            let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char)
310
                .to_str()
311
                .unwrap();
312
            assert_eq!(string, "5216ddcc58e8dade5256075e77f642da");
313
        }
314
    }
315
}