Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lua: expose hashing functions to lua - v4 #12459

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/userguide/lua/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Lua support

lua-usage
lua-functions
libs/index
99 changes: 99 additions & 0 deletions doc/userguide/lua/libs/hashlib.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
Hashing
-------

Hashing functions are exposed to Lua scripts with ``suricata.hashing``
library. For example::

local hashing = require("suricata.hashing")

SHA-256
~~~~~~~

``sha256_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^

SHA-256 hash the provided string returning the digest as bytes.

``sha256_hex_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

SHA-256 hash the provided string returning the digest as a hex string.

``sha256()``
^^^^^^^^^^^^

Returns a SHA-256 hasher that can be updated multiple times, for
example::

local hashing = require("suricata.hashing")
hasher = hashing.sha256()
hasher.update("www.suricata")
hasher.update(".io")
hash = hasher.finalize_to_hex()

The methods on the hasher object include:

* ``update(string)``: Add more data to the hasher
* ``finalize()``: Finalize the hash returning the hash as a byte string
* ``finalize_to_hex()``: Finalize the hash returning the has as a hex string

SHA-1
~~~~~

``sha1_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^^

SHA-1 hash the provided string returning the digest as bytes.

``sha1_hex_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^^^

SHA-1 hash the provided string returning the digest as a hex string.

``sha1()``
^^^^^^^^^^

Returns a SHA-1 hasher that can be updated multiple times, for
example::

local hashing = require("suricata.hashing")
hasher = hashing.sha1()
hasher.update("www.suricata")
hasher.update(".io")
hash = hasher.finalize_to_hex()

The methods on the hasher object include:

* ``update(string)``: Add more data to the hasher
* ``finalize()``: Finalize the hash returning the hash as a byte string
* ``finalize_to_hex()``: Finalize the hash returning the has as a hex string

MD5
~~~

``md5_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^

MD5 hash the provided string returning the digest as bytes.

