CVE-2021-46784: Reachable Assertion (CWE-617)
value: In Squid 3.x through 3.5.28, 4.x through 4.17, and 5.x before 5.6, due to improper buffer management, a Denial of Service can occur when processing long Gopher server responses.
CWE description: The product contains an assert() or similar statement that can be triggered by an attacker, which leads to an application exit or other behavior that is more severe than necessary.
CVSS v3 base score: 6.5
Published: 2022-07-17T22:15Z
Repository: https://github.com/squid-cache/squid
Language: C++
Files changed: src/gopher.cc
Commit date: 2022-04-19 19:17:42 +0000
Commit message: Improve handling of Gopher responses (#1022)
Diff: diff --git a/src/gopher.cc b/src/gopher.cc
index 95afd9528f..409c1808c6 100644
--- a/src/gopher.cc
+++ b/src/gopher.cc
@@ -363,21 +363,19 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
 {
     char *pos = inbuf;
     char *lpos = NULL;
     char *tline = NULL;
     LOCAL_ARRAY(char, line, TEMP_BUF_SIZE);
-    LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
     char *name = NULL;
     char *selector = NULL;
     char *host = NULL;
     char *port = NULL;
     char *escaped_selector = NULL;
     const char *icon_url = NULL;
     char gtype;
     StoreEntry *entry = NULL;
 
-    memset(tmpbuf, '\0', TEMP_BUF_SIZE);
     memset(line, '\0', TEMP_BUF_SIZE);
 
     entry = gopherState->entry;
 
     if (gopherState->conversion == GopherStateData::HTML_INDEX_PAGE) {
@@ -408,11 +406,11 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
         gopherState->HTML_header_added = 1;
 
         return;
     }
 
-    String outbuf;
+    SBuf outbuf;
 
     if (!gopherState->HTML_header_added) {
         if (gopherState->conversion == GopherStateData::HTML_CSO_RESULT)
             gopherHTMLHeader(entry, "CSO Search Result", NULL);
         else
@@ -580,41 +578,38 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
                     default:
                         icon_url = mimeGetIconURL("internal-unknown");
                         break;
                     }
 
-                    memset(tmpbuf, '\0', TEMP_BUF_SIZE);
-
                     if ((gtype == GOPHER_TELNET) || (gtype == GOPHER_3270)) {
                         if (strlen(escaped_selector) != 0)
-                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
+                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
                                      icon_url, escaped_selector, rfc1738_escape_part(host),
                                      *port ? ":" : "", port, html_quote(name));
                         else
-                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
+                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
                                      icon_url, rfc1738_escape_part(host), *port ? ":" : "",
                                      port, html_quote(name));
 
                     } else if (gtype == GOPHER_INFO) {
-                        snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
+                        outbuf.appendf("\t%s\n", html_quote(name));
                     } else {
                         if (strncmp(selector, "GET /", 5) == 0) {
                             /* WWW link */
-                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
+                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
                                      icon_url, host, rfc1738_escape_unescaped(selector + 5), html_quote(name));
                         } else if (gtype == GOPHER_WWW) {
-                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"%s\">%s</A>\n",
+                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
                                      icon_url, rfc1738_escape_unescaped(selector), html_quote(name));
                         } else {
                             /* Standard link */
-                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
+                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
                                      icon_url, host, gtype, escaped_selector, html_quote(name));
                         }
                     }
 
                     safe_free(escaped_selector);
-                    outbuf.append(tmpbuf);
                 } else {
                     memset(line, '\0', TEMP_BUF_SIZE);
                     continue;
                 }
             } else {
@@ -643,17 +638,16 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
 
                 if (code != 200)
                     break;
 
                 if (gopherState->cso_recno != recno) {
-                    snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
+                    outbuf.appendf("</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
                     gopherState->cso_recno = recno;
                 } else {
-                    snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
+                    outbuf.appendf("%s\n", html_quote(result));
                 }
 
-                outbuf.append(tmpbuf);
                 break;
             } else {
                 int code;
                 char *s_code, *result;
 
@@ -677,12 +671,11 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
 
                 case 501:   /* No Match */
 
                 case 502: { /* Too Many Matches */
                     /* Print the message the server returns */
-                    snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
-                    outbuf.append(tmpbuf);
+                    outbuf.appendf("</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
                     break;
                 }
 
                 }
             }
@@ -694,17 +687,16 @@ gopherToHTML(GopherStateData * gopherState, char *inbuf, int len)
 
         }           /* switch */
 
     }               /* while loop */
 
-    if (outbuf.size() > 0) {
-        entry->append(outbuf.rawBuf(), outbuf.size());
+    if (outbuf.length() > 0) {
+        entry->append(outbuf.rawContent(), outbuf.length());
         /* now let start sending stuff to client */
         entry->flush();
     }
 
-    outbuf.clean();
     return;
 }
 
 static void
 gopherTimeout(const CommTimeoutCbParams &io)

Vulnerable code:     LOCAL_ARRAY(char, tmpbuf, TEMP_BUF_SIZE);
    memset(tmpbuf, '\0', TEMP_BUF_SIZE);
    String outbuf;
                    memset(tmpbuf, '\0', TEMP_BUF_SIZE);

                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
                        snprintf(tmpbuf, TEMP_BUF_SIZE, "\t%s\n", html_quote(name));
                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"%s\">%s</A>\n",
                            snprintf(tmpbuf, TEMP_BUF_SIZE, "<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
                    outbuf.append(tmpbuf);
                    snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
                    snprintf(tmpbuf, TEMP_BUF_SIZE, "%s\n", html_quote(result));
                outbuf.append(tmpbuf);
                    snprintf(tmpbuf, TEMP_BUF_SIZE, "</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
                    outbuf.append(tmpbuf);
    if (outbuf.size() > 0) {
        entry->append(outbuf.rawBuf(), outbuf.size());
    outbuf.clean();
Fixed code:     SBuf outbuf;
                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s@%s%s%s/\">%s</A>\n",
                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"telnet://%s%s%s/\">%s</A>\n",
                        outbuf.appendf("\t%s\n", html_quote(name));
                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"http://%s/%s\">%s</A>\n",
                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
                            outbuf.appendf("<IMG border=\"0\" SRC=\"%s\"> <A HREF=\"gopher://%s/%c%s\">%s</A>\n",
                    outbuf.appendf("</PRE><HR noshade size=\"1px\"><H2>Record# %d<br><i>%s</i></H2>\n<PRE>", recno, html_quote(result));
                    outbuf.appendf("%s\n", html_quote(result));
                    outbuf.appendf("</PRE><HR noshade size=\"1px\"><H2>%s</H2>\n<PRE>", html_quote(result));
    if (outbuf.length() > 0) {
        entry->append(outbuf.rawContent(), outbuf.length());