From 80e55ef8ba5b51eff6477d465b4326d94388ebf0 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Sun, 23 May 2021 20:32:33 +0200
Subject: [PATCH] CMD: Limit the command name size to avoid big recursive calls

We do that because an ill-intentioned client might send a 2000 character
long command name to try to SEGV the lektord process because of a stack
to small to handle all the recursive calls.

The recursive calls are done in the trie find function, where the stack
consumed by each calls increases each time a recursive call is done
(indexes and other local variables...).
---
 inc/lektor/common.h | 13 +++++----
 src/net/command.c   | 69 ++++++++++++++++++++++++++++++++-------------
 2 files changed, 57 insertions(+), 25 deletions(-)

diff --git a/inc/lektor/common.h b/inc/lektor/common.h
index 82a58096..5e80b94b 100644
--- a/inc/lektor/common.h
+++ b/inc/lektor/common.h
@@ -136,14 +136,15 @@ void ___lkt_print(const char *section, const char *format, ...);
 /* Default iteration count in search_iter */
 #define LKT_DB_DEFAULT_ITERATION_MAX 100
 
-#define LEKTOR_TAG_MAX        256
-#define LKT_MESSAGE_ARGS_MAX  32
-#define LKT_MESSAGE_MAX       2048
-#define LKT_DEFAULT_LIST_SIZE 10
+#define LEKTOR_TAG_MAX          256     /* Max length of a tag                          */
+#define LKT_COMMAND_LEN_MAX     32      /* MPD commands are at most 32 character long   */
+#define LKT_MESSAGE_ARGS_MAX    32      /* At most 32 words in a command are supported  */
+#define LKT_MESSAGE_MAX         2048    /* A message is at most <defined> chars long    */
+#define LKT_DEFAULT_LIST_SIZE   10
 
 #define LKT_BACKLOG      32
-#define COMMAND_LIST_MAX 64
-#define BUFFER_OUT_MAX   16
+#define COMMAND_LIST_MAX 64             /* At most 64 commands per client */
+#define BUFFER_OUT_MAX   16             /* At most 16 messages per client */
 #define MPD_VERSION      "0.21.16"
 
 typedef volatile enum {
diff --git a/src/net/command.c b/src/net/command.c
index 0f9321ce..3d72a8bd 100644
--- a/src/net/command.c
+++ b/src/net/command.c
@@ -3,39 +3,64 @@
 #include <lektor/common.h>
 #include <lektor/net.h>
 
+PRIVATE_FUNCTION void
+___crop_string(char *string, const size_t max_len)
+{
+    if (strlen(string) > max_len) {
+        string[max_len] = '\0';
+    }
+}
+
 struct lkt_command
 lkt_command_parse(char *raw)
 {
     errno = 0;
     char *endptr;
-    struct lkt_command res = { .name = raw, .args = { 0 }, .cont = strtol(raw, &endptr, 10) };
+    bool skip_continuation = false;
+    struct lkt_command res = {
+        .name = raw,
+        .args = { 0 },
+        .cont = strtol(raw, &endptr, 10)
+    };
 
+    /* Do we need the continuation? */
     if (errno != 0) {
         res.cont = 0;
-        goto skip_cont;
-    }
-
-    if (endptr == raw) {
+        skip_continuation = true;
+    } else if (endptr == raw) {
         res.cont = 0;
-        goto skip_cont;
+        skip_continuation = true;
     }
 
-    if (*endptr == '\0') {
+    /* Need the continuation here. */
+    if (!skip_continuation) {
         /* That thing is an error. */
-        return res;
-    }
+        if (*endptr == '\0') {
+            ___crop_string(res.name, LKT_COMMAND_LEN_MAX);
+            return res;
+        }
 
-    /* Get the real command name. */
-    raw += strspn(raw, " 1234567890");
-    if (!*raw)
-        return res;
-    res.name = raw;
+        /* Get the real command name and not the continuation number. */
+        raw += strspn(raw, " 1234567890");
+        if (!*raw) {
+            /* No real name found, only got integers, we will fail later. */
+            ___crop_string(res.name, LKT_COMMAND_LEN_MAX);
+            return res;
+        }
+        res.name = raw;
+    }
 
-    /* Get the args. */
-skip_cont:
+    /* Get the args. We skip the first word in the 'raw'. */
     raw += strcspn(raw, " ");
-    if (!*raw)
+
+    /* We also restrict the number of characters of the command, to avoid some
+     * incredibly hight recursive calls... */
+    ___crop_string(res.name, LKT_COMMAND_LEN_MAX);
+
+    if (!*raw) {
+        /* Here we have only one argument. */
         return res;
+    }
 
     *raw++ = 0;
     raw += strspn(raw, " ");
@@ -49,7 +74,9 @@ skip_cont:
                 raw += strcspn(raw, " ");
                 *raw++ = 0;
             }
-        } else if (*raw == '\'') {
+        }
+
+        else if (*raw == '\'') {
             res.args[i] = ++raw;
             raw += strcspn(raw, "'");
             if (*raw) {
@@ -57,14 +84,18 @@ skip_cont:
                 raw += strcspn(raw, " ");
                 *raw++ = 0;
             }
-        } else {
+        }
+
+        else {
             res.args[i] = raw;
             raw += strcspn(raw, " ");
             *raw++ = 0;
         }
+
         raw += strspn(raw, " ");
     }
 
+    /* The command has arguments. */
     return res;
 }
 
-- 
GitLab