]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
app/ftp: Use Rust FTP response line handling
authorJeff Lucovsky <jlucovsky@oisf.net>
Fri, 21 Mar 2025 14:01:27 +0000 (10:01 -0400)
committerJason Ish <jason.ish@oisf.net>
Mon, 7 Apr 2025 21:25:04 +0000 (15:25 -0600)
Use the Rust logic to parse FTP response lines with the goal to support
multi-buffer matches better.

A side effect is that the completion codes are no longer strings; the
schema update reflects this.

Issue: 4082

src/app-layer-ftp.c
src/app-layer-ftp.h
src/output-json-ftp.c

index 707a8d5b01a8743d2857c90ba0255e10844caeae..7541214335fe19c238205414b2246abf037fb856 100644 (file)
@@ -20,7 +20,7 @@
  *
  * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
  * \author Eric Leblond <eric@regit.org>
- * \author Jeff Lucovsky <jeff@lucovsky.org>
+ * \author Jeff Lucovsky <jlucovsky@oisf.net>
  *
  * App Layer Parser for FTP
  */
@@ -130,14 +130,13 @@ static void *FTPCalloc(size_t n, size_t size)
 
 static void *FTPRealloc(void *ptr, size_t orig_size, size_t size)
 {
-    void *rptr = NULL;
-
+    DEBUG_VALIDATE_BUG_ON(size == 0);
     if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) {
         sc_errno = SC_ELIMIT;
         return NULL;
     }
 
-    rptr = SCRealloc(ptr, size);
+    void *rptr = SCRealloc(ptr, size);
     if (rptr == NULL) {
         sc_errno = SC_ENOMEM;
         return NULL;
@@ -159,18 +158,24 @@ static void FTPFree(void *ptr, size_t size)
     FTPDecrMemuse((uint64_t)size);
 }
 
-static FTPString *FTPStringAlloc(void)
+static FTPResponseWrapper *FTPResponseWrapperAlloc(FTPResponseLine *response)
 {
-    return FTPCalloc(1, sizeof(FTPString));
+    FTPResponseWrapper *wrapper = FTPCalloc(1, sizeof(FTPResponseWrapper));
+    if (likely(wrapper)) {
+        FTPIncrMemuse(response->total_size);
+        wrapper->response = response;
+    }
+    return wrapper;
 }
 
-static void FTPStringFree(FTPString *str)
+static void FTPResponseWrapperFree(FTPResponseWrapper *wrapper)
 {
-    if (str->str) {
-        FTPFree(str->str, str->len);
+    if (wrapper->response) {
+        FTPDecrMemuse(wrapper->response->total_size);
+        SCFTPFreeResponseLine(wrapper->response);
     }
 
-    FTPFree(str, sizeof(FTPString));
+    FTPFree(wrapper, sizeof(FTPResponseWrapper));
 }
 
 static void *FTPLocalStorageAlloc(void)
@@ -244,10 +249,10 @@ static void FTPTransactionFree(FTPTransaction *tx)
         FTPFree(tx->request, tx->request_length);
     }
 
