Blame SOURCES/redis-CVE-2021-32675.patch

e8abc3
From 5674b0057ff2903d43eaff802017eddf37c360f8 Mon Sep 17 00:00:00 2001
e8abc3
From: Oran Agra <oran@redislabs.com>
e8abc3
Date: Wed, 9 Jun 2021 17:31:39 +0300
e8abc3
Subject: [PATCH] Prevent unauthenticated client from easily consuming lots of
e8abc3
 memory (CVE-2021-32675)
e8abc3
e8abc3
This change sets a low limit for multibulk and bulk length in the
e8abc3
protocol for unauthenticated connections, so that they can't easily
e8abc3
cause redis to allocate massive amounts of memory by sending just a few
e8abc3
characters on the network.
e8abc3
The new limits are 10 arguments of 16kb each (instead of 1m of 512mb)
e8abc3
---
e8abc3
 src/networking.c    | 17 +++++++++++++++++
e8abc3
 src/server.c        |  9 ++-------
e8abc3
 src/server.h        |  1 +
e8abc3
 tests/unit/auth.tcl | 16 ++++++++++++++++
e8abc3
 4 files changed, 36 insertions(+), 7 deletions(-)
e8abc3
e8abc3
diff --git a/src/networking.c b/src/networking.c
e8abc3
index a61678dab2b..b02397c96f4 100644
e8abc3
--- a/src/networking.c
e8abc3
+++ b/src/networking.c
e8abc3
@@ -97,6 +97,15 @@ void linkClient(client *c) {
e8abc3
     raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
e8abc3
 }
e8abc3
 
e8abc3
+int authRequired(client *c) {
e8abc3
+    /* Check if the user is authenticated. This check is skipped in case
e8abc3
+     * the default user is flagged as "nopass" and is active. */
e8abc3
+    int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
e8abc3
+                          (DefaultUser->flags & USER_FLAG_DISABLED)) &&
e8abc3
+                        !c->authenticated;
e8abc3
+    return auth_required;
e8abc3
+}
e8abc3
+
e8abc3
 client *createClient(connection *conn) {
e8abc3
     client *c = zmalloc(sizeof(client));
e8abc3
 
e8abc3
@@ -1744,6 +1753,10 @@ int processMultibulkBuffer(client *c) {
e8abc3
             addReplyError(c,"Protocol error: invalid multibulk length");
e8abc3
             setProtocolError("invalid mbulk count",c);
e8abc3
             return C_ERR;
e8abc3
+        } else if (ll > 10 && authRequired(c)) {
e8abc3
+            addReplyError(c, "Protocol error: unauthenticated multibulk length");
e8abc3
+            setProtocolError("unauth mbulk count", c);
e8abc3
+            return C_ERR;
e8abc3
         }
e8abc3
 
e8abc3
         c->qb_pos = (newline-c->querybuf)+2;
e8abc3
@@ -1791,6 +1804,10 @@ int processMultibulkBuffer(client *c) {
e8abc3
                 addReplyError(c,"Protocol error: invalid bulk length");
e8abc3
                 setProtocolError("invalid bulk length",c);
e8abc3
                 return C_ERR;
e8abc3
+            } else if (ll > 16384 && authRequired(c)) {
e8abc3
+                addReplyError(c, "Protocol error: unauthenticated bulk length");
e8abc3
+                setProtocolError("unauth bulk length", c);
e8abc3
+                return C_ERR;
e8abc3
             }
e8abc3
 
e8abc3
             c->qb_pos = newline-c->querybuf+2;
e8abc3
diff --git a/src/server.c b/src/server.c
e8abc3
index 93148f8e3ed..c8768b1824b 100644
e8abc3
--- a/src/server.c
e8abc3
+++ b/src/server.c
e8abc3
@@ -3590,13 +3590,8 @@ int processCommand(client *c) {
e8abc3
     int is_denyloading_command = !(c->cmd->flags & CMD_LOADING) ||
e8abc3
                                  (c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_LOADING));
e8abc3
 
e8abc3
-    /* Check if the user is authenticated. This check is skipped in case
e8abc3
-     * the default user is flagged as "nopass" and is active. */
e8abc3
-    int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
e8abc3
-                          (DefaultUser->flags & USER_FLAG_DISABLED)) &&
e8abc3
-                        !c->authenticated;
e8abc3
-    if (auth_required) {
e8abc3
-        /* AUTH and HELLO and no auth modules are valid even in
e8abc3
+    if (authRequired(c)) {
e8abc3
+        /* AUTH and HELLO and no auth commands are valid even in
e8abc3
          * non-authenticated state. */
e8abc3
         if (!(c->cmd->flags & CMD_NO_AUTH)) {
e8abc3
             rejectCommand(c,shared.noautherr);
e8abc3
diff --git a/src/server.h b/src/server.h
e8abc3
index a16f2885829..530355421a8 100644
e8abc3
--- a/src/server.h
e8abc3
+++ b/src/server.h
e8abc3
@@ -1743,6 +1743,7 @@ void protectClient(client *c);
e8abc3
 void unprotectClient(client *c);
e8abc3
 void initThreadedIO(void);
e8abc3
 client *lookupClientByID(uint64_t id);
e8abc3
+int authRequired(client *c);
e8abc3
 
e8abc3
 #ifdef __GNUC__
e8abc3
 void addReplyErrorFormat(client *c, const char *fmt, ...)
e8abc3
diff --git a/tests/unit/auth.tcl b/tests/unit/auth.tcl
e8abc3
index 9080d4bf7e9..530ca7c8d91 100644
e8abc3
--- a/tests/unit/auth.tcl
e8abc3
+++ b/tests/unit/auth.tcl
e8abc3
@@ -24,4 +24,20 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
e8abc3
         r set foo 100
e8abc3
         r incr foo
e8abc3
     } {101}
e8abc3
+
e8abc3
+    test {For unauthenticated clients multibulk and bulk length are limited} {
e8abc3
+        set rr [redis [srv "host"] [srv "port"] 0 $::tls]
e8abc3
+        $rr write "*100\r\n"
e8abc3
+        $rr flush
e8abc3
+        catch {[$rr read]} e
e8abc3
+        assert_match {*unauthenticated multibulk length*} $e
e8abc3
+        $rr close
e8abc3
+
e8abc3
+        set rr [redis [srv "host"] [srv "port"] 0 $::tls]
e8abc3
+        $rr write "*1\r\n\$100000000\r\n"
e8abc3
+        $rr flush
e8abc3
+        catch {[$rr read]} e
e8abc3
+        assert_match {*unauthenticated bulk length*} $e
e8abc3
+        $rr close
e8abc3
+    }
e8abc3
 }