``md5_hex_digest(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^^

MD5 hash the provided string returning the digest as a hex string.

``md5()``
^^^^^^^^^

Returns a MD5 hasher that can be updated multiple times, for example::

local hashing = require("suricata.hashing")
hasher = hashing.md5()
hasher.update("www.suricata")
hasher.update(".io")
hash = hasher.finalize_to_hex()

The methods on the hasher object include:

* ``update(string)``: Add more data to the hasher
* ``finalize()``: Finalize the hash returning the hash as a byte string
* ``finalize_to_hex()``: Finalize the hash returning the hash as a hex string
11 changes: 11 additions & 0 deletions doc/userguide/lua/libs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Lua Libraries
=============

Suricata provides Lua extensions, or libraries to Lua scripts with the
``require`` keyword. These extensions are particularly important in
Lua rules as Lua rules are executed in a restricted sandbox
environment without access to additional modules.

.. toctree::

hashlib
86 changes: 71 additions & 15 deletions rust/src/ffi/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ use sha1::Sha1;
use sha2::Sha256;
use std::os::raw::c_char;

pub const SC_SHA1_LEN: usize = 20;
pub const SC_SHA256_LEN: usize = 32;
pub const SC_SHA1_LEN: usize = 20;
pub const SC_MD5_LEN: usize = 16;

// Length of hex digests without trailing NUL.
pub const SC_MD5_HEX_LEN: usize = 32;
pub const SC_SHA256_HEX_LEN: usize = 64;
pub const SC_SHA1_HEX_LEN: usize = 40;
pub const SC_MD5_HEX_LEN: usize = 32;

// Wrap the Rust Sha256 in a new type named SCSha256 to give this type
// the "SC" prefix. The one drawback is we must access the actual context
Expand Down Expand Up @@ -59,11 +61,13 @@ pub unsafe extern "C" fn SCSha256Finalize(hasher: &mut SCSha256, out: *mut u8, l
/// But even given the notes, this appears to be faster than the equivalent that we
/// did in C using NSS.
#[no_mangle]
pub unsafe extern "C" fn SCSha256FinalizeToHex(hasher: &mut SCSha256, out: *mut c_char, len: u32) {
pub unsafe extern "C" fn SCSha256FinalizeToHex(
hasher: &mut SCSha256, out: *mut c_char, len: u32,
) -> bool {
let hasher: Box<SCSha256> = Box::from_raw(hasher);
let result = hasher.0.finalize();
let hex = format!("{:x}", &result);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

/// Free an unfinalized Sha256 context.
Expand All @@ -87,6 +91,16 @@ pub unsafe extern "C" fn SCSha256HashBuffer(
return true;
}

#[no_mangle]
pub unsafe extern "C" fn SCSha256HashBufferToHex(
buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
) -> bool {
let data = std::slice::from_raw_parts(buf, buf_len as usize);
let hash = Sha256::new().chain(data).finalize();
let hex = format!("{:x}", &hash);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

// Start of SHA1 C bindings.

pub struct SCSha1(Sha1);
Expand All @@ -108,6 +122,16 @@ pub unsafe extern "C" fn SCSha1Finalize(hasher: &mut SCSha1, out: *mut u8, len:
finalize(hasher.0, out, len);
}

#[no_mangle]
pub unsafe extern "C" fn SCSha1FinalizeToHex(
hasher: &mut SCSha1, out: *mut c_char, len: u32,
) -> bool {
let hasher: Box<SCSha1> = Box::from_raw(hasher);
let result = hasher.0.finalize();
let hex = format!("{:x}", &result);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

/// Free an unfinalized Sha1 context.
#[no_mangle]
pub unsafe extern "C" fn SCSha1Free(hasher: &mut SCSha1) {
Expand All @@ -129,6 +153,16 @@ pub unsafe extern "C" fn SCSha1HashBuffer(
return true;
}

#[no_mangle]
pub unsafe extern "C" fn SCSha1HashBufferToHex(
buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
) -> bool {
let data = std::slice::from_raw_parts(buf, buf_len as usize);
let hash = Sha1::new().chain(data).finalize();
let hex = format!("{:x}", &hash);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

// Start of MD5 C bindings.

pub struct SCMd5(Md5);
Expand Down Expand Up @@ -157,11 +191,13 @@ pub unsafe extern "C" fn SCMd5Finalize(hasher: &mut SCMd5, out: *mut u8, len: u3
///
/// Consumes the hash context and cannot be re-used.
#[no_mangle]
pub unsafe extern "C" fn SCMd5FinalizeToHex(hasher: &mut SCMd5, out: *mut c_char, len: u32) {
pub unsafe extern "C" fn SCMd5FinalizeToHex(
hasher: &mut SCMd5, out: *mut c_char, len: u32,
) -> bool {
let hasher: Box<SCMd5> = Box::from_raw(hasher);
let result = hasher.0.finalize();
let hex = format!("{:x}", &result);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

/// Free an unfinalized Sha1 context.
Expand All @@ -172,22 +208,28 @@ pub unsafe extern "C" fn SCMd5Free(hasher: &mut SCMd5) {
}

#[no_mangle]
pub unsafe extern "C" fn SCMd5HashBuffer(buf: *const u8, buf_len: u32, out: *mut u8, len: u32) {
pub unsafe extern "C" fn SCMd5HashBuffer(
buf: *const u8, buf_len: u32, out: *mut u8, len: u32,
) -> bool {
if len as usize != SC_MD5_LEN {
return false;
}
let data = std::slice::from_raw_parts(buf, buf_len as usize);
let output = std::slice::from_raw_parts_mut(out, len as usize);
let hash = Md5::new().chain(data).finalize();
output.copy_from_slice(&hash);
true
}

/// C binding for a function to MD5 hash a single buffer to a hex string.
#[no_mangle]
pub unsafe extern "C" fn SCMd5HashBufferToHex(
buf: *const u8, buf_len: u32, out: *mut c_char, len: u32,
) {
) -> bool {
let data = std::slice::from_raw_parts(buf, buf_len as usize);
let hash = Md5::new().chain(data).finalize();
let hex = format!("{:x}", &hash);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize);
crate::ffi::strings::copy_to_c_char(hex, out, len as usize)
}

// Functions that are generic over Digest. For the most part the C bindings are
Expand Down Expand Up @@ -230,9 +272,18 @@ mod test {
SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32);
let hex = [0_u8; SC_SHA256_HEX_LEN + 1];
SCSha256FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_SHA256_HEX_LEN + 1) as u32);
let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
assert_eq!(string, "22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153");
SCSha256FinalizeToHex(
hasher,
hex.as_ptr() as *mut c_char,
(SC_SHA256_HEX_LEN + 1) as u32,
);
let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char)
.to_str()
.unwrap();
assert_eq!(
string,
"22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153"
);
}
}

Expand All @@ -250,10 +301,15 @@ mod test {
SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32);
let hex = [0_u8; SC_MD5_HEX_LEN + 1];
SCMd5FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_MD5_HEX_LEN + 1) as u32);
let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap();
SCMd5FinalizeToHex(
hasher,
hex.as_ptr() as *mut c_char,
(SC_MD5_HEX_LEN + 1) as u32,
);
let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char)
.to_str()
.unwrap();
assert_eq!(string, "5216ddcc58e8dade5256075e77f642da");
}
}

}
4 changes: 4 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,14 @@ noinst_HEADERS = \
util-landlock.h \
util-logopenfile.h \
util-log-redis.h \
util-lua-builtins.h \
util-lua-common.h \
util-lua-dataset.h \
util-lua-dnp3.h \
util-lua-dnp3-objects.h \
util-lua-dns.h \
util-lua.h \
util-lua-hashlib.h \
util-lua-hassh.h \
util-lua-http.h \
util-lua-ja3.h \
Expand Down Expand Up @@ -1055,11 +1057,13 @@ libsuricata_c_a_SOURCES = \
util-logopenfile.c \
util-log-redis.c \
util-lua.c \
util-lua-builtins.c \
util-lua-common.c \
util-lua-dataset.c \
util-lua-dnp3.c \
util-lua-dnp3-objects.c \
util-lua-dns.c \
util-lua-hashlib.c \
util-lua-hassh.c \
util-lua-http.c \
util-lua-ja3.c \
Expand Down
3 changes: 3 additions & 0 deletions src/output-lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "suricata-common.h"
#include "output-lua.h"

