diff --git a/doc/userguide/lua/index.rst b/doc/userguide/lua/index.rst index d6bbf70964bd..fcb558408f10 100644 --- a/doc/userguide/lua/index.rst +++ b/doc/userguide/lua/index.rst @@ -5,3 +5,4 @@ Lua support lua-usage lua-functions + libs/index diff --git a/doc/userguide/lua/libs/hashlib.rst b/doc/userguide/lua/libs/hashlib.rst new file mode 100644 index 000000000000..be8fe77e5172 --- /dev/null +++ b/doc/userguide/lua/libs/hashlib.rst @@ -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 diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst new file mode 100644 index 000000000000..09e67ca9a600 --- /dev/null +++ b/doc/userguide/lua/libs/index.rst @@ -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 diff --git a/rust/src/ffi/hashing.rs b/rust/src/ffi/hashing.rs index 0a62772c1cf1..b1d1c1d4b458 100644 --- a/rust/src/ffi/hashing.rs +++ b/rust/src/ffi/hashing.rs @@ -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 @@ -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 = 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. @@ -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); @@ -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 = 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) { @@ -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); @@ -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 = 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. @@ -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 @@ -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" + ); } } @@ -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"); } } - } diff --git a/src/Makefile.am b/src/Makefile.am index 615816953542..72d0fe6276d0 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ @@ -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 \ diff --git a/src/output-lua.c b/src/output-lua.c index 4cd1f5924425..001404b97f0d 100644 --- a/src/output-lua.c +++ b/src/output-lua.c @@ -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" @@ -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) { @@ -551,6 +553,7 @@ static lua_State *LuaScriptSetup(const char *filename) } luaL_openlibs(luastate); + SCLuaRequirefBuiltIns(luastate); int status = luaL_loadfile(luastate, filename); if (status) { diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c new file mode 100644 index 000000000000..c826df4d9f6d --- /dev/null +++ b/src/util-lua-builtins.c @@ -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); + } +} diff --git a/src/util-lua-builtins.h b/src/util-lua-builtins.h new file mode 100644 index 000000000000..8f1865467663 --- /dev/null +++ b/src/util-lua-builtins.h @@ -0,0 +1,26 @@ +/* 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. + */ + +#ifndef SURICATA_UTIL_LUA_BUILTINS_H +#define SURICATA_UTIL_LUA_BUILTINS_H + +#include "lua.h" + +bool SCLuaLoadBuiltIns(lua_State *L, const char *name); +void SCLuaRequirefBuiltIns(lua_State *L); + +#endif /* SURICATA_UTIL_LUA_BUILTINS_H */ diff --git a/src/util-lua-dataset.c b/src/util-lua-dataset.c index 6af9ba5901e7..8d1956172637 100644 --- a/src/util-lua-dataset.c +++ b/src/util-lua-dataset.c @@ -120,11 +120,13 @@ static const luaL_Reg datasetlib[] = { }; // clang-format on -void LuaLoadDatasetLib(lua_State *luastate) +int LuaLoadDatasetLib(lua_State *luastate) { luaL_newmetatable(luastate, "dataset::metatable"); lua_pushvalue(luastate, -1); lua_setfield(luastate, -2, "__index"); luaL_setfuncs(luastate, datasetlib, 0); luaL_newlib(luastate, datasetlib); + + return 1; } diff --git a/src/util-lua-dataset.h b/src/util-lua-dataset.h index 2bf0efdddc75..ad551dd11c42 100644 --- a/src/util-lua-dataset.h +++ b/src/util-lua-dataset.h @@ -20,6 +20,6 @@ #include "lua.h" -void LuaLoadDatasetLib(lua_State *luastate); +int LuaLoadDatasetLib(lua_State *luastate); #endif /* SURICATA_UTIL_LUA_DATASET_H */ diff --git a/src/util-lua-hashlib.c b/src/util-lua-hashlib.c new file mode 100644 index 000000000000..9c8bf7dab2b3 --- /dev/null +++ b/src/util-lua-hashlib.c @@ -0,0 +1,429 @@ +/* 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. + */ + +/** + * \file + * + * Hashing library for Lua. + * + * Usage: + * + * local hashing = require("suricata.hashing") + * + * -- One shot hash + * hash = hashing.sha256_digest("www.suricata.io") + * + * -- One shot hash to hex + * hash = hashing.sha256_hexdigest("www.suricata.io") + * + * -- Incremental hashing + * hasher = hashing.sha256() + * hasher:update("www.") + * hasher:update("suricata.io") + * hash = hasher:finalize() + * + * Support hashes: sha256, sha1, md5 + */ + +#include "util-lua-hashlib.h" + +#include "lauxlib.h" +#include "rust-bindings.h" + +#define SHA256_MT "suricata:hashlib:sha256" +#define SHA1_MT "suricata:hashlib:sha1" +#define MD5_MT "suricata:hashlib:md5" + +/** + * \brief Create a new SHA-256 hash instance. + */ +static int LuaHashLibSha256New(lua_State *L) +{ + struct SCSha256 **hasher = lua_newuserdata(L, sizeof(struct SCSha256 *)); + if (hasher == NULL) { + return luaL_error(L, "failed to allocate userdata for sha256"); + } + *hasher = SCSha256New(); + luaL_getmetatable(L, SHA256_MT); + lua_setmetatable(L, -2); + return 1; +} + +/** + * \brief Add more data to an existing SHA-256 hash instance. + */ +static int LuaHashLibSha256Update(lua_State *L) +{ + struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + size_t data_len; + const char *data = luaL_checklstring(L, 2, &data_len); + SCSha256Update(*hasher, (const uint8_t *)data, data_len); + return 0; +} + +static int LuaHashLibSha256Finalize(lua_State *L) +{ + struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + uint8_t hash[SC_SHA256_LEN]; + SCSha256Finalize(*hasher, hash, sizeof(hash)); + lua_pushlstring(L, (const char *)hash, sizeof(hash)); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibSha256FinalizeToHex(lua_State *L) +{ + struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + char hash[SC_SHA256_HEX_LEN + 1]; + if (!SCSha256FinalizeToHex(*hasher, hash, sizeof(hash))) { + *hasher = NULL; + return luaL_error(L, "sha256 hashing failed"); + } + + lua_pushstring(L, (const char *)hash); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibSha256Digest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + size_t output_len = SC_SHA256_LEN; + uint8_t output[output_len]; + if (!SCSha256HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, output_len)) { + return luaL_error(L, "sha256 hashing failed"); + } + + lua_pushlstring(L, (const char *)output, output_len); + + return 1; +} + +static int LuaHashLibSha256HexDigest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + char output[SC_SHA256_HEX_LEN + 1]; + if (!SCSha256HashBufferToHex((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) { + return luaL_error(L, "sha256 hashing failed"); + } + + lua_pushstring(L, (const char *)output); + return 1; +} + +static int LuaHashLibSha256Gc(lua_State *L) +{ + struct SCSha256 **hasher = luaL_checkudata(L, 1, SHA256_MT); + if (hasher && *hasher) { + SCSha256Free(*hasher); + } + return 0; +} + +static int LuaHashLibSha1New(lua_State *L) +{ + struct SCSha1 **hasher = lua_newuserdata(L, sizeof(struct SCSha1 *)); + if (hasher == NULL) { + return luaL_error(L, "failed to allocate userdata for sha1"); + } + *hasher = SCSha1New(); + luaL_getmetatable(L, SHA1_MT); + lua_setmetatable(L, -2); + return 1; +} + +static int LuaHashLibSha1Update(lua_State *L) +{ + struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + size_t data_len; + const char *data = luaL_checklstring(L, 2, &data_len); + SCSha1Update(*hasher, (const uint8_t *)data, data_len); + return 0; +} + +static int LuaHashLibSha1Finalize(lua_State *L) +{ + struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + uint8_t hash[SC_SHA1_LEN]; + SCSha1Finalize(*hasher, hash, sizeof(hash)); + lua_pushlstring(L, (const char *)hash, sizeof(hash)); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibSha1FinalizeToHex(lua_State *L) +{ + struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + char hash[SC_SHA1_HEX_LEN + 1]; + if (!SCSha1FinalizeToHex(*hasher, hash, sizeof(hash))) { + *hasher = NULL; + return luaL_error(L, "sha1 hashing failed"); + } + + lua_pushstring(L, (const char *)hash); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibSha1Digest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + uint8_t output[SC_SHA1_LEN]; + if (!SCSha1HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) { + return luaL_error(L, "sha1 hashing failed"); + } + + lua_pushlstring(L, (const char *)output, sizeof(output)); + return 1; +} + +static int LuaHashLibSha1HexDigest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + char output[SC_SHA1_HEX_LEN + 1]; + if (!SCSha1HashBufferToHex((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) { + return luaL_error(L, "sha1 hashing failed"); + } + + lua_pushstring(L, (const char *)output); + return 1; +} + +static int LuaHashLibSha1Gc(lua_State *L) +{ + struct SCSha1 **hasher = luaL_checkudata(L, 1, SHA1_MT); + if (hasher && *hasher) { + SCSha1Free(*hasher); + } + return 0; +} + +static int LuaHashLibMd5New(lua_State *L) +{ + struct SCMd5 **hasher = lua_newuserdata(L, sizeof(struct SCMd5 *)); + if (hasher == NULL) { + return luaL_error(L, "failed to allocate userdata for sha1"); + } + *hasher = SCMd5New(); + luaL_getmetatable(L, MD5_MT); + lua_setmetatable(L, -2); + return 1; +} + +static int LuaHashLibMd5Update(lua_State *L) +{ + struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + size_t data_len; + const char *data = luaL_checklstring(L, 2, &data_len); + SCMd5Update(*hasher, (const uint8_t *)data, data_len); + return 0; +} + +static int LuaHashLibMd5Finalize(lua_State *L) +{ + struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + uint8_t hash[SC_MD5_LEN]; + SCMd5Finalize(*hasher, hash, sizeof(hash)); + lua_pushlstring(L, (const char *)hash, sizeof(hash)); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibMd5FinalizeToHex(lua_State *L) +{ + struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT); + if (hasher == NULL) { + return luaL_error(L, "null userdata"); + } + + char hash[SC_MD5_HEX_LEN + 1]; + if (!SCMd5FinalizeToHex(*hasher, hash, sizeof(hash))) { + *hasher = NULL; + return luaL_error(L, "md5 hashing failed"); + } + + lua_pushstring(L, (const char *)hash); + + // Finalize consumes the hasher, so set to NULL so its not free'd + // during garbage collection. + *hasher = NULL; + + return 1; +} + +static int LuaHashLibMd5Digest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + uint8_t output[SC_MD5_LEN]; + if (!SCMd5HashBuffer((uint8_t *)input, (uint32_t)buf_len, output, sizeof(output))) { + return luaL_error(L, "md5 hashing failed"); + } + + lua_pushlstring(L, (const char *)output, sizeof(output)); + return 1; +} + +static int LuaHashLibMd5HexDigest(lua_State *L) +{ + size_t buf_len; + const char *input = luaL_checklstring(L, 1, &buf_len); + + char output[SC_MD5_HEX_LEN + 1]; + if (!SCMd5HashBufferToHex((uint8_t *)input, (size_t)buf_len, output, sizeof(output))) { + return luaL_error(L, "md5 hashing failed"); + } + + lua_pushstring(L, (const char *)output); + return 1; +} + +static int LuaHashLibMd5Gc(lua_State *L) +{ + struct SCMd5 **hasher = luaL_checkudata(L, 1, MD5_MT); + if (hasher && *hasher) { + SCMd5Free(*hasher); + } + return 0; +} + +static const struct luaL_Reg hashlib[] = { + // clang-format off + { "sha256_digest", LuaHashLibSha256Digest }, + { "sha256_hexdigest", LuaHashLibSha256HexDigest }, + { "sha256", LuaHashLibSha256New }, + { "sha1_digest", LuaHashLibSha1Digest }, + { "sha1_hexdigest", LuaHashLibSha1HexDigest }, + { "sha1", LuaHashLibSha1New }, + { "md5_digest", LuaHashLibMd5Digest }, + { "md5_hexdigest", LuaHashLibMd5HexDigest }, + { "md5", LuaHashLibMd5New }, + { NULL, NULL }, + // clang-format on +}; + +static const struct luaL_Reg sha256_meta[] = { + // clang-format off + { "update", LuaHashLibSha256Update }, + { "finalize", LuaHashLibSha256Finalize }, + { "finalize_to_hex", LuaHashLibSha256FinalizeToHex }, + { "__gc", LuaHashLibSha256Gc }, + { NULL, NULL }, + // clang-format on +}; + +static const struct luaL_Reg sha1_meta[] = { + // clang-format off + { "update", LuaHashLibSha1Update }, + { "finalize", LuaHashLibSha1Finalize }, + { "finalize_to_hex", LuaHashLibSha1FinalizeToHex }, + { "__gc", LuaHashLibSha1Gc }, + { NULL, NULL }, + // clang-format on +}; + +static const struct luaL_Reg md5_meta[] = { + // clang-format off + { "update", LuaHashLibMd5Update }, + { "finalize", LuaHashLibMd5Finalize }, + { "finalize_to_hex", LuaHashLibMd5FinalizeToHex }, + { "__gc", LuaHashLibMd5Gc }, + { NULL, NULL }, + // clang-format on +}; + +int SCLuaLoadHashlib(lua_State *L) +{ + luaL_newmetatable(L, SHA256_MT); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, sha256_meta, 0); + + luaL_newmetatable(L, SHA1_MT); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, sha1_meta, 0); + + luaL_newmetatable(L, MD5_MT); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, md5_meta, 0); + + luaL_newlib(L, hashlib); + + return 1; +} diff --git a/src/util-lua-hashlib.h b/src/util-lua-hashlib.h new file mode 100644 index 000000000000..3686840b8b95 --- /dev/null +++ b/src/util-lua-hashlib.h @@ -0,0 +1,26 @@ +/* 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. + */ + +#ifndef SURICATA_UTIL_LUA_HASHLIB_H +#define SURICATA_UTIL_LUA_HASHLIB_H + +#include "rust.h" +#include "lua.h" + +int SCLuaLoadHashlib(lua_State *l); + +#endif /* SURICATA_UTIL_LUA_HASHLIB_H */ diff --git a/src/util-lua-sandbox.c b/src/util-lua-sandbox.c index 4c4838418f39..ffe8e5b5adc8 100644 --- a/src/util-lua-sandbox.c +++ b/src/util-lua-sandbox.c @@ -29,9 +29,8 @@ #include "util-debug.h" #include "util-debug.h" -#include "util-validate.h" #include "util-lua-sandbox.h" -#include "util-lua-dataset.h" +#include "util-lua-builtins.h" #define SANDBOX_CTX "SANDBOX_CTX" @@ -264,8 +263,7 @@ static int SCLuaSbRequire(lua_State *L) { const char *module_name = luaL_checkstring(L, 1); - if (strcmp(module_name, "suricata.dataset") == 0) { - LuaLoadDatasetLib(L); + if (SCLuaLoadBuiltIns(L, module_name)) { return 1; }