]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
ftp: truncate command data that is too long
authorJason Ish <jason.ish@oisf.net>
Wed, 6 Apr 2022 21:38:35 +0000 (15:38 -0600)
committerShivani Bhardwaj <shivanib134@gmail.com>
Thu, 21 Apr 2022 07:31:56 +0000 (13:01 +0530)
FTP control commands will be buffered forever until a new line is seen,
this can lead to memory exhaustion in Suricata.

To fix, set an upper bound, 4096 bytes on the size of the command that
is saved in the transaction. The input continues to be parsed to find
the end of the command so the parser can continue to move onto the next
command.

The result is that the command data in the transaction is truncated,
which also shows up in the ftp transaction logs.

This value is configurable with the max-line-length field in the ftp
app-layer.protocols section.

As FTP doesn't have events at this time, add a new fields to eve-log
that specificy if the request, or the response has been truncated.

Ticket #5024

(cherry-picked from commit cf8ed576e09a68886760259055e309e51bf5bec3)

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

index d3fe846b6950544ce9d0f1e347482f159dd25958..61a7566ee06d4e9f08dd7ca9226ff346127a8f18 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2007-2020 Open Information Security Foundation
+/* Copyright (C) 2007-2022 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
@@ -57,6 +57,7 @@
 #include "util-memrchr.h"
 #include "util-mem.h"
 #include "util-misc.h"
+#include "util-validate.h"
 
 #include "output-json.h"
 #include "rust.h"
@@ -125,6 +126,7 @@ const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = {
     { FTP_COMMAND_UNKNOWN, NULL, 0}
 };
 uint64_t ftp_config_memcap = 0;
+uint32_t ftp_max_line_len = 4096;
 
 SC_ATOMIC_DECLARE(uint64_t, ftp_memuse);
 SC_ATOMIC_DECLARE(uint64_t, ftp_memcap);
@@ -152,6 +154,14 @@ static void FTPParseMemcap(void)
 
     SC_ATOMIC_INIT(ftp_memuse);
     SC_ATOMIC_INIT(ftp_memcap);
+
+    if ((ConfGet("app-layer.protocols.ftp.max-line-length", &conf_val)) == 1) {
+        if (ParseSizeStringU32(conf_val, &ftp_max_line_len) < 0) {
+            SCLogError(SC_ERR_SIZE_PARSE, "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)
@@ -350,6 +360,7 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
         /* we have seen the lf for the previous line.  Clear the parser
          * details to parse new line */
         line_state->current_line_lf_seen = 0;
