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

In Memcached before 1.6.22, an off-by-one error exists when processing proxy requests in proxy mode, if \\n is used instead of \\r\\n.

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

Weakness information:
cwe_id: CWE-193
cwe_name: Off-by-one Error
description: A product calculates or uses an incorrect maximum or minimum value that is 1 more, or 1 less, than the correct value.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/memcached/memcached
commit_details: repo_url: https://github.com/memcached/memcached
author: dormando
author_date: 2023-08-02 15:45:56-07:00
msg: proxy: fix off-by-one if \r is missing

A bunch of the parser assumed we only had \r\n, but I didn't actually
have that strictness set. Some commands worked and some broke in subtle
ways when just "\n" was being submitted.

I'm not 100% confident in this change yet so I'm opening a PR to stage
it while I run some more thorough tests.
repository: repo_url: https://github.com/memcached/memcached
repo_name: memcached/memcached
description: memcached development tree
repo_language: C
owner: memcached
file_changes: filename: proxy.h
change_type: ModificationType.MODIFY
diff: @@ -276,6 +276,7 @@ struct mcp_parser_s {
     uint8_t keytoken; // because GAT. sigh. also cmds without a key.
     uint32_t parsed; // how far into the request we parsed already
     uint32_t reqlen; // full length of request buffer.
+    uint32_t endlen; // index to the start of \r\n or \n
     int vlen;
     uint32_t klen; // length of key.
     uint16_t tokens[PARSER_MAX_TOKENS]; // offsets for start of each token

programming_language: C++
filename: proxy_request.c
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@
 // where we later scan or directly feed data into API's.
 static int _process_tokenize(mcp_parser_t *pr, const size_t max) {
     const char *s = pr->request;
-    int len = pr->reqlen - 2;
+    int len = pr->endlen;
 
     // since multigets can be huge, we can't purely judge reqlen against this
     // limit, but we also can't index past it since the tokens are shorts.
@@ -93,7 +93,7 @@ static int _process_request_key(mcp_parser_t *pr) {
 // Returns the offset for the next key.
 size_t _process_request_next_key(mcp_parser_t *pr) {
     const char *cur = pr->request + pr->parsed;
-    int remain = pr->reqlen - pr->parsed - 2;
+    int remain = pr->endlen - pr->parsed;
 
     // chew off any leading whitespace.
     while (remain) {
@@ -126,7 +126,7 @@ static int _process_request_metaflags(mcp_parser_t *pr, int token) {
         return 0;
     }
     const char *cur = pr->request + pr->tokens[token];
-    const char *end = pr->request + pr->reqlen - 2;
+    const char *end = pr->request + pr->endlen;
 
     // We blindly convert flags into bits, since the range of possible
     // flags is deliberately < 64.
@@ -294,15 +294,25 @@ int process_request(mcp_parser_t *pr, const char *command, size_t cmdlen) {
         return -1;
     }
 
-    const char *s = memchr(command, ' ', cmdlen-2);
+    // Commands can end with bare '\n's. Depressingly I intended to be strict
+    // with a \r\n requirement but never did this and need backcompat.
+    // In this case we _know_ \n is at cmdlen because we can't enter this
+    // function otherwise.
+    if (cm[cmdlen-2] == '\r') {
+        pr->endlen = cmdlen - 2;
+    } else {
+        pr->endlen = cmdlen - 1;
+    }
+
+    const char *s = memchr(command, ' ', pr->endlen);
     if (s != NULL) {
         cl = s - command;
     } else {
-        cl = cmdlen - 2;
+        cl = pr->endlen;
     }
     pr->keytoken = 0;
     pr->has_space = false;
-    pr->parsed = cl + 1;
+    pr->parsed = cl;
     pr->request = command;
     pr->reqlen = cmdlen;
     int token_max = PARSER_MAX_TOKENS;

programming_language: C
filename: proxy.t
change_type: ModificationType.MODIFY
diff: @@ -151,13 +151,14 @@ my $p_sock = $p_srv->sock;
 # NOTE: memcached always allowed [\r]\n for single command lines, but payloads
 # (set/etc) require exactly \r\n as termination.
 # doc/protocol.txt has always specified \r\n for command/response.
-# Proxy is more strict than normal server in this case.
+# Note a bug lead me to believe that the proxy was more strict, we accept any
+# \n or \r\n terminated commands.
 {
     my $s = $srv[0]->sock;
     print $s "version\n";
     like(<$s>, qr/VERSION/, "direct server version cmd with just newline");
     print $p_sock "version\n";
-    like(<$p_sock>, qr/CLIENT_ERROR/, "proxy version cmd with just newline");
+    like(<$p_sock>, qr/VERSION/, "proxy version cmd with just newline");
     print $p_sock "version\r\n";
     like(<$p_sock>, qr/VERSION/, "proxy version cmd with full CRLF");
 }

programming_language: Perl