From 668fcfc11e740a76ffd9dd5d9a06adcd60e1871a Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Sun, 2 Feb 2025 09:12:59 -0500 Subject: [PATCH 1/2] ftp: convert enumerations to Rust As part of the effort to convert the FTP/FTPDATA parser to rust, move the enums from C to rust. Issue: 4082 --- rust/cbindgen.toml | 3 + rust/src/ftp/constant.rs | 89 +++++++++++++++++ rust/src/ftp/ftp.rs | 201 +++++++++++++++++++++++++++++++++++++++ rust/src/ftp/mod.rs | 4 +- src/app-layer-ftp.c | 150 ++++++++++------------------- src/app-layer-ftp.h | 89 +++-------------- src/detect-ftp-command.c | 13 ++- src/output-json-ftp.c | 36 ++++--- 8 files changed, 390 insertions(+), 195 deletions(-) create mode 100644 rust/src/ftp/constant.rs create mode 100644 rust/src/ftp/ftp.rs diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index e2730789c808..7e20a8411a40 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -82,6 +82,9 @@ include = [ "QuicState", "QuicTransaction", "FtpEvent", + "FtpRequestCommand", + "FtpStateValues", + "FtpDataStateValues", "SCSigTableElmt", "SCTransformTableElmt", "DataRepType", diff --git a/rust/src/ftp/constant.rs b/rust/src/ftp/constant.rs new file mode 100644 index 000000000000..2b3e8dd0887e --- /dev/null +++ b/rust/src/ftp/constant.rs @@ -0,0 +1,89 @@ +/* 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. + */ + +// FTP state progress values +#[repr(u8)] +#[allow(non_camel_case_types)] +pub enum FtpStateValues { + FTP_STATE_NONE, + FTP_STATE_IN_PROGRESS, + FTP_STATE_PORT_DONE, + FTP_STATE_FINISHED, +} +// FTP Data progress values +#[repr(u8)] +#[allow(non_camel_case_types)] +pub enum FtpDataStateValues { + FTPDATA_STATE_IN_PROGRESS = 1, + FTPDATA_STATE_FINISHED = 2, +} + +// FTP request command values +#[repr(u8)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +pub enum FtpRequestCommand { + FTP_COMMAND_UNKNOWN, + FTP_COMMAND_ABOR, + FTP_COMMAND_ACCT, + FTP_COMMAND_ALLO, + FTP_COMMAND_APPE, + FTP_COMMAND_AUTH_TLS, + FTP_COMMAND_CDUP, + FTP_COMMAND_CHMOD, + FTP_COMMAND_CWD, + FTP_COMMAND_DELE, + FTP_COMMAND_EPSV, + FTP_COMMAND_HELP, + FTP_COMMAND_IDLE, + FTP_COMMAND_LIST, + FTP_COMMAND_MAIL, + FTP_COMMAND_MDTM, + FTP_COMMAND_MKD, + FTP_COMMAND_MLFL, + FTP_COMMAND_MODE, + FTP_COMMAND_MRCP, + FTP_COMMAND_MRSQ, + FTP_COMMAND_MSAM, + FTP_COMMAND_MSND, + FTP_COMMAND_MSOM, + FTP_COMMAND_NLST, + FTP_COMMAND_NOOP, + FTP_COMMAND_PASS, + FTP_COMMAND_PASV, + FTP_COMMAND_PORT, + FTP_COMMAND_PWD, + FTP_COMMAND_QUIT, + FTP_COMMAND_REIN, + FTP_COMMAND_REST, + FTP_COMMAND_RETR, + FTP_COMMAND_RMD, + FTP_COMMAND_RNFR, + FTP_COMMAND_RNTO, + FTP_COMMAND_SITE, + FTP_COMMAND_SIZE, + FTP_COMMAND_SMNT, + FTP_COMMAND_STAT, + FTP_COMMAND_STOR, + FTP_COMMAND_STOU, + FTP_COMMAND_STRU, + FTP_COMMAND_SYST, + FTP_COMMAND_TYPE, + FTP_COMMAND_UMASK, + FTP_COMMAND_USER, + FTP_COMMAND_EPRT, +} diff --git a/rust/src/ftp/ftp.rs b/rust/src/ftp/ftp.rs new file mode 100644 index 000000000000..2cc456607742 --- /dev/null +++ b/rust/src/ftp/ftp.rs @@ -0,0 +1,201 @@ +/* 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. + */ + +use std; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; + +use crate::ftp::constant::*; +use lazy_static::lazy_static; + +/// cbindgen:ignore +#[repr(C)] +pub struct FtpCommand { + command_name: CString, + command: FtpRequestCommand, + command_length: u8, +} + +impl FtpCommand { + fn new(command_name: &str, command: FtpRequestCommand) -> FtpCommand { + let cstring = CString::new(command_name).unwrap(); + let length = cstring.as_bytes().len(); + FtpCommand { + command_name: cstring, + command, + command_length: length as u8, + } + } +} + +lazy_static! { + static ref FTP_COMMANDS: Vec = vec![ + FtpCommand::new("PORT", FtpRequestCommand::FTP_COMMAND_PORT), + FtpCommand::new("EPRT", FtpRequestCommand::FTP_COMMAND_EPRT), + FtpCommand::new("AUTH_TLS", FtpRequestCommand::FTP_COMMAND_AUTH_TLS), + FtpCommand::new("PASV", FtpRequestCommand::FTP_COMMAND_PASV), + FtpCommand::new("EPSV", FtpRequestCommand::FTP_COMMAND_EPSV), + FtpCommand::new("RETR", FtpRequestCommand::FTP_COMMAND_RETR), + FtpCommand::new("STOR", FtpRequestCommand::FTP_COMMAND_STOR), + FtpCommand::new("ABOR", FtpRequestCommand::FTP_COMMAND_ABOR), + FtpCommand::new("ACCT", FtpRequestCommand::FTP_COMMAND_ACCT), + FtpCommand::new("ALLO", FtpRequestCommand::FTP_COMMAND_ALLO), + FtpCommand::new("APPE", FtpRequestCommand::FTP_COMMAND_APPE), + FtpCommand::new("CDUP", FtpRequestCommand::FTP_COMMAND_CDUP), + FtpCommand::new("CHMOD", FtpRequestCommand::FTP_COMMAND_CHMOD), + FtpCommand::new("CWD", FtpRequestCommand::FTP_COMMAND_CWD), + FtpCommand::new("DELE", FtpRequestCommand::FTP_COMMAND_DELE), + FtpCommand::new("HELP", FtpRequestCommand::FTP_COMMAND_HELP), + FtpCommand::new("IDLE", FtpRequestCommand::FTP_COMMAND_IDLE), + FtpCommand::new("LIST", FtpRequestCommand::FTP_COMMAND_LIST), + FtpCommand::new("MAIL", FtpRequestCommand::FTP_COMMAND_MAIL), + FtpCommand::new("MDTM", FtpRequestCommand::FTP_COMMAND_MDTM), + FtpCommand::new("MKD", FtpRequestCommand::FTP_COMMAND_MKD), + FtpCommand::new("MLFL", FtpRequestCommand::FTP_COMMAND_MLFL), + FtpCommand::new("MODE", FtpRequestCommand::FTP_COMMAND_MODE), + FtpCommand::new("MRCP", FtpRequestCommand::FTP_COMMAND_MRCP), + FtpCommand::new("MRSQ", FtpRequestCommand::FTP_COMMAND_MRSQ), + FtpCommand::new("MSAM", FtpRequestCommand::FTP_COMMAND_MSAM), + FtpCommand::new("MSND", FtpRequestCommand::FTP_COMMAND_MSND), + FtpCommand::new("MSOM", FtpRequestCommand::FTP_COMMAND_MSOM), + FtpCommand::new("NLST", FtpRequestCommand::FTP_COMMAND_NLST), + FtpCommand::new("NOOP", FtpRequestCommand::FTP_COMMAND_NOOP), + FtpCommand::new("PASS", FtpRequestCommand::FTP_COMMAND_PASS), + FtpCommand::new("PWD", FtpRequestCommand::FTP_COMMAND_PWD), + FtpCommand::new("QUIT", FtpRequestCommand::FTP_COMMAND_QUIT), + FtpCommand::new("REIN", FtpRequestCommand::FTP_COMMAND_REIN), + FtpCommand::new("REST", FtpRequestCommand::FTP_COMMAND_REST), + FtpCommand::new("RMD", FtpRequestCommand::FTP_COMMAND_RMD), + FtpCommand::new("RNFR", FtpRequestCommand::FTP_COMMAND_RNFR), + FtpCommand::new("RNTO", FtpRequestCommand::FTP_COMMAND_RNTO), + FtpCommand::new("SITE", FtpRequestCommand::FTP_COMMAND_SITE), + FtpCommand::new("SIZE", FtpRequestCommand::FTP_COMMAND_SIZE), + FtpCommand::new("SMNT", FtpRequestCommand::FTP_COMMAND_SMNT), + FtpCommand::new("STAT", FtpRequestCommand::FTP_COMMAND_STAT), + FtpCommand::new("STOU", FtpRequestCommand::FTP_COMMAND_STOU), + FtpCommand::new("STRU", FtpRequestCommand::FTP_COMMAND_STRU), + FtpCommand::new("SYST", FtpRequestCommand::FTP_COMMAND_SYST), + FtpCommand::new("TYPE", FtpRequestCommand::FTP_COMMAND_TYPE), + FtpCommand::new("UMASK", FtpRequestCommand::FTP_COMMAND_UMASK), + FtpCommand::new("USER", FtpRequestCommand::FTP_COMMAND_USER), + FtpCommand::new("UNKNOWN", FtpRequestCommand::FTP_COMMAND_UNKNOWN), + ]; +} + +/// cbindgen:ignore +extern "C" { + pub fn MpmAddPatternCI( + ctx: *const c_void, pat: *const libc::c_char, pat_len: c_int, _offset: c_int, + _depth: c_int, id: c_int, rule_id: c_int, _flags: c_int, + ) -> c_void; +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn SCGetFtpCommandInfo( + index: usize, name_ptr: *mut *const c_char, code_ptr: *mut u8, len_ptr: *mut u8, +) -> bool { + if index <= FTP_COMMANDS.len() { + unsafe { + if !name_ptr.is_null() { + *name_ptr = FTP_COMMANDS[index].command_name.as_ptr(); + } + if !code_ptr.is_null() { + *code_ptr = FTP_COMMANDS[index].command as u8; + } + if !len_ptr.is_null() { + *len_ptr = FTP_COMMANDS[index].command_length; + } + } + true + } else { + false + } +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn SCFTPSetMpmState(ctx: *const c_void) { + for index in 0..FTP_COMMANDS.len() { + let name_ptr = FTP_COMMANDS[index].command_name.as_ptr(); + let len = FTP_COMMANDS[index].command_length; + if len > 0 { + MpmAddPatternCI( + ctx, + name_ptr, + len as c_int, + 0, + 0, + index as c_int, + index as c_int, + 0, + ); + } + } +} + +#[repr(C)] +#[allow(dead_code)] +pub struct FtpTransferCmd { + // Must be first -- required by app-layer expectation logic + data_free: unsafe extern "C" fn(*mut c_void), + pub flow_id: u64, + pub file_name: *mut u8, + pub file_len: u16, + pub direction: u8, + pub cmd: u8, +} + +impl Default for FtpTransferCmd { + fn default() -> Self { + FtpTransferCmd { + flow_id: 0, + file_name: std::ptr::null_mut(), + file_len: 0, + direction: 0, + cmd: FtpStateValues::FTP_STATE_NONE as u8, + data_free: default_free_fn, + } + } +} + +unsafe extern "C" fn default_free_fn(_ptr: *mut c_void) {} +impl FtpTransferCmd { + pub fn new() -> Self { + FtpTransferCmd { + ..Default::default() + } + } +} + +/// Returns *mut FtpTransferCmd +#[no_mangle] +pub unsafe extern "C" fn SCFTPTransferCmdNew() -> *mut FtpTransferCmd { + SCLogDebug!("allocating ftp transfer cmd"); + let cmd = FtpTransferCmd::new(); + Box::into_raw(Box::new(cmd)) +} + +/// Params: +/// - transfer command: *mut FTPTransferCmd as void pointer +#[no_mangle] +pub unsafe extern "C" fn SCFTPTransferCmdFree(cmd: *mut FtpTransferCmd) { + SCLogDebug!("freeing ftp transfer cmd"); + if !cmd.is_null() { + let _transfer_cmd = Box::from_raw(cmd); + } +} diff --git a/rust/src/ftp/mod.rs b/rust/src/ftp/mod.rs index a206aa2f350c..b7b74732c9e7 100644 --- a/rust/src/ftp/mod.rs +++ b/rust/src/ftp/mod.rs @@ -26,7 +26,9 @@ use std; use std::str; use std::str::FromStr; +pub mod constant; pub mod event; +pub mod ftp; // We transform an integer string into a i64, ignoring surrounding whitespaces // We look for a digit suite, and try to convert it. @@ -111,7 +113,7 @@ pub unsafe extern "C" fn rs_ftp_pasv_response(input: *const u8, len: u32) -> u16 if input.is_null() { return 0; } - let buf = build_slice!(input, len as usize); + let buf = build_slice!(input, len as usize); match ftp_pasv_response(buf) { Ok((_, dport)) => { return dport; diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index a6a1f632fcb4..682ee2b42a2c 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-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 @@ -47,63 +47,6 @@ typedef struct FTPThreadCtx_ { static MpmCtx *ftp_mpm_ctx = NULL; -// clang-format off -const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = { - /* Parsed and handled */ - { "PORT", FTP_COMMAND_PORT, 4 }, - { "EPRT", FTP_COMMAND_EPRT, 4 }, - { "AUTH TLS", FTP_COMMAND_AUTH_TLS, 8 }, - { "PASV", FTP_COMMAND_PASV, 4 }, - { "RETR", FTP_COMMAND_RETR, 4 }, - { "EPSV", FTP_COMMAND_EPSV, 4 }, - { "STOR", FTP_COMMAND_STOR, 4 }, - - /* Parsed, but not handled */ - { "ABOR", FTP_COMMAND_ABOR, 4 }, - { "ACCT", FTP_COMMAND_ACCT, 4 }, - { "ALLO", FTP_COMMAND_ALLO, 4 }, - { "APPE", FTP_COMMAND_APPE, 4 }, - { "CDUP", FTP_COMMAND_CDUP, 4 }, - { "CHMOD", FTP_COMMAND_CHMOD, 5 }, - { "CWD", FTP_COMMAND_CWD, 3 }, - { "DELE", FTP_COMMAND_DELE, 4 }, - { "HELP", FTP_COMMAND_HELP, 4 }, - { "IDLE", FTP_COMMAND_IDLE, 4 }, - { "LIST", FTP_COMMAND_LIST, 4 }, - { "MAIL", FTP_COMMAND_MAIL, 4 }, - { "MDTM", FTP_COMMAND_MDTM, 4 }, - { "MKD", FTP_COMMAND_MKD, 3 }, - { "MLFL", FTP_COMMAND_MLFL, 4 }, - { "MODE", FTP_COMMAND_MODE, 4 }, - { "MRCP", FTP_COMMAND_MRCP, 4 }, - { "MRSQ", FTP_COMMAND_MRSQ, 4 }, - { "MSAM", FTP_COMMAND_MSAM, 4 }, - { "MSND", FTP_COMMAND_MSND, 4 }, - { "MSOM", FTP_COMMAND_MSOM, 4 }, - { "NLST", FTP_COMMAND_NLST, 4 }, - { "NOOP", FTP_COMMAND_NOOP, 4 }, - { "PASS", FTP_COMMAND_PASS, 4 }, - { "PWD", FTP_COMMAND_PWD, 3 }, - { "QUIT", FTP_COMMAND_QUIT, 4 }, - { "REIN", FTP_COMMAND_REIN, 4 }, - { "REST", FTP_COMMAND_REST, 4 }, - { "RMD", FTP_COMMAND_RMD, 3 }, - { "RNFR", FTP_COMMAND_RNFR, 4 }, - { "RNTO", FTP_COMMAND_RNTO, 4 }, - { "SITE", FTP_COMMAND_SITE, 4 }, - { "SIZE", FTP_COMMAND_SIZE, 4 }, - { "SMNT", FTP_COMMAND_SMNT, 4 }, - { "STAT", FTP_COMMAND_STAT, 4 }, - { "STOU", FTP_COMMAND_STOU, 4 }, - { "STRU", FTP_COMMAND_STRU, 4 }, - { "SYST", FTP_COMMAND_SYST, 4 }, - { "TYPE", FTP_COMMAND_TYPE, 4 }, - { "UMASK", FTP_COMMAND_UMASK, 5 }, - { "USER", FTP_COMMAND_USER, 4 }, - { NULL, FTP_COMMAND_UNKNOWN, 0 } -}; -// clang-format on - uint64_t ftp_config_memcap = 0; uint32_t ftp_config_maxtx = 1024; uint32_t ftp_max_line_len = 4096; @@ -421,7 +364,7 @@ static AppLayerResult FTPGetLineForDirection( * \retval 1 when the command is parsed, 0 otherwise */ static int FTPParseRequestCommand( - FTPThreadCtx *td, FtpLineState *line, const FtpCommand **cmd_descriptor) + FTPThreadCtx *td, FtpLineState *line, FtpCommandInfo *cmd_descriptor) { SCEnter(); @@ -431,34 +374,40 @@ static int FTPParseRequestCommand( int mpm_cnt = mpm_table[FTP_MPM].Search( ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, line->buf, line->len); if (mpm_cnt) { - *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; - SCReturnInt(1); + uint8_t command_code; + if (SCGetFtpCommandInfo(td->pmq->rule_id_array[0], NULL, &command_code, NULL)) { + cmd_descriptor->command_code = command_code; + /* FTP command indices are expressed in Rust as a u8 */ + cmd_descriptor->command_index = (uint8_t)td->pmq->rule_id_array[0]; + SCReturnInt(1); + } else { + /* Where is out command? */ + DEBUG_VALIDATE_BUG_ON(1); + } +#ifdef DEBUG + if (SCLogDebugEnabled()) { + const char *command_name = NULL; + (void)SCGetFtpCommandInfo(td->pmq->rule_id_array[0], &command_name, NULL, NULL); + SCLogDebug("matching FTP command is %s [code: %d, index %d]", command_name, + command_code, td->pmq->rule_id_array[0]); + } +#endif } - *cmd_descriptor = NULL; + cmd_descriptor->command_code = FTP_COMMAND_UNKNOWN; SCReturnInt(0); } -struct FtpTransferCmd { - /** Need to look like a ExpectationData so DFree must - * be first field . */ - void (*DFree)(void *); - uint64_t flow_id; - uint8_t *file_name; - uint16_t file_len; - uint8_t direction; /**< direction in which the data will flow */ - FtpRequestCommand cmd; -}; - static void FtpTransferCmdFree(void *data) { - struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; + FtpTransferCmd *cmd = (FtpTransferCmd *)data; if (cmd == NULL) return; if (cmd->file_name) { - FTPFree(cmd->file_name, cmd->file_len + 1); + FTPFree((void *)cmd->file_name, cmd->file_len + 1); } - FTPFree(cmd, sizeof(struct FtpTransferCmd)); + SCFTPTransferCmdFree(cmd); + FTPDecrMemuse((uint64_t)sizeof(FtpTransferCmd)); } static uint32_t CopyCommandLine(uint8_t **dest, FtpLineState *line) @@ -523,14 +472,14 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt } else if (res.status == -1) { break; } - const FtpCommand *cmd_descriptor; + FtpCommandInfo cmd_descriptor; if (!FTPParseRequestCommand(thread_data, &line, &cmd_descriptor)) { state->command = FTP_COMMAND_UNKNOWN; continue; } - state->command = cmd_descriptor->command; + state->command = cmd_descriptor.command_code; FTPTransaction *tx = FTPTransactionCreate(state); if (unlikely(tx == NULL)) SCReturnStruct(APP_LAYER_ERROR); @@ -588,10 +537,12 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt if (state->dyn_port == 0 || line.len < 6) { SCReturnStruct(APP_LAYER_ERROR); } - struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); + FtpTransferCmd *data = SCFTPTransferCmdNew(); if (data == NULL) SCReturnStruct(APP_LAYER_ERROR); - data->DFree = FtpTransferCmdFree; + FTPIncrMemuse((uint64_t)(sizeof *data)); + data->data_free = FtpTransferCmdFree; + /* * Min size has been checked in FTPParseRequestCommand * SC_FILENAME_MAX includes the null @@ -725,9 +676,9 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS } lasttx = tx; tx->tx_data.updated_tc = true; - if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { + if (state->command == FTP_COMMAND_UNKNOWN) { /* unknown */ - tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX - 1]; + tx->command_descriptor.command_code = FTP_COMMAND_UNKNOWN; } state->curr_tx = tx; @@ -844,9 +795,17 @@ static void FTPStateFree(void *s) FTPTransaction *tx = NULL; while ((tx = TAILQ_FIRST(&fstate->tx_list))) { TAILQ_REMOVE(&fstate->tx_list, tx, next); - SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p", - tx->command_descriptor->command_name, s, tx->tx_id, tx->request_length, - tx->request); +#ifdef DEBUG + if (SCLogDebugEnabled()) { + const char *command_name = NULL; + (void)SCGetFtpCommandInfo( + tx->command_descriptor.command_index, &command_name, NULL, NULL); + SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p", + command_name != NULL ? command_name : "n/a", s, tx->tx_id, tx->request_length, + tx->request); + } +#endif + FTPTransactionFree(tx); } @@ -957,7 +916,8 @@ static int FTPGetAlstateProgress(void *vtx, uint8_t direction) FTPTransaction *tx = vtx; if (!tx->done) { - if (direction == STREAM_TOSERVER && tx->command_descriptor->command == FTP_COMMAND_PORT) { + if (direction == STREAM_TOSERVER && + tx->command_descriptor.command_code == FTP_COMMAND_PORT) { return FTP_STATE_PORT_DONE; } return FTP_STATE_IN_PROGRESS; @@ -1071,8 +1031,8 @@ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, SCLogDebug("FTP-DATA flags %04x dir %d", flags, direction); if (input_len && ftpdata_state->files == NULL) { - struct FtpTransferCmd *data = - (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); + FtpTransferCmd *data = + (FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); if (data == NULL) { SCReturnStruct(APP_LAYER_ERROR); } @@ -1283,19 +1243,7 @@ static void FTPSetMpmState(void) } MpmInitCtx(ftp_mpm_ctx, FTP_MPM); - uint32_t i = 0; - for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { - const FtpCommand *cmd = &FtpCommands[i]; - if (cmd->command_length == 0) - continue; - - MpmAddPatternCI(ftp_mpm_ctx, - (uint8_t *)cmd->command_name, - cmd->command_length, - 0 /* defunct */, 0 /* defunct */, - i /* id */, i /* rule id */ , 0 /* no flags */); - } - + SCFTPSetMpmState(ftp_mpm_ctx); mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); } diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index e69415d8cf13..99d42b8b97ef 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2021 Open Information Security Foundation +/* Copyright (C) 2007-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 @@ -27,74 +27,7 @@ #include "rust.h" -enum { - FTP_STATE_IN_PROGRESS, - FTP_STATE_PORT_DONE, - FTP_STATE_FINISHED, -}; - -typedef enum { - FTP_COMMAND_UNKNOWN = 0, - FTP_COMMAND_ABOR, - FTP_COMMAND_ACCT, - FTP_COMMAND_ALLO, - FTP_COMMAND_APPE, - FTP_COMMAND_AUTH_TLS, - FTP_COMMAND_CDUP, - FTP_COMMAND_CHMOD, - FTP_COMMAND_CWD, - FTP_COMMAND_DELE, - FTP_COMMAND_EPSV, - FTP_COMMAND_HELP, - FTP_COMMAND_IDLE, - FTP_COMMAND_LIST, - FTP_COMMAND_MAIL, - FTP_COMMAND_MDTM, - FTP_COMMAND_MKD, - FTP_COMMAND_MLFL, - FTP_COMMAND_MODE, - FTP_COMMAND_MRCP, - FTP_COMMAND_MRSQ, - FTP_COMMAND_MSAM, - FTP_COMMAND_MSND, - FTP_COMMAND_MSOM, - FTP_COMMAND_NLST, - FTP_COMMAND_NOOP, - FTP_COMMAND_PASS, - FTP_COMMAND_PASV, - FTP_COMMAND_PORT, - FTP_COMMAND_PWD, - FTP_COMMAND_QUIT, - FTP_COMMAND_REIN, - FTP_COMMAND_REST, - FTP_COMMAND_RETR, - FTP_COMMAND_RMD, - FTP_COMMAND_RNFR, - FTP_COMMAND_RNTO, - FTP_COMMAND_SITE, - FTP_COMMAND_SIZE, - FTP_COMMAND_SMNT, - FTP_COMMAND_STAT, - FTP_COMMAND_STOR, - FTP_COMMAND_STOU, - FTP_COMMAND_STRU, - FTP_COMMAND_SYST, - FTP_COMMAND_TYPE, - FTP_COMMAND_UMASK, - FTP_COMMAND_USER, - FTP_COMMAND_EPRT, - - /* must be last */ - FTP_COMMAND_MAX - /** \todo more if missing.. */ -} FtpRequestCommand; - -typedef struct FtpCommand_ { - const char *command_name; - FtpRequestCommand command; - const uint8_t command_length; -} FtpCommand; -extern const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1]; +struct FtpCommand; typedef uint32_t FtpRequestCommandArgOfs; @@ -115,6 +48,17 @@ typedef struct FTPString_ { TAILQ_ENTRY(FTPString_) next; } FTPString; +/* + * These are the values for the table index value and the FTP command + * enum value. These *should* be the same if the enum and command insertion + * order remain the same. However, we store each value to protect against + * drift between enum and insertion order. + */ +typedef struct FtpCommandInfo_ { + uint8_t command_index; + FtpRequestCommand command_code; +} FtpCommandInfo; + typedef struct FTPTransaction_ { /** id of this tx, starting at 0 */ uint64_t tx_id; @@ -127,7 +71,7 @@ typedef struct FTPTransaction_ { bool request_truncated; /* for the command description */ - const FtpCommand *command_descriptor; + FtpCommandInfo command_descriptor; uint16_t dyn_port; /* dynamic port, if applicable */ bool done; /* transaction complete? */ @@ -163,11 +107,6 @@ typedef struct FtpState_ { AppLayerStateData state_data; } FtpState; -enum { - FTPDATA_STATE_IN_PROGRESS, - FTPDATA_STATE_FINISHED, -}; - /** FTP Data State for app layer parser */ typedef struct FtpDataState_ { uint8_t *input; diff --git a/src/detect-ftp-command.c b/src/detect-ftp-command.c index b106ed4ed847..f06e46cccb77 100644 --- a/src/detect-ftp-command.c +++ b/src/detect-ftp-command.c @@ -67,13 +67,16 @@ static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, if (buffer->inspect == NULL) { FTPTransaction *tx = (FTPTransaction *)txv; - if (tx->command_descriptor->command_name == NULL || - tx->command_descriptor->command_length == 0) + if (tx->command_descriptor.command_code == FTP_COMMAND_UNKNOWN) return NULL; - InspectionBufferSetupAndApplyTransforms(det_ctx, list_id, buffer, - (const uint8_t *)tx->command_descriptor->command_name, - tx->command_descriptor->command_length, transforms); + const char *b = NULL; + uint8_t b_len = 0; + + if (SCGetFtpCommandInfo(tx->command_descriptor.command_index, &b, NULL, &b_len)) { + InspectionBufferSetupAndApplyTransforms( + det_ctx, list_id, buffer, (const uint8_t *)b, b_len, transforms); + } } return buffer; diff --git a/src/output-json-ftp.c b/src/output-json-ftp.c index 14232bdfe393..f7fae9983b76 100644 --- a/src/output-json-ftp.c +++ b/src/output-json-ftp.c @@ -58,18 +58,28 @@ bool EveFTPLogCommand(void *vtx, JsonBuilder *jb) return false; } } + const char *command_name = NULL; + uint8_t command_name_length; + if (tx->command_descriptor.command_code != FTP_COMMAND_UNKNOWN) { + if (!SCGetFtpCommandInfo(tx->command_descriptor.command_index, &command_name, NULL, + &command_name_length)) { + SCLogDebug("Unable to fetch info for FTP command code %d [index %d]", + tx->command_descriptor.command_code, tx->command_descriptor.command_index); + return false; + } + } jb_open_object(jb, "ftp"); - jb_set_string(jb, "command", tx->command_descriptor->command_name); - uint32_t min_length = tx->command_descriptor->command_length + 1; /* command + space */ - if (tx->request_length > min_length) { - jb_set_string_from_bytes(jb, - "command_data", - (const uint8_t *)tx->request + min_length, - tx->request_length - min_length - 1); - if (tx->request_truncated) { - JB_SET_TRUE(jb, "command_truncated"); - } else { - JB_SET_FALSE(jb, "command_truncated"); + if (command_name) { + jb_set_string(jb, "command", command_name); + uint32_t min_length = command_name_length + 1; /* command + space */ + if (tx->request_length > min_length) { + jb_set_string_from_bytes(jb, "command_data", (const uint8_t *)tx->request + min_length, + tx->request_length - min_length - 1); + if (tx->request_truncated) { + JB_SET_TRUE(jb, "command_truncated"); + } else { + JB_SET_FALSE(jb, "command_truncated"); + } } } @@ -131,8 +141,8 @@ bool EveFTPLogCommand(void *vtx, JsonBuilder *jb) jb_set_uint(jb, "dynamic_port", tx->dyn_port); } - if (tx->command_descriptor->command == FTP_COMMAND_PORT || - tx->command_descriptor->command == FTP_COMMAND_EPRT) { + if (tx->command_descriptor.command_code == FTP_COMMAND_PORT || + tx->command_descriptor.command_code == FTP_COMMAND_EPRT) { if (tx->active) { JB_SET_STRING(jb, "mode", "active"); } else { From 7c8b312a713803fe3a049182afa4cb8edcbd4582 Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Sun, 2 Feb 2025 11:10:59 -0500 Subject: [PATCH 2/2] ftp: Move config file handling to Rust Issue: 4082 Move the configuration file handling to Rust. These changes will no longer terminate Suricata when there's an invalid value for ftp.memcap. Like earlier Suricata releases, an error message is logged "Invalid value for ftp.memcap" but Suricata will no longer terminate execution. It will use a default value of "0" instead. --- doc/userguide/upgrade.rst | 3 +++ rust/src/ftp/ftp.rs | 44 +++++++++++++++++++++++++++++++++++++++ src/app-layer-ftp.c | 33 +---------------------------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 282c5a0cf1de..e8a3410c04b4 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -95,6 +95,9 @@ Major changes will need to be set before external modules can be loaded. See the new default configuration file or :ref:`lua-output-yaml` for more details. +- If the configuration value ``ftp.memcap`` is invalid, Suricata will set it to ``0`` which means + no limit will be placed. In previous Suricata releases, Suricata would terminate execution. A + warning message will be displayed `Invalid value for ftp.memcap` when this occurs. Removals ~~~~~~~~ diff --git a/rust/src/ftp/ftp.rs b/rust/src/ftp/ftp.rs index 2cc456607742..3963a2adfa77 100644 --- a/rust/src/ftp/ftp.rs +++ b/rust/src/ftp/ftp.rs @@ -19,6 +19,7 @@ use std; use std::ffi::CString; use std::os::raw::{c_char, c_int, c_void}; +use crate::conf::{conf_get, get_memval}; use crate::ftp::constant::*; use lazy_static::lazy_static; @@ -182,6 +183,49 @@ impl FtpTransferCmd { } } +#[no_mangle] +pub unsafe extern "C" fn SCFTPGetConfigValues( + memcap: *mut u64, max_tx: *mut u32, max_line_len: *mut u32, +) { + if let Some(val) = conf_get("app-layer.protocols.ftp.memcap") { + if let Ok(v) = get_memval(val) { + *memcap = v; + SCLogConfig!("FTP memcap: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.memcap; defaulting to {}", + val, + *memcap + ); + } + } + if let Some(val) = conf_get("app-layer.protocols.ftp.max-tx") { + if let Ok(v) = val.parse::() { + *max_tx = v; + SCLogConfig!("FTP max tx: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.max-tx; defaulting to {}", + val, + *max_tx + ); + } + } + // This value is often expressed with a unit suffix, e.g., 5kb, hence get_memval + if let Some(val) = conf_get("app-layer.protocols.ftp.max-line-length") { + if let Ok(v) = get_memval(val) { + *max_line_len = v as u32; + SCLogConfig!("FTP max line length: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.max-line-length; defaulting to {}", + val, + *max_line_len + ); + } + } +} + /// Returns *mut FtpTransferCmd #[no_mangle] pub unsafe extern "C" fn SCFTPTransferCmdNew() -> *mut FtpTransferCmd { diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index 682ee2b42a2c..29df14e53094 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -58,41 +58,10 @@ static FTPTransaction *FTPGetOldestTx(const FtpState *, FTPTransaction *); static void FTPParseMemcap(void) { - const char *conf_val; - - /** set config values for memcap, prealloc and hash_size */ - if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1) - { - if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) { - SCLogError("Error parsing ftp.memcap " - "from conf file - %s. Killing engine", - conf_val); - exit(EXIT_FAILURE); - } - SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap); - } else { - /* default to unlimited */ - ftp_config_memcap = 0; - } + SCFTPGetConfigValues(&ftp_config_memcap, &ftp_config_maxtx, &ftp_max_line_len); SC_ATOMIC_INIT(ftp_memuse); SC_ATOMIC_INIT(ftp_memcap); - - if ((ConfGet("app-layer.protocols.ftp.max-tx", &conf_val)) == 1) { - if (ParseSizeStringU32(conf_val, &ftp_config_maxtx) < 0) { - SCLogError("Error parsing ftp.max-tx " - "from conf file - %s.", - conf_val); - } - SCLogInfo("FTP max tx: %" PRIu32, ftp_config_maxtx); - } - - if ((ConfGet("app-layer.protocols.ftp.max-line-length", &conf_val)) == 1) { - if (ParseSizeStringU32(conf_val, &ftp_max_line_len) < 0) { - SCLogError("Error parsing ftp.max-line-length from conf file - %s.", conf_val); - } - SCLogConfig("FTP max line length: %" PRIu32, ftp_max_line_len); - } } static void FTPIncrMemuse(uint64_t size)