From eba18fdad39864f82e78a8fb38fdf7ab311bb1d4 Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Thu, 30 Jan 2025 10:29:36 -0600 Subject: [PATCH 1/4] base64: prefix base64 C API with "SC" --- rust/src/detect/transform_base64.rs | 28 ++++++++++----------- rust/src/ffi/base64.rs | 38 ++++++++++++++--------------- src/datasets-string.c | 5 ++-- src/datasets.c | 18 +++++++------- src/detect-base64-decode.c | 2 +- src/detect-transform-base64.c | 16 ++++++------ src/log-tlsstore.c | 7 +++--- src/output-json.c | 4 +-- src/tests/fuzz/fuzz_decodebase64.c | 6 ++--- 9 files changed, 62 insertions(+), 62 deletions(-) diff --git a/rust/src/detect/transform_base64.rs b/rust/src/detect/transform_base64.rs index d97fca2f2115..ed9c96191206 100644 --- a/rust/src/detect/transform_base64.rs +++ b/rust/src/detect/transform_base64.rs @@ -21,7 +21,7 @@ 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; @@ -29,7 +29,7 @@ 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; @@ -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 { @@ -82,11 +82,11 @@ impl SCDetectTransformFromBase64Data { } } -fn get_mode_value(value: &str) -> Option { +fn get_mode_value(value: &str) -> Option { 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, }; @@ -269,7 +269,7 @@ mod tests { nbytes_str: &str, offset: u32, offset_str: &str, - mode: Base64Mode, + mode: SCBase64Mode, flags: u8, ) { let tbd = SCDetectTransformFromBase64Data { @@ -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(); @@ -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, @@ -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, @@ -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, @@ -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 @@ -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 diff --git a/rust/src/ffi/base64.rs b/rust/src/ffi/base64.rs index 64db0cbbb30f..8b07c5fadc1c 100644 --- a/rust/src/ffi/base64.rs +++ b/rust/src/ffi/base64.rs @@ -22,7 +22,7 @@ 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, @@ -30,7 +30,7 @@ pub enum Base64ReturnCode { #[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 @@ -46,8 +46,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 @@ -63,11 +63,11 @@ 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 */ } #[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); } @@ -76,10 +76,10 @@ 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; } @@ -89,19 +89,19 @@ 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; } @@ -118,31 +118,31 @@ pub unsafe extern "C" fn Base64Decode( /// 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 SCBase64Encode( input: *const u8, input_len: c_ulong, output: *mut c_uchar, output_len: *mut c_ulong, -) -> Base64ReturnCode { +) -> 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); 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 } /// 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 } diff --git a/src/datasets-string.c b/src/datasets-string.c index 85fe864f52db..da6e039bc31d 100644 --- a/src/datasets-string.c +++ b/src/datasets-string.c @@ -47,10 +47,9 @@ int StringAsBase64(const void *s, char *out, size_t out_size) { const StringType *str = s; - unsigned long len = Base64EncodeBufferSize(str->len); + unsigned long len = SCBase64EncodeBufferSize(str->len); uint8_t encoded_data[len]; - if (Base64Encode((unsigned char *)str->ptr, str->len, - encoded_data, &len) != SC_BASE64_OK) + if (SCBase64Encode((unsigned char *)str->ptr, str->len, encoded_data, &len) != SC_BASE64_OK) return 0; strlcpy(out, (const char *)encoded_data, out_size); diff --git a/src/datasets.c b/src/datasets.c index 402c7d34fe99..6db46bc56703 100644 --- a/src/datasets.c +++ b/src/datasets.c @@ -517,11 +517,11 @@ static int DatasetLoadString(Dataset *set) if (r == NULL) { line[strlen(line) - 1] = '\0'; SCLogDebug("line: '%s'", line); - uint32_t decoded_size = Base64DecodeBufferSize(strlen(line)); + uint32_t decoded_size = SCBase64DecodeBufferSize(strlen(line)); // coverity[alloc_strlen : FALSE] uint8_t decoded[decoded_size]; - uint32_t num_decoded = - Base64Decode((const uint8_t *)line, strlen(line), Base64ModeStrict, decoded); + uint32_t num_decoded = SCBase64Decode( + (const uint8_t *)line, strlen(line), SCBase64ModeStrict, decoded); if (num_decoded == 0 && strlen(line) > 0) { FatalErrorOnInit("bad base64 encoding %s/%s", set->name, set->load); continue; @@ -538,10 +538,10 @@ static int DatasetLoadString(Dataset *set) *r = '\0'; - uint32_t decoded_size = Base64DecodeBufferSize(strlen(line)); + uint32_t decoded_size = SCBase64DecodeBufferSize(strlen(line)); uint8_t decoded[decoded_size]; - uint32_t num_decoded = - Base64Decode((const uint8_t *)line, strlen(line), Base64ModeStrict, decoded); + uint32_t num_decoded = SCBase64Decode( + (const uint8_t *)line, strlen(line), SCBase64ModeStrict, decoded); if (num_decoded == 0) { FatalErrorOnInit("bad base64 encoding %s/%s", set->name, set->load); continue; @@ -1606,10 +1606,10 @@ static int DatasetOpSerialized(Dataset *set, const char *string, DatasetOpFunc D switch (set->type) { case DATASET_TYPE_STRING: { - uint32_t decoded_size = Base64DecodeBufferSize(strlen(string)); + uint32_t decoded_size = SCBase64DecodeBufferSize(strlen(string)); uint8_t decoded[decoded_size]; - uint32_t num_decoded = Base64Decode( - (const uint8_t *)string, strlen(string), Base64ModeStrict, decoded); + uint32_t num_decoded = SCBase64Decode( + (const uint8_t *)string, strlen(string), SCBase64ModeStrict, decoded); if (num_decoded == 0) { return -2; } diff --git a/src/detect-base64-decode.c b/src/detect-base64-decode.c index 59796aa760cf..4a2219a3ceaa 100644 --- a/src/detect-base64-decode.c +++ b/src/detect-base64-decode.c @@ -95,7 +95,7 @@ int DetectBase64DecodeDoMatch(DetectEngineThreadCtx *det_ctx, const Signature *s if (decode_len > 0) { uint32_t num_decoded = - Base64Decode(payload, decode_len, Base64ModeRFC4648, det_ctx->base64_decoded); + SCBase64Decode(payload, decode_len, SCBase64ModeRFC4648, det_ctx->base64_decoded); det_ctx->base64_decoded_len = num_decoded; SCLogDebug("Decoded %d bytes from base64 data.", det_ctx->base64_decoded_len); } diff --git a/src/detect-transform-base64.c b/src/detect-transform-base64.c index ddea2933543c..18a36be10e37 100644 --- a/src/detect-transform-base64.c +++ b/src/detect-transform-base64.c @@ -40,7 +40,7 @@ static int DetectTransformFromBase64DecodeSetup(DetectEngineCtx *, Signature *, const char *); static void DetectTransformFromBase64DecodeFree(DetectEngineCtx *, void *); #ifdef UNITTESTS -#define DETECT_TRANSFORM_FROM_BASE64_MODE_DEFAULT (uint8_t) Base64ModeRFC4648 +#define DETECT_TRANSFORM_FROM_BASE64_MODE_DEFAULT (uint8_t) SCBase64ModeRFC4648 static void DetectTransformFromBase64DecodeRegisterTests(void); #endif static void TransformFromBase64Decode(InspectionBuffer *buffer, void *options); @@ -119,7 +119,7 @@ static void TransformFromBase64Decode(InspectionBuffer *buffer, void *options) const uint32_t input_len = buffer->inspect_len; uint32_t decode_length = input_len; - Base64Mode mode = b64d->mode; + SCBase64Mode mode = b64d->mode; uint32_t offset = b64d->offset; uint32_t nbytes = b64d->nbytes; @@ -144,9 +144,9 @@ static void TransformFromBase64Decode(InspectionBuffer *buffer, void *options) return; } - uint32_t decoded_size = Base64DecodeBufferSize(decode_length); + uint32_t decoded_size = SCBase64DecodeBufferSize(decode_length); uint8_t decoded[decoded_size]; - uint32_t num_decoded = Base64Decode((const uint8_t *)input, decode_length, mode, decoded); + uint32_t num_decoded = SCBase64Decode((const uint8_t *)input, decode_length, mode, decoded); if (num_decoded > 0) { // PrintRawDataFp(stdout, output, b64data->decoded_len); InspectionBufferCopy(buffer, decoded, num_decoded); @@ -185,7 +185,7 @@ static int DetectTransformFromBase64DecodeTest01a(void) uint32_t input_len = strlen((char *)input); const char *result = "foobar"; uint32_t result_len = strlen((char *)result); - SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = Base64ModeRFC2045 }; + SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = SCBase64ModeRFC2045 }; InspectionBuffer buffer; InspectionBufferInit(&buffer, input_len); @@ -204,7 +204,7 @@ static int DetectTransformFromBase64DecodeTest02(void) { const uint8_t *input = (const uint8_t *)"This is Suricata\n"; uint32_t input_len = strlen((char *)input); - SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = Base64ModeStrict }; + SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = SCBase64ModeStrict }; InspectionBuffer buffer; InspectionBuffer buffer_orig; InspectionBufferInit(&buffer, input_len); @@ -321,7 +321,7 @@ static int DetectTransformFromBase64DecodeTest07(void) uint32_t result_len = strlen((char *)result); SCDetectTransformFromBase64Data b64d = { .nbytes = input_len - 4, /* NB: stop early */ - .mode = Base64ModeRFC2045 }; + .mode = SCBase64ModeRFC2045 }; InspectionBuffer buffer; InspectionBufferInit(&buffer, input_len); @@ -342,7 +342,7 @@ static int DetectTransformFromBase64DecodeTest08(void) const uint8_t *input = (const uint8_t *)"This is not base64-encoded"; uint32_t input_len = strlen((char *)input); - SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = Base64ModeRFC2045 }; + SCDetectTransformFromBase64Data b64d = { .nbytes = input_len, .mode = SCBase64ModeRFC2045 }; InspectionBuffer buffer; InspectionBufferInit(&buffer, input_len); diff --git a/src/log-tlsstore.c b/src/log-tlsstore.c index ad94fb3d19a0..9f8d69f577f9 100644 --- a/src/log-tlsstore.c +++ b/src/log-tlsstore.c @@ -111,7 +111,7 @@ static void LogTlsLogPem(LogTlsStoreLogThread *aft, const Packet *p, SSLState *s } TAILQ_FOREACH (cert, &connp->certs, next) { - pemlen = Base64EncodeBufferSize(cert->cert_len); + pemlen = SCBase64EncodeBufferSize(cert->cert_len); if (pemlen > aft->enc_buf_len) { ptmp = (uint8_t*) SCRealloc(aft->enc_buf, sizeof(uint8_t) * pemlen); if (ptmp == NULL) { @@ -127,9 +127,10 @@ static void LogTlsLogPem(LogTlsStoreLogThread *aft, const Packet *p, SSLState *s memset(aft->enc_buf, 0, aft->enc_buf_len); - ret = Base64Encode((unsigned char*) cert->cert_data, cert->cert_len, aft->enc_buf, &pemlen); + ret = SCBase64Encode( + (unsigned char *)cert->cert_data, cert->cert_len, aft->enc_buf, &pemlen); if (ret != SC_BASE64_OK) { - SCLogWarning("Invalid return of Base64Encode function"); + SCLogWarning("Invalid return of SCBase64Encode function"); goto end_fwrite_fp; } diff --git a/src/output-json.c b/src/output-json.c index 2880a25d87f9..ca945ce28dc6 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -616,7 +616,7 @@ static bool CalculateCommunityFlowIdv4(const Flow *f, if (SCSha1HashBuffer((const uint8_t *)&ipv4, sizeof(ipv4), hash, sizeof(hash)) == 1) { strlcpy((char *)base64buf, "1:", COMMUNITY_ID_BUF_SIZE); unsigned long out_len = COMMUNITY_ID_BUF_SIZE - 2; - if (Base64Encode(hash, sizeof(hash), base64buf+2, &out_len) == SC_BASE64_OK) { + if (SCBase64Encode(hash, sizeof(hash), base64buf + 2, &out_len) == SC_BASE64_OK) { return true; } } @@ -665,7 +665,7 @@ static bool CalculateCommunityFlowIdv6(const Flow *f, if (SCSha1HashBuffer((const uint8_t *)&ipv6, sizeof(ipv6), hash, sizeof(hash)) == 1) { strlcpy((char *)base64buf, "1:", COMMUNITY_ID_BUF_SIZE); unsigned long out_len = COMMUNITY_ID_BUF_SIZE - 2; - if (Base64Encode(hash, sizeof(hash), base64buf+2, &out_len) == SC_BASE64_OK) { + if (SCBase64Encode(hash, sizeof(hash), base64buf + 2, &out_len) == SC_BASE64_OK) { return true; } } diff --git a/src/tests/fuzz/fuzz_decodebase64.c b/src/tests/fuzz/fuzz_decodebase64.c index 9c6501a5b61f..6bbe65f7af79 100644 --- a/src/tests/fuzz/fuzz_decodebase64.c +++ b/src/tests/fuzz/fuzz_decodebase64.c @@ -16,11 +16,11 @@ static int initialized = 0; static void Base64FuzzTest(const uint8_t *src, size_t len) { - uint32_t decoded_len = Base64DecodeBufferSize(len); + uint32_t decoded_len = SCBase64DecodeBufferSize(len); uint8_t *decoded = SCCalloc(decoded_len, sizeof(uint8_t)); - for (uint8_t mode = Base64ModeRFC2045; mode <= Base64ModeStrict; mode++) { - (void)Base64Decode(src, len, mode, decoded); + for (uint8_t mode = SCBase64ModeRFC2045; mode <= SCBase64ModeStrict; mode++) { + (void)SCBase64Decode(src, len, mode, decoded); } SCFree(decoded); From 220667a50706a0b2f4574170d5e10d67ba07aa1b Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Thu, 30 Jan 2025 10:49:37 -0600 Subject: [PATCH 2/4] base64: expose no padding and padding optional variants A no padding option is provided as a mode, as its a variant suitable for encoding and decoding. A padding optional function is added that is indifferent to padding when decoding. This can be useful when you're not sure if padding exists, and don't really care. --- rust/src/ffi/base64.rs | 60 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/rust/src/ffi/base64.rs b/rust/src/ffi/base64.rs index 8b07c5fadc1c..711ca19e108e 100644 --- a/rust/src/ffi/base64.rs +++ b/rust/src/ffi/base64.rs @@ -15,10 +15,13 @@ * 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)] @@ -64,6 +67,12 @@ pub enum SCBase64Mode { * encountered * */ 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] @@ -79,7 +88,8 @@ pub unsafe extern "C" fn SCBase64DecodeBufferSize(input_len: u32) -> u32 { /// It allows decoding in the modes described by ``SCBase64Mode`` enum. #[no_mangle] pub unsafe extern "C" fn SCBase64Decode( - input: *const u8, len: usize, mode: SCBase64Mode, output: *mut u8) -> u32 { + input: *const u8, len: usize, mode: SCBase64Mode, output: *mut u8, +) -> u32 { if input.is_null() || len == 0 { return 0; } @@ -106,13 +116,26 @@ pub unsafe extern "C" fn SCBase64Decode( 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. @@ -121,14 +144,18 @@ pub unsafe extern "C" fn SCBase64Decode( /// 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( +pub unsafe extern "C" fn SCBase64EncodeWithMode( input: *const u8, input_len: c_ulong, output: *mut c_uchar, output_len: *mut c_ulong, + mode: SCBase64Mode, ) -> SCBase64ReturnCode { if input.is_null() || output.is_null() || output_len.is_null() { 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 SCBase64ReturnCode::SC_BASE64_OVERFLOW; } @@ -139,6 +166,27 @@ pub unsafe extern "C" fn SCBase64Encode( 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. From f1db60c1992502a84d50e331e913403c1f2a2f5b Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Mon, 27 Jan 2025 15:57:13 -0600 Subject: [PATCH 3/4] lua: add base64 lib Export our base64 decoding and encoding functions to Lua. Ticket: #7074 --- src/Makefile.am | 2 + src/util-lua-base64lib.c | 118 +++++++++++++++++++++++++++++++++++++++ src/util-lua-base64lib.h | 25 +++++++++ src/util-lua-builtins.c | 6 +- 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/util-lua-base64lib.c create mode 100644 src/util-lua-base64lib.h diff --git a/src/Makefile.am b/src/Makefile.am index 1b1d8fca6144..9fb9d69acf3a 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -508,6 +508,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 \ @@ -1061,6 +1062,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 \ diff --git a/src/util-lua-base64lib.c b/src/util-lua-base64lib.c new file mode 100644 index 000000000000..cdce4d87fdc4 --- /dev/null +++ b/src/util-lua-base64lib.c @@ -0,0 +1,118 @@ +/* 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-base64lib.h" +#include "util-validate.h" +#include "lauxlib.h" +#include "rust.h" + +static int LuaBase64Encode(lua_State *L, SCBase64Mode mode) +{ + size_t input_len; + const char *input = luaL_checklstring(L, 1, &input_len); + size_t out_len = SCBase64EncodeBufferSize(input_len); + char *output = SCCalloc(out_len + 1, sizeof(char)); + if (output == NULL) { + return luaL_error(L, "malloc"); + } + if (SCBase64EncodeWithMode((uint8_t *)input, (unsigned long)input_len, (u_char *)output, + (unsigned long *)&out_len, mode) != 0) { + SCFree(output); + return luaL_error(L, "base64 encoding failed"); + } + lua_pushstring(L, (const char *)output); + SCFree(output); + + return 1; +} + +static int LuaBase64EncodeStandard(lua_State *L) +{ + return LuaBase64Encode(L, SCBase64ModeStrict); +} + +static int LuaBase64EncodeStandardNoPad(lua_State *L) +{ + return LuaBase64Encode(L, SCBase64ModeNoPad); +} + +static int LuaBase64Decode(lua_State *L, SCBase64Mode mode) +{ + size_t input_len; + const char *input = luaL_checklstring(L, 1, &input_len); + char *output = SCCalloc(input_len, sizeof(char)); + if (output == NULL) { + return luaL_error(L, "malloc"); + } + uint32_t n = SCBase64Decode((uint8_t *)input, (uintptr_t)input_len, mode, (uint8_t *)output); + if (n == 0) { + SCFree(output); + return luaL_error(L, "base64 decoding failed"); + } + DEBUG_VALIDATE_BUG_ON(n > input_len); + output[n] = '\0'; + lua_pushstring(L, (const char *)output); + SCFree(output); + + return 1; +} + +static int LuaBase64DecodeStandard(lua_State *L) +{ + return LuaBase64Decode(L, SCBase64ModeStrict); +} + +static int LuaBase64DecodeStandardNoPad(lua_State *L) +{ + return LuaBase64Decode(L, SCBase64ModeNoPad); +} + +static int LuaBase64DecodeStandardPadOpt(lua_State *L) +{ + return LuaBase64Decode(L, SCBase64ModePadOpt); +} + +static int LuaBase64DecodeRFC2045(lua_State *L) +{ + return LuaBase64Decode(L, SCBase64ModeRFC2045); +} + +static int LuaBase64DecodeRFC4648(lua_State *L) +{ + return LuaBase64Decode(L, SCBase64ModeRFC4648); +} + +static const struct luaL_Reg base64lib[] = { + // clang-format off + { "encode", LuaBase64EncodeStandard }, + { "encode_nopad", LuaBase64EncodeStandardNoPad }, + { "decode", LuaBase64DecodeStandard }, + { "decode_nopad", LuaBase64DecodeStandardNoPad }, + { "decode_padopt", LuaBase64DecodeStandardPadOpt }, + { "decode_rfc2045", LuaBase64DecodeRFC2045 }, + { "decode_rfc4648", LuaBase64DecodeRFC4648 }, + { NULL, NULL }, + // clang-format on +}; + +int SCLuaLoadBase64Lib(lua_State *L) +{ + luaL_newlib(L, base64lib); + + return 1; +} diff --git a/src/util-lua-base64lib.h b/src/util-lua-base64lib.h new file mode 100644 index 000000000000..20f6d4fb0cca --- /dev/null +++ b/src/util-lua-base64lib.h @@ -0,0 +1,25 @@ +/* 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_LUA_UTIL_BASE64LIB_H +#define SURICATA_LUA_UTIL_BASE64LIB_H + +#include "lua.h" + +int SCLuaLoadBase64Lib(lua_State *L); + +#endif /* SURICATA_LUA_UTIL_BASE64LIB_H */ diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index c826df4d9f6d..cfb419845c02 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -17,14 +17,16 @@ #include "suricata-common.h" #include "util-lua-builtins.h" -#include "util-lua-hashlib.h" +#include "util-lua-base64lib.h" #include "util-lua-dataset.h" +#include "util-lua-hashlib.h" #include "lauxlib.h" static const luaL_Reg builtins[] = { - { "suricata.hashlib", SCLuaLoadHashlib }, + { "suricata.base64", SCLuaLoadBase64Lib }, { "suricata.dataset", LuaLoadDatasetLib }, + { "suricata.hashlib", SCLuaLoadHashlib }, { NULL, NULL }, }; From ed375f2cd481d268d4135f0b7b01c4e90972582b Mon Sep 17 00:00:00 2001 From: Jason Ish Date: Mon, 27 Jan 2025 16:56:29 -0600 Subject: [PATCH 4/4] doc/userguide: document Lua base64 library Ticket: #7074 --- doc/userguide/lua/libs/base64.rst | 55 +++++++++++++++++++++++++++++++ doc/userguide/lua/libs/index.rst | 1 + 2 files changed, 56 insertions(+) create mode 100644 doc/userguide/lua/libs/base64.rst diff --git a/doc/userguide/lua/libs/base64.rst b/doc/userguide/lua/libs/base64.rst new file mode 100644 index 000000000000..412eec2b85f9 --- /dev/null +++ b/doc/userguide/lua/libs/base64.rst @@ -0,0 +1,55 @@ +Base64 +------ + +Base64 functions are exposed to Lua scripts with the +``suricata.base64``. 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 encoded 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. diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index 09e67ca9a600..1a6833bf59be 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -8,4 +8,5 @@ environment without access to additional modules. .. toctree:: + base64 hashlib