+        state->current_line_truncated = false;
         if (line_state->current_line_db == 1) {
             line_state->current_line_db = 0;
             FTPFree(line_state->db, line_state->db_len);
@@ -377,20 +388,27 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
             line_state->current_line_db = 1;
             memcpy(line_state->db, state->input, state->input_len);
             line_state->db_len = state->input_len;
-        } else {
-            ptmp = FTPRealloc(line_state->db, line_state->db_len,
-                             (line_state->db_len + state->input_len));
-            if (ptmp == NULL) {
-                FTPFree(line_state->db, line_state->db_len);
-                line_state->db = NULL;
-                line_state->db_len = 0;
-                return -1;
+        } else if (!state->current_line_truncated) {
+            int32_t input_len = state->input_len;
+            if (line_state->db_len + input_len > ftp_max_line_len) {
+                input_len = ftp_max_line_len - line_state->db_len;
+                DEBUG_VALIDATE_BUG_ON(input_len < 0);
+                state->current_line_truncated = true;
             }
-            line_state->db = ptmp;
+            if (input_len > 0) {
+                ptmp = FTPRealloc(
+                        line_state->db, line_state->db_len, (line_state->db_len + input_len));
+                if (ptmp == NULL) {
+                    FTPFree(line_state->db, line_state->db_len);
+                    line_state->db = NULL;
+                    line_state->db_len = 0;
+                    return -1;
+                }
+                line_state->db = ptmp;
 
-            memcpy(line_state->db + line_state->db_len,
-                   state->input, state->input_len);
-            line_state->db_len += state->input_len;
+                memcpy(line_state->db + line_state->db_len, state->input, input_len);
+                line_state->db_len += input_len;
+            }
         }
         state->input += state->input_len;
         state->input_len = 0;
@@ -401,27 +419,35 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
         line_state->current_line_lf_seen = 1;
 
         if (line_state->current_line_db == 1) {
-            ptmp = FTPRealloc(line_state->db, line_state->db_len,
-                             (line_state->db_len + (lf_idx + 1 - state->input)));
-            if (ptmp == NULL) {
-                FTPFree(line_state->db, line_state->db_len);
-                line_state->db = NULL;
-                line_state->db_len = 0;
-                return -1;
-            }
-            line_state->db = ptmp;
+            if (!state->current_line_truncated) {
+                int32_t input_len = lf_idx + 1 - state->input;
+                if (line_state->db_len + input_len > ftp_max_line_len) {
+                    input_len = ftp_max_line_len - line_state->db_len;
+                    DEBUG_VALIDATE_BUG_ON(input_len < 0);
+                    state->current_line_truncated = true;
+                }
+                if (input_len > 0) {
+                    ptmp = FTPRealloc(
+                            line_state->db, line_state->db_len, (line_state->db_len + input_len));
+                    if (ptmp == NULL) {
+                        FTPFree(line_state->db, line_state->db_len);
+                        line_state->db = NULL;
+                        line_state->db_len = 0;
+                        return -1;
+                    }
+                    line_state->db = ptmp;
 
-            memcpy(line_state->db + line_state->db_len,
-                   state->input, (lf_idx + 1 - state->input));
-            line_state->db_len += (lf_idx + 1 - state->input);
+                    memcpy(line_state->db + line_state->db_len, state->input, input_len);
+                    line_state->db_len += input_len;
 
-            if (line_state->db_len > 1 &&
-                line_state->db[line_state->db_len - 2] == 0x0D) {
-                line_state->db_len -= 2;
-                state->current_line_delimiter_len = 2;
-            } else {
-                line_state->db_len -= 1;
-                state->current_line_delimiter_len = 1;
+                    if (line_state->db_len > 1 && line_state->db[line_state->db_len - 2] == 0x0D) {
+                        line_state->db_len -= 2;
+                        state->current_line_delimiter_len = 2;
+                    } else {
+                        line_state->db_len -= 1;
+                        state->current_line_delimiter_len = 1;
+                    }
+                }
             }
 
             state->current_line = line_state->db;
@@ -429,7 +455,12 @@ static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state)
 
         } else {
             state->current_line = state->input;
-            state->current_line_len = lf_idx - state->input;
+            if (lf_idx - state->input > ftp_max_line_len) {
+                state->current_line_len = ftp_max_line_len;
+                state->current_line_truncated = true;
+            } else {
+                state->current_line_len = lf_idx - state->input;
+            }
 
             if (state->input != lf_idx &&
                 *(lf_idx - 1) == 0x0D) {
@@ -591,6 +622,7 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state,
         tx->command_descriptor = cmd_descriptor;
         tx->request_length = CopyCommandLine(&tx->request,
                 state->current_line, state->current_line_len);
+        tx->request_truncated = state->current_line_truncated;
 
         /* change direction (default to server) so expectation will handle
          * the correct message when expectation will match.
@@ -813,6 +845,7 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS
             FTPString *response = FTPStringAlloc();
             if (likely(response)) {
                 response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len);
+                response->truncated = state->current_line_truncated;
                 TAILQ_INSERT_TAIL(&tx->response_list, response, next);
             }
         }
index d535ce87c3607ba841d0651ff150861e611cde1c..af975bd14d5087035bf6dd715fb68a477823b1f9 100644 (file)
@@ -121,13 +121,14 @@ typedef struct FtpLineState_ {
     uint8_t *db;
     uint32_t db_len;
     uint8_t current_line_db;
-    /** we have see LF for the currently parsed line */
+    /** we have seen LF for the currently parsed line */
     uint8_t current_line_lf_seen;
 } FtpLineState;
 
 typedef struct FTPString_ {
     uint8_t *str;
-    uint16_t len;
+    uint32_t len;
+    bool truncated;
     TAILQ_ENTRY(FTPString_) next;
 } FTPString;
 
@@ -140,6 +141,7 @@ typedef struct FTPTransaction_  {
     /* for the request */
     uint32_t request_length;
     uint8_t *request;
+    bool request_truncated;
 
     /* for the command description */
     const FtpCommand *command_descriptor;
@@ -175,6 +177,7 @@ typedef struct FtpState_ {
     /** length of the line in current_line.  Doesn't include the delimiter */
     uint32_t current_line_len;
     uint8_t current_line_delimiter_len;
+    bool current_line_truncated;
 
     /* 0 for toserver, 1 for toclient */
     FtpLineState line_state[2];
index 619f4148b3ce79371abdc4fef3058469524f58f5..fc24e8e095422998d2ae43003526c5a29a762c63 100644 (file)
@@ -77,8 +77,15 @@ static void EveFTPLogCommand(Flow *f, FTPTransaction *tx, JsonBuilder *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");
+        }
     }
 
+    bool reply_truncated = false;
+
     if (!TAILQ_EMPTY(&tx->response_list)) {
         int resp_code_cnt = 0;
         int resp_cnt = 0;
@@ -89,6 +96,9 @@ static void EveFTPLogCommand(Flow *f, FTPTransaction *tx, JsonBuilder *jb)
             uint8_t *where = response->str;
             uint16_t length = response->len ? response->len -1 : 0;
             uint16_t pos;
+            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 */
@@ -143,6 +153,12 @@ static void EveFTPLogCommand(Flow *f, FTPTransaction *tx, JsonBuilder *jb)
     } else {
         JB_SET_STRING(jb, "reply_received", "no");
     }
+
+    if (reply_truncated) {
+        JB_SET_TRUE(jb, "reply_truncated");
+    } else {
+        JB_SET_FALSE(jb, "reply_truncated");
+    }
 }