#include "util-lua-builtins.h"
#include "util-print.h"
#include "util-unittest.h"
#include "util-debug.h"
Expand Down Expand Up @@ -417,6 +418,7 @@ static int LuaScriptInit(const char *filename, LogLuaScriptOptions *options) {
if (luastate == NULL)
goto error;
luaL_openlibs(luastate);
SCLuaRequirefBuiltIns(luastate);

int status = luaL_loadfile(luastate, filename);
if (status) {
Expand Down Expand Up @@ -551,6 +553,7 @@ static lua_State *LuaScriptSetup(const char *filename)
}

luaL_openlibs(luastate);
SCLuaRequirefBuiltIns(luastate);

int status = luaL_loadfile(luastate, filename);
if (status) {
Expand Down
55 changes: 55 additions & 0 deletions src/util-lua-builtins.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* Copyright (C) 2025 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

#include "suricata-common.h"
#include "util-lua-builtins.h"
#include "util-lua-hashlib.h"
#include "util-lua-dataset.h"

#include "lauxlib.h"

static const luaL_Reg builtins[] = {
{ "suricata.hashlib", SCLuaLoadHashlib },
{ "suricata.dataset", LuaLoadDatasetLib },
{ NULL, NULL },
};

/**
* \brief Load a Suricata built-in module in a sand-boxed environment.
*/
bool SCLuaLoadBuiltIns(lua_State *L, const char *name)
{
for (const luaL_Reg *lib = builtins; lib->name; lib++) {
if (strcmp(name, lib->name) == 0) {
lib->func(L);
return true;
}
}
return false;
}

/**
* \brief Register Suricata built-in modules for loading in a
* non-sandboxed environment.
*/
void SCLuaRequirefBuiltIns(lua_State *L)
{
for (const luaL_Reg *lib = builtins; lib->name; lib++) {
luaL_requiref(L, lib->name, lib->func, 0);
lua_pop(L, 1);
}
}
Loading
Loading