CVE-2023-46852 (published 2023-10-27T20:15Z)

In Memcached before 1.6.22, a buffer overflow exists when processing multiget requests in proxy mode, if there are many spaces after the "get" substring.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-120
cwe_name: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
extended_description: A buffer overflow condition exists when a product attempts to put more data in a buffer than it can hold, or when it attempts to put data in a memory area outside of the boundaries of a buffer. The simplest type of error, and the most common cause of buffer overflows, is the "classic" case in which the product copies the buffer without restricting how much is copied. Other variants exist, but the existence of a classic overflow strongly suggests that the programmer is not considering even the most basic of security protections.
         

Fix information:
repo_url: https://github.com/memcached/memcached
commit_details: repo_url: https://github.com/memcached/memcached
author: dormando
author_date: 2023-07-28 10:32:16-07:00
msg: proxy: fix buffer overflow with multiget syntax

"get[200 spaces]key1 key2\r\n" would overflow a temporary buffer used to
process multiget syntax.

To exploit this you must first pass the check in try_read_command_proxy:
- The request before the first newline must be less than 1024 bytes.
- If it is more than 1024 bytes there is a limit of 100 spaces.
- The key length is still checked at 250 bytes
- Meaning you have up to 772 spaces and then the key to create stack
  corruption.

So the amount of data you can shove in here isn't unlimited.

The fix caps the amount of data pre-key to be reasonable. Something like
GAT needs space for a 32bit TTL which is at most going to be 15 bytes +
spaces, so we limit it to 20 bytes.

I hate hate hate hate hate the multiget syntax. hate it.
repository: repo_url: https://github.com/memcached/memcached
repo_name: memcached/memcached
description: memcached development tree
repo_language: C
owner: memcached
file_changes: filename: proto_proxy.c
change_type: ModificationType.MODIFY
diff: @@ -721,6 +721,12 @@ int proxy_run_coroutine(lua_State *Lc, mc_resp *resp, io_pending_proxy_t *p, con
     return 0;
 }
 
+// basically any data before the first key.
+// max is like 15ish plus spaces. we can be more strict about how many spaces
+// to expect because any client spamming space is being deliberately stupid
+// anyway.
+#define MAX_CMD_PREFIX 20
+
 static void proxy_process_command(conn *c, char *command, size_t cmdlen, bool multiget) {
     assert(c != NULL);
     LIBEVENT_THREAD *thr = c->thread;
@@ -793,12 +799,18 @@ static void proxy_process_command(conn *c, char *command, size_t cmdlen, bool mu
     if (!multiget && pr.cmd_type == CMD_TYPE_GET && pr.has_space) {
         uint32_t keyoff = pr.tokens[pr.keytoken];
         while (pr.klen != 0) {
-            char temp[KEY_MAX_LENGTH + 30];
+            char temp[KEY_MAX_LENGTH + MAX_CMD_PREFIX + 30];
             char *cur = temp;
             // Core daemon can abort the entire command if one key is bad, but
             // we cannot from the proxy. Instead we have to inject errors into
             // the stream. This should, thankfully, be rare at least.
-            if (pr.klen > KEY_MAX_LENGTH) {
+            if (pr.tokens[pr.keytoken] > MAX_CMD_PREFIX) {
+                if (!resp_start(c)) {
+                    conn_set_state(c, conn_closing);
+                    return;
+                }
+                proxy_out_errstring(c->resp, PROXY_CLIENT_ERROR, "malformed request");
+            } else if (pr.klen > KEY_MAX_LENGTH) {
                 if (!resp_start(c)) {
                     conn_set_state(c, conn_closing);
                     return;

programming_language: C
filename: proxyunits.t
change_type: ModificationType.MODIFY
diff: @@ -151,6 +151,12 @@ sub proxy_test {
         print $ps "$_\r\n";
         is(scalar <$ps>, "CLIENT_ERROR parsing request\r\n", "$_ got CLIENT_ERROR for too few tokens");
     }
+
+    my $space = ' ' x 200;
+    print $ps "get$space key key\r\n";
+    is(scalar <$ps>, "CLIENT_ERROR malformed request\r\n");
+    is(scalar <$ps>, "CLIENT_ERROR malformed request\r\n");
+    is(scalar <$ps>, "END\r\n"); # god damn multiget syntax.
 }
 
 # Basic test with a backend; write a request to the client socket, read it

programming_language: Perl