-    FTPString *str = NULL;
-    while ((str = TAILQ_FIRST(&tx->response_list))) {
-        TAILQ_REMOVE(&tx->response_list, str, next);
-        FTPStringFree(str);
+    FTPResponseWrapper *wrapper;
+    while ((wrapper = TAILQ_FIRST(&tx->response_list))) {
+        TAILQ_REMOVE(&tx->response_list, wrapper, next);
+        FTPResponseWrapperFree(wrapper);
     }
 
     FTPFree(tx, sizeof(*tx));
@@ -693,18 +698,24 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS
         }
 
         if (likely(line.len)) {
-            FTPString *response = FTPStringAlloc();
+            FTPResponseLine *response = SCFTPParseResponseLine((const char *)line.buf, line.len);
             if (likely(response)) {
-                response->len = CopyCommandLine(&response->str, &line);
-                response->truncated = state->current_line_truncated_tc;
-                if (response->truncated) {
-                    AppLayerDecoderEventsSetEventRaw(
-                            &tx->tx_data.events, FtpEventResponseCommandTooLong);
-                }
-                if (line.lf_found) {
-                    state->current_line_truncated_tc = false;
+                FTPResponseWrapper *wrapper = FTPResponseWrapperAlloc(response);
+                if (likely(wrapper)) {
+                    response->truncated = state->current_line_truncated_tc;
+                    if (response->truncated) {
+                        AppLayerDecoderEventsSetEventRaw(
+                                &tx->tx_data.events, FtpEventResponseCommandTooLong);
+                    }
+                    if (line.lf_found) {
+                        state->current_line_truncated_tc = false;
+                    }
+                    TAILQ_INSERT_TAIL(&tx->response_list, wrapper, next);
+                } else {
+                    SCFTPFreeResponseLine(response);
                 }
-                TAILQ_INSERT_TAIL(&tx->response_list, response, next);
+            } else {
+                SCLogDebug("unable to parse FTP response line \"%s\"", line.buf);
             }
         }
 
index 7c704c1949ab95628d56d82633d54de9df884c61..99252137bc353d4adf5e39049f9cd43bfcf478f3 100644 (file)
@@ -41,12 +41,10 @@ typedef struct FtpLineState_ {
     bool lf_found;
 } FtpLineState;
 
-typedef struct FTPString_ {
-    uint8_t *str;
-    uint32_t len;
-    bool truncated;
-    TAILQ_ENTRY(FTPString_) next;
-} FTPString;
+typedef struct FTPResponseWrapper_ {
+    FTPResponseLine *response;
+    TAILQ_ENTRY(FTPResponseWrapper_) next;
+} FTPResponseWrapper;
 
 /*
  * These are the values for the table index value and the FTP command
@@ -80,7 +78,7 @@ typedef struct FTPTransaction_  {
     uint8_t direction;
 
     /* Handle multiple responses */
-    TAILQ_HEAD(, FTPString_) response_list;
+    TAILQ_HEAD(, FTPResponseWrapper_) response_list;
 
     TAILQ_ENTRY(FTPTransaction_) next;
 } FTPTransaction;
index c623b5e6c146239aaac273bf515f80818a18a4f3..19b50ef947db77361b629a6f2f3f1d6d38fdd50c 100644 (file)
@@ -87,44 +87,30 @@ bool EveFTPLogCommand(void *vtx, SCJsonBuilder *jb)
 
     if (!TAILQ_EMPTY(&tx->response_list)) {
         int resp_cnt = 0;
-        FTPString *response;
+        FTPResponseWrapper *wrapper;
         bool is_cc_array_open = false;
-        TAILQ_FOREACH(response, &tx->response_list, next) {
+        TAILQ_FOREACH (wrapper, &tx->response_list, next) {
             /* handle multiple lines within the response, \r\n delimited */
-            uint8_t *where = response->str;
-            uint16_t length = 0;
-            uint16_t pos;
-            if (response->len > 0 && response->len <= UINT16_MAX) {
-                length = (uint16_t)response->len - 1;
-            } else if (response->len > UINT16_MAX) {
-                length = UINT16_MAX;
+            if (!wrapper->response) {
+                continue;
             }
+            FTPResponseLine *response = wrapper->response;
+
             if (!reply_truncated && response->truncated) {
                 reply_truncated = true;
             }
-            while ((pos = JsonGetNextLineFromBuffer((const char *)where, length)) != UINT16_MAX) {
-                uint16_t offset = 0;
-                /* Try to find a completion code for this line */
-                if (pos >= 3)  {
-                    /* Gather the completion code if present */
-                    if (isdigit(where[0]) && isdigit(where[1]) && isdigit(where[2])) {
-                        if (!is_cc_array_open) {
-                            SCJbOpenArray(jb, "completion_code");
-                            is_cc_array_open = true;
-                        }
-                        SCJbAppendStringFromBytes(jb, (const uint8_t *)where, 3);
-                        offset = 4;
-                    }
-                }
-                /* move past 3 character completion code */
-                if (pos >= offset) {
-                    SCJbAppendStringFromBytes(
-                            js_resplist, (const uint8_t *)where + offset, pos - offset);
-                    resp_cnt++;
+            int code_len = strlen((const char *)response->code);
+            if (code_len > 0) {
+                if (!is_cc_array_open) {
+                    SCJbOpenArray(jb, "completion_code");
+                    is_cc_array_open = true;
                 }
-
-                where += pos;
-                length -= pos;
+                SCJbAppendStringFromBytes(jb, (const uint8_t *)response->code, code_len);
+            }
+            if (response->length) {
+                SCJbAppendStringFromBytes(
+                        js_resplist, (const uint8_t *)response->response, response->length);
+                resp_cnt++;
             }
         }