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

next/719/20250213/v1 #12579

Merged
merged 5 commits into from
Feb 14, 2025
Merged
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
55 changes: 55 additions & 0 deletions doc/userguide/lua/libs/base64.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Base64
------

Base64 functions are exposed to Lua scripts with the
``suricata.base64`` library. For example::

local base64 = require("suricata.base64")

Functions
~~~~~~~~~

``encode(string)``
^^^^^^^^^^^^^^^^^^

Encode a buffer with standard base64 encoding. This standard encoding
includes padding.

``decode(string)``
^^^^^^^^^^^^^^^^^^

Decode a base64 string that contains padding.

``encode_nopad(string)``
^^^^^^^^^^^^^^^^^^^^^^^^

Encode a buffer with standard base64 encoding but don't include any
padding.

``decode_nopad(string)``
^^^^^^^^^^^^^^^^^^^^^^^^

Decode a base64 string that contains no padding.

``decode_padopt(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^

Decode a base64 string that may or may not contain trailing padding.

``decode_rfc2045(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^^

Decode an RFC 2045 formatted base64 string.

``decode_rfc4648(string)``
^^^^^^^^^^^^^^^^^^^^^^^^^^

Decode an RFC 4648 formatted base64 string.

Implementation Details
~~~~~~~~~~~~~~~~~~~~~~

The base64 functions provided come from the Rust base64 library
documented at https://docs.rs/base64 and correspond to the
``STANDARD`` and ``STANDARD_NO_PAD`` base64 engines provided in that
library.
1 change: 1 addition & 0 deletions doc/userguide/lua/libs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ environment without access to additional modules.

.. toctree::

base64
hashlib
packetlib
4 changes: 2 additions & 2 deletions rust/Cargo.lock.in

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/Cargo.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ time = "~0.3.36"

suricata-derive = { path = "./derive", version = "@PACKAGE_VERSION@" }

suricata-lua-sys = { version = "0.1.0-alpha.5" }
suricata-lua-sys = { version = "0.1.0-alpha.6" }

[dev-dependencies]
test-case = "~3.3.1"
28 changes: 14 additions & 14 deletions rust/src/detect/transform_base64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ use crate::detect::error::RuleParseError;
use crate::detect::parser::{parse_var, take_until_whitespace, ResultValue};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use crate::ffi::base64::Base64Mode;
use crate::ffi::base64::SCBase64Mode;

use nom7::bytes::complete::tag;
use nom7::character::complete::multispace0;
use nom7::sequence::preceded;
use nom7::{Err, IResult};
use std::str;

pub const TRANSFORM_FROM_BASE64_MODE_DEFAULT: Base64Mode = Base64Mode::Base64ModeRFC4648;
pub const TRANSFORM_FROM_BASE64_MODE_DEFAULT: SCBase64Mode = SCBase64Mode::SCBase64ModeRFC4648;

const DETECT_TRANSFORM_BASE64_MAX_PARAM_COUNT: usize = 3;
pub const DETECT_TRANSFORM_BASE64_FLAG_MODE: u8 = 0x01;
Expand All @@ -46,7 +46,7 @@ pub struct SCDetectTransformFromBase64Data {
nbytes_str: *const c_char,
offset: u32,
offset_str: *const c_char,
mode: Base64Mode,
mode: SCBase64Mode,
}

impl Drop for SCDetectTransformFromBase64Data {
Expand Down Expand Up @@ -82,11 +82,11 @@ impl SCDetectTransformFromBase64Data {
}
}

fn get_mode_value(value: &str) -> Option<Base64Mode> {
fn get_mode_value(value: &str) -> Option<SCBase64Mode> {
let res = match value {
"rfc4648" => Some(Base64Mode::Base64ModeRFC4648),
"rfc2045" => Some(Base64Mode::Base64ModeRFC2045),
"strict" => Some(Base64Mode::Base64ModeStrict),
"rfc4648" => Some(SCBase64Mode::SCBase64ModeRFC4648),
"rfc2045" => Some(SCBase64Mode::SCBase64ModeRFC2045),
"strict" => Some(SCBase64Mode::SCBase64ModeStrict),
_ => None,
};

Expand Down Expand Up @@ -269,7 +269,7 @@ mod tests {
nbytes_str: &str,
offset: u32,
offset_str: &str,
mode: Base64Mode,
mode: SCBase64Mode,
flags: u8,
) {
let tbd = SCDetectTransformFromBase64Data {
Expand Down Expand Up @@ -327,7 +327,7 @@ mod tests {
assert_eq!(val, tbd);

tbd.flags = DETECT_TRANSFORM_BASE64_FLAG_MODE;
tbd.mode = Base64Mode::Base64ModeRFC2045;
tbd.mode = SCBase64Mode::SCBase64ModeRFC2045;
tbd.offset = 0;
tbd.nbytes = 0;
let (_, val) = parse_transform_base64("mode rfc2045").unwrap();
Expand All @@ -344,7 +344,7 @@ mod tests {
"",
3933,
"",
Base64Mode::Base64ModeStrict,
SCBase64Mode::SCBase64ModeStrict,
DETECT_TRANSFORM_BASE64_FLAG_NBYTES
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET
| DETECT_TRANSFORM_BASE64_FLAG_MODE,
Expand All @@ -356,7 +356,7 @@ mod tests {
"",
3933,
"",
Base64Mode::Base64ModeRFC2045,
SCBase64Mode::SCBase64ModeRFC2045,
DETECT_TRANSFORM_BASE64_FLAG_NBYTES
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET
| DETECT_TRANSFORM_BASE64_FLAG_MODE,
Expand All @@ -368,7 +368,7 @@ mod tests {
"",
3933,
"",
Base64Mode::Base64ModeRFC4648,
SCBase64Mode::SCBase64ModeRFC4648,
DETECT_TRANSFORM_BASE64_FLAG_NBYTES
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET
| DETECT_TRANSFORM_BASE64_FLAG_MODE,
Expand All @@ -380,7 +380,7 @@ mod tests {
"",
0,
"var",
Base64Mode::Base64ModeRFC4648,
SCBase64Mode::SCBase64ModeRFC4648,
DETECT_TRANSFORM_BASE64_FLAG_NBYTES
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET_VAR
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET
Expand All @@ -393,7 +393,7 @@ mod tests {
"var",
3933,
"",
Base64Mode::Base64ModeRFC4648,
SCBase64Mode::SCBase64ModeRFC4648,
DETECT_TRANSFORM_BASE64_FLAG_NBYTES
| DETECT_TRANSFORM_BASE64_FLAG_NBYTES_VAR
| DETECT_TRANSFORM_BASE64_FLAG_OFFSET
Expand Down
94 changes: 71 additions & 23 deletions rust/src/ffi/base64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,25 @@
* 02110-1301, USA.
*/

use crate::utils::base64::{decode_rfc4648, decode_rfc2045, get_decoded_buffer_size, Decoder};
use crate::utils::base64::{decode_rfc2045, decode_rfc4648, get_decoded_buffer_size, Decoder};
use base64::{
engine::general_purpose::{STANDARD, STANDARD_NO_PAD},
Engine,
};
use libc::c_ulong;
use std::os::raw::c_uchar;
use base64::{Engine, engine::general_purpose::STANDARD};

#[repr(C)]
#[allow(non_camel_case_types)]
pub enum Base64ReturnCode {
pub enum SCBase64ReturnCode {
SC_BASE64_OK = 0,
SC_BASE64_INVALID_ARG,
SC_BASE64_OVERFLOW,
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Base64Mode {
pub enum SCBase64Mode {
/* If the following strings were to be passed to the decoder with RFC2045 mode,
* the results would be as follows. See the unittest B64TestVectorsRFC2045 in
* src/util-base64.c
Expand All @@ -46,8 +49,8 @@ pub enum Base64Mode {
* BASE64("foobar") = "Zm$9vYm.Fy" # According to RFC 2045, All line breaks or *other
* characters* not found in base64 alphabet must be ignored by decoding software
* */
Base64ModeRFC2045 = 0, /* SPs are allowed during transfer but must be skipped by Decoder */
Base64ModeStrict,
SCBase64ModeRFC2045 = 0, /* SPs are allowed during transfer but must be skipped by Decoder */
SCBase64ModeStrict,
/* If the following strings were to be passed to the decoder with RFC4648 mode,
* the results would be as follows. See the unittest B64TestVectorsRFC4648 in
* src/util-base64.c
Expand All @@ -63,11 +66,17 @@ pub enum Base64Mode {
* BASE64("f") = "Zm$9vYm.Fy" <-- Notice how the processing stops once an invalid char is
* encountered
* */
Base64ModeRFC4648, /* reject the encoded data if it contains characters outside the base alphabet */
SCBase64ModeRFC4648, /* reject the encoded data if it contains characters outside the base alphabet */

/// Standard base64 without padding, and strict about it.
SCBase64ModeNoPad,

/// Standard base64 with optional padding: decode only.
SCBase64ModePadOpt,
}

#[no_mangle]
pub unsafe extern "C" fn Base64DecodeBufferSize(input_len: u32) -> u32 {
pub unsafe extern "C" fn SCBase64DecodeBufferSize(input_len: u32) -> u32 {
return get_decoded_buffer_size(input_len);
}

Expand All @@ -76,10 +85,11 @@ pub unsafe extern "C" fn Base64DecodeBufferSize(input_len: u32) -> u32 {
/// This method exposes the Rust base64 decoder to C and should not be called from
/// Rust code.
///
/// It allows decoding in the modes described by ``Base64Mode`` enum.
/// It allows decoding in the modes described by ``SCBase64Mode`` enum.
#[no_mangle]
pub unsafe extern "C" fn Base64Decode(
input: *const u8, len: usize, mode: Base64Mode, output: *mut u8) -> u32 {
pub unsafe extern "C" fn SCBase64Decode(
input: *const u8, len: usize, mode: SCBase64Mode, output: *mut u8,
) -> u32 {
if input.is_null() || len == 0 {
return 0;
}
Expand All @@ -89,60 +99,98 @@ pub unsafe extern "C" fn Base64Decode(
let mut num_decoded: u32 = 0;
let mut decoder = Decoder::new();
match mode {
Base64Mode::Base64ModeRFC2045 => {
SCBase64Mode::SCBase64ModeRFC2045 => {
if decode_rfc2045(&mut decoder, in_vec, out_vec, &mut num_decoded).is_err() {
debug_validate_bug_on!(num_decoded >= len as u32);
return num_decoded;
}
}
Base64Mode::Base64ModeRFC4648 => {
SCBase64Mode::SCBase64ModeRFC4648 => {
if decode_rfc4648(&mut decoder, in_vec, out_vec, &mut num_decoded).is_err() {
debug_validate_bug_on!(num_decoded >= len as u32);
return num_decoded;
}
}
Base64Mode::Base64ModeStrict => {
SCBase64Mode::SCBase64ModeStrict => {
if let Ok(decoded_len) = STANDARD.decode_slice(in_vec, out_vec) {
num_decoded = decoded_len as u32;
}
}
SCBase64Mode::SCBase64ModeNoPad => {
if let Ok(decoded_len) = STANDARD_NO_PAD.decode_slice(in_vec, out_vec) {
num_decoded = decoded_len as u32;
}
}
SCBase64Mode::SCBase64ModePadOpt => {
let config = base64::engine::GeneralPurposeConfig::new()
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent);
let decoder = base64::engine::GeneralPurpose::new(&base64::alphabet::STANDARD, config);
if let Ok(decoded_len) = decoder.decode_slice(in_vec, out_vec) {
num_decoded = decoded_len as u32;
}
}
}

debug_validate_bug_on!(num_decoded >= len as u32);
return num_decoded;
}

/// Base64 encode a buffer.
/// Base64 encode a buffer with a provided mode.
///
/// This method exposes the Rust base64 encoder to C and should not be called from
/// Rust code.
///
/// The output parameter must be an allocated buffer of at least the size returned
/// from Base64EncodeBufferSize for the input_len, and this length must be provided
/// from SCBase64EncodeBufferSize for the input_len, and this length must be provided
/// in the output_len variable.
#[no_mangle]
pub unsafe extern "C" fn Base64Encode(
pub unsafe extern "C" fn SCBase64EncodeWithMode(
input: *const u8, input_len: c_ulong, output: *mut c_uchar, output_len: *mut c_ulong,
) -> Base64ReturnCode {
mode: SCBase64Mode,
) -> SCBase64ReturnCode {
if input.is_null() || output.is_null() || output_len.is_null() {
return Base64ReturnCode::SC_BASE64_INVALID_ARG;
return SCBase64ReturnCode::SC_BASE64_INVALID_ARG;
}
let input = std::slice::from_raw_parts(input, input_len as usize);
let encoded = STANDARD.encode(input);
let encoded = match mode {
SCBase64Mode::SCBase64ModeNoPad => STANDARD_NO_PAD.encode(input),
_ => STANDARD.encode(input),
};
if encoded.len() + 1 > *output_len as usize {
return Base64ReturnCode::SC_BASE64_OVERFLOW;
return SCBase64ReturnCode::SC_BASE64_OVERFLOW;
}
let output = std::slice::from_raw_parts_mut(&mut *output, *output_len as usize);
output[0..encoded.len()].copy_from_slice(encoded.as_bytes());
output[encoded.len()] = 0;
*output_len = encoded.len() as c_ulong;
Base64ReturnCode::SC_BASE64_OK
SCBase64ReturnCode::SC_BASE64_OK
}

/// Base64 encode a buffer.
///
/// This method exposes the Rust base64 encoder to C and should not be called from
/// Rust code.
///
/// The output parameter must be an allocated buffer of at least the size returned
/// from SCBase64EncodeBufferSize for the input_len, and this length must be provided
/// in the output_len variable.
#[no_mangle]
pub unsafe extern "C" fn SCBase64Encode(
input: *const u8, input_len: c_ulong, output: *mut c_uchar, output_len: *mut c_ulong,
) -> SCBase64ReturnCode {
SCBase64EncodeWithMode(
input,
input_len,
output,
output_len,
SCBase64Mode::SCBase64ModeStrict,
)
}

/// Ratio of output bytes to input bytes for Base64 Encoding is 4:3, hence the
/// required output bytes are 4 * ceil(input_len / 3) and an additional byte for
/// storing the NULL pointer.
#[no_mangle]
pub extern "C" fn Base64EncodeBufferSize(len: c_ulong) -> c_ulong {
pub extern "C" fn SCBase64EncodeBufferSize(len: c_ulong) -> c_ulong {
(4 * ((len) + 2) / 3) + 1
}
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ noinst_HEADERS = \
util-landlock.h \
util-logopenfile.h \
util-log-redis.h \
util-lua-base64lib.h \
util-lua-builtins.h \
util-lua-common.h \
util-lua-dataset.h \
Expand Down Expand Up @@ -1066,6 +1067,7 @@ libsuricata_c_a_SOURCES = \
util-logopenfile.c \
util-log-redis.c \
util-lua.c \
util-lua-base64lib.c \
util-lua-builtins.c \
util-lua-common.c \
util-lua-dataset.c \
Expand Down
Loading
Loading