From 2192bd4200083a0d20bf601c2fc9d635e7e4dbfc Mon Sep 17 00:00:00 2001
From: Eric Covener <covener@apache.org>
Date: Tue, 10 Jan 2023 13:18:26 +0000
Subject: [PATCH] fail on bad header

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1906539 13f79535-47bb-0310-9956-ffa450edef68
---
 modules/proxy/mod_proxy_http.c | 46 ++++++++++++++++++++--------------
 server/protocol.c              |  2 ++
 2 files changed, 29 insertions(+), 19 deletions(-)

Index: httpd-2.2.34/modules/proxy/mod_proxy_http.c
===================================================================
--- httpd-2.2.34.orig/modules/proxy/mod_proxy_http.c
+++ httpd-2.2.34/modules/proxy/mod_proxy_http.c
@@ -25,6 +25,9 @@ static apr_status_t ap_proxy_http_cleanu
                                           request_rec *r,
                                           proxy_conn_rec *backend);
 
+static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
+                                    request_rec *r, int flags, int *read);
+
 /*
  * Canonicalise http-like URLs.
  *  scheme is the scheme for the URL
@@ -1233,17 +1236,18 @@ static void process_proxy_header(request
  * any sense at all, since we depend on buffer still containing
  * what was read by ap_getline() upon return.
  */
-static void ap_proxy_read_headers(request_rec *r, request_rec *rr,
+static apr_status_t ap_proxy_read_headers(request_rec *r, request_rec *rr,
                                   char *buffer, int size,
                                   conn_rec *c, int *pread_len)
 {
     int len;
     char *value, *end;
-    char field[MAX_STRING_LEN];
     int saw_headers = 0;
     void *sconf = r->server->module_config;
     proxy_server_conf *psc;
     proxy_dir_conf *dconf;
+    apr_status_t rc;
+    apr_bucket_brigade *tmp_bb;
 
     dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
     psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
@@ -1256,7 +1260,38 @@ static void ap_proxy_read_headers(reques
      * Read header lines until we get the empty separator line, a read error,
      * the connection closes (EOF), or we timeout.
      */
-    while ((len = ap_getline(buffer, size, rr, 1)) > 0) {
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+                  "Headers received from backend:");
+
+    tmp_bb = apr_brigade_create(r->pool, c->bucket_alloc);
+    while (1) {
+        rc = ap_proxygetline(tmp_bb, buffer, size, rr,
+                             AP_GETLINE_FOLD | AP_GETLINE_NOSPC_EOL, &len);
+
+
+        if (rc != APR_SUCCESS) {
+            if (APR_STATUS_IS_ENOSPC(rc)) {
+                int trunc = (len > 128 ? 128 : len) / 2;
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124)
+                        "header size is over the limit allowed by "
+                        "ResponseFieldSize (%d bytes). "
+                        "Bad response header: '%.*s[...]%s'",
+                        size, trunc, buffer, buffer + len - trunc);
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10404)
+                              "Error reading headers from backend");
+            }
+            r->headers_out = NULL;
+            return rc;
+        }
+
+        if (len <= 0) {
+            break;
+        }
+        else {
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "%s", buffer);
+        }
 
         if (!(value = strchr(buffer, ':'))) {     /* Find the colon separator */
 
@@ -1275,7 +1310,7 @@ static void ap_proxy_read_headers(reques
                 if (psc->badopt == bad_error) {
                     /* Nope, it wasn't even an extra HTTP header. Give up. */
                     r->headers_out = NULL;
-                    return ;
+                    return APR_EINVAL;
                 }
                 else if (psc->badopt == bad_body) {
                     /* if we've already started loading headers_out, then
@@ -1288,12 +1323,12 @@ static void ap_proxy_read_headers(reques
                          "proxy: Starting body due to bogus non-header in headers "
                          "returned by %s (%s)", r->uri, r->method);
                         *pread_len = len;
-                        return ;
+                        return APR_SUCCESS;
                     } else {
                          ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
                          "proxy: No HTTP headers "
                          "returned by %s (%s)", r->uri, r->method);
-                        return ;
+                        return APR_SUCCESS;
                     }
                 }
             }
@@ -1313,8 +1348,7 @@ static void ap_proxy_read_headers(reques
             ++value;            /* Skip to start of value   */
 
         /* should strip trailing whitespace as well */
-        for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --
-end)
+        for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
             *end = '\0';
 
         /* make sure we add so as not to destroy duplicated headers
@@ -1323,17 +1357,8 @@ end)
          */
         process_proxy_header(r, dconf, buffer, value) ;
         saw_headers = 1;
-
-        /* the header was too long; at the least we should skip extra data */
-        if (len >= size - 1) {
-            while ((len = ap_getline(field, MAX_STRING_LEN, rr, 1))
-                    >= MAX_STRING_LEN - 1) {
-                /* soak up the extra data */
-            }
-            if (len == 0) /* time to exit the larger loop as well */
-                break;
-        }
     }
+    return APR_SUCCESS;
 }
 
 
@@ -1344,23 +1369,19 @@ static int addit_dammit(void *v, const c
     return 1;
 }
 
-static
-apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n, request_rec *r,
-                             int fold, int *writen)
+static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n,
+                                    request_rec *r, int flags, int *read)
 {
-    char *tmp_s = s;
     apr_status_t rv;
     apr_size_t len;
 
-    rv = ap_rgetline(&tmp_s, n, &len, r, fold, bb);
+    rv = ap_rgetline(&s, n, &len, r, flags, bb);
     apr_brigade_cleanup(bb);
 
-    if (rv == APR_SUCCESS) {
-        *writen = (int) len;
-    } else if (rv == APR_ENOSPC) {
-        *writen = n;
+    if (rv == APR_SUCCESS || APR_STATUS_IS_ENOSPC(rv)) {
+        *read = (int)len;
     } else {
-        *writen = -1;
+        *read = -1;
     }
 
     return rv;
@@ -1557,10 +1578,10 @@ apr_status_t ap_proxy_http_process_respo
                          "Set-Cookie", NULL);
 
             /* shove the headers direct into r->headers_out */
-            ap_proxy_read_headers(r, rp, buffer, sizeof(buffer), origin,
+            rc = ap_proxy_read_headers(r, rp, buffer, sizeof(buffer), origin,
                                   &pread_len);
 
-            if (r->headers_out == NULL) {
+            if (rc != APR_SUCCESS || r->headers_out == NULL) {
                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
                              r->server, "proxy: bad HTTP/%d.%d header "
                              "returned by %s (%s)", major, minor, r->uri,
Index: httpd-2.2.34/server/protocol.c
===================================================================
--- httpd-2.2.34.orig/server/protocol.c
+++ httpd-2.2.34/server/protocol.c
@@ -219,9 +219,12 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
     int do_alloc = (*s == NULL), saw_eos = 0;
     int fold = flags & AP_GETLINE_FOLD;
     int crlf = flags & AP_GETLINE_CRLF;
+    int nospc_eol = flags & AP_GETLINE_NOSPC_EOL;
+    int saw_eol = 0, saw_nospc = 0;
 
     if (!n) {
         /* Needs room for NUL byte at least */
+        *read = 0;
         return APR_BADARG;
     }
 
@@ -233,7 +236,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
     if (last_char)
         *last_char = '\0';
 
-    for (;;) {
+    do {
         apr_brigade_cleanup(bb);
         rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE,
                             APR_BLOCK_READ, 0);
@@ -277,8 +280,52 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
 
             /* Would this overrun our buffer?  If so, we'll die. */
             if (n < bytes_handled + len) {
-                rv = APR_ENOSPC;
-                goto cleanup;
+                /* Before we die, let's fill the buffer up to its limit (i.e.
+                 * fall through with the remaining length, if any), setting
+                 * saw_eol on LF to stop the outer loop appropriately; we may
+                 * come back here once the buffer is filled (no LF seen), and
+                 * either be done at that time or continue to wait for LF here
+                 * if nospc_eol is set.
+                 *
+                 * But there is also a corner cases which we want to address,
+                 * namely if the buffer is overrun by the final LF only (i.e.
+                 * the CR fits in); this is not really an overrun since we'll
+                 * strip the CR finally (and use it for NUL byte), but anyway
+                 * we have to handle the case so that it's not returned to the
+                 * caller as part of the truncated line (it's not!). This is
+                 * easier to consider that LF is out of counting and thus fall
+                 * through with no error (saw_eol is set to 2 so that we later
+                 * ignore LF handling already done here), while folding and
+                 * nospc_eol logics continue to work (or fail) appropriately.
+                 */
+                saw_eol = (str[len - 1] == APR_ASCII_LF);
+                if (/* First time around */
+                    saw_eol && !saw_nospc
+                    /*  Single LF completing the buffered CR, */
+                    && ((len == 1 && ((*s)[bytes_handled - 1] == APR_ASCII_CR))
+                    /*  or trailing CRLF overuns by LF only */
+                        || (len > 1 && str[len - 2] == APR_ASCII_CR
+                            && n - bytes_handled + 1 == len))) {
+                    /* In both cases *last_char is (to be) the CR stripped by
+                     * later 'bytes_handled = last_char - *s'.
+                     */
+                    saw_eol = 2;
+                }
+                else {
+                    /* In any other case we'd lose data. */
+                    rv = APR_ENOSPC;
+                    saw_nospc = 1;
+                }
+                len = n - bytes_handled;
+                if (!len) {
+                    if (saw_eol) {
+                        break;
+                    }
+                    if (nospc_eol) {
+                        continue;
+                    }
+                    goto cleanup;
+                }
             }
 
             /* Do we have to handle the allocation ourselves? */
@@ -317,18 +364,28 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
 
         /* If we got a full line of input, stop reading */
         if (last_char && (*last_char == APR_ASCII_LF)) {
-            break;
+            saw_eol = 1;
         }
+    } while (!saw_eol);
+
+    if (rv != APR_SUCCESS) {
+        /* End of line after APR_ENOSPC above */
+        goto cleanup;
     }
 
     /* Now terminate the string at the end of the line;
-     * if the last-but-one character is a CR, terminate there */
-    if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
-        last_char--;
-    }
-    else if (crlf) {
-        rv = APR_EINVAL;
-        goto cleanup;
+     * if the last-but-one character is a CR, terminate there.
+     * LF is handled above (not accounted) when saw_eol == 2,
+     * the last char is CR to terminate at still.
+     */
+    if (saw_eol < 2) {
+        if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
+            last_char--;
+        }
+        else if (crlf) {
+            rv = APR_EINVAL;
+            goto cleanup;
+        }
     }
     bytes_handled = last_char - *s;
 
@@ -402,8 +459,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
 
                     next_size = n - bytes_handled;
 
-                    rv = ap_rgetline_core(&tmp, next_size,
-                                          &next_len, r, 0, bb);
+                    rv = ap_rgetline_core(&tmp, next_size, &next_len, r,
+                                          flags & ~AP_GETLINE_FOLD, bb);
                     if (rv != APR_SUCCESS) {
                         goto cleanup;
                     }
@@ -437,22 +494,22 @@ cleanup:
     if (bytes_handled >= n) {
         bytes_handled = n - 1;
     }
+
+    *read = bytes_handled;
     if (*s) {
         /* ensure the string is NUL terminated */
-        (*s)[bytes_handled] = '\0';
-    }
-    *read = bytes_handled;
-
-    if (rv != APR_SUCCESS) {
-        return rv;
-    }
+        (*s)[*read] = '\0';
 
-    /* PR#43039: We shouldn't accept NULL bytes within the line */
-    if (strlen(*s) < bytes_handled) {
-        return APR_EINVAL;
+        /* PR#43039: We shouldn't accept NULL bytes within the line */
+        bytes_handled = strlen(*s);
+        if (bytes_handled < *read) {
+            *read = bytes_handled;
+            if (rv == APR_SUCCESS) {
+                rv = APR_EINVAL;
+            }
+        }
     }
-
-    return APR_SUCCESS;
+    return rv;
 }
 
 #if APR_CHARSET_EBCDIC
Index: httpd-2.2.34/include/http_protocol.h
===================================================================
--- httpd-2.2.34.orig/include/http_protocol.h
+++ httpd-2.2.34/include/http_protocol.h
@@ -558,6 +558,8 @@ AP_CORE_DECLARE(void) ap_parse_uri(reque
 
 #define AP_GETLINE_FOLD 1 /* Whether to merge continuation lines */
 #define AP_GETLINE_CRLF 2 /*Whether line ends must be in the form CR LF */
+#define AP_GETLINE_NOSPC_EOL 4 /* Whether to consume up to and including the
+                                  end of line on APR_ENOSPC */
 
 /**
  * Get the next line of input for the request
Index: httpd-2.2.34/include/http_log.h
===================================================================
--- httpd-2.2.34.orig/include/http_log.h
+++ httpd-2.2.34/include/http_log.h
@@ -47,6 +47,14 @@ extern "C" {
 #define APLOG_NOTICE    LOG_NOTICE    /* normal but significant condition */
 #define APLOG_INFO      LOG_INFO      /* informational */
 #define APLOG_DEBUG     LOG_DEBUG     /* debug-level messages */
+#define APLOG_TRACE1   (LOG_DEBUG + 1)  /* trace-level 1 messages */
+#define APLOG_TRACE2   (LOG_DEBUG + 2)  /* trace-level 2 messages */
+#define APLOG_TRACE3   (LOG_DEBUG + 3)  /* trace-level 3 messages */
+#define APLOG_TRACE4   (LOG_DEBUG + 4)  /* trace-level 4 messages */
+#define APLOG_TRACE5   (LOG_DEBUG + 5)  /* trace-level 5 messages */
+#define APLOG_TRACE6   (LOG_DEBUG + 6)  /* trace-level 6 messages */
+#define APLOG_TRACE7   (LOG_DEBUG + 7)  /* trace-level 7 messages */
+#define APLOG_TRACE8   (LOG_DEBUG + 8)  /* trace-level 8 messages */
 
 #define APLOG_LEVELMASK LOG_PRIMASK   /* mask off the level value */
 
@@ -60,8 +68,17 @@ extern "C" {
 #define	APLOG_NOTICE	5	/* normal but significant condition */
 #define	APLOG_INFO	6	/* informational */
 #define	APLOG_DEBUG	7	/* debug-level messages */
+#define APLOG_TRACE1     8     /* trace-level 1 messages */
+#define APLOG_TRACE2     9     /* trace-level 2 messages */
+#define APLOG_TRACE3    10     /* trace-level 3 messages */
+#define APLOG_TRACE4    11     /* trace-level 4 messages */
+#define APLOG_TRACE5    12     /* trace-level 5 messages */
+#define APLOG_TRACE6    13     /* trace-level 6 messages */
+#define APLOG_TRACE7    14     /* trace-level 7 messages */
+#define APLOG_TRACE8    15     /* trace-level 8 messages */
 
-#define	APLOG_LEVELMASK	7	/* mask off the level value */
+
+#define	APLOG_LEVELMASK	15	/* mask off the level value */
 
 #endif
 
@@ -91,6 +108,14 @@ extern "C" {
 #define DEFAULT_LOGLEVEL	APLOG_WARNING
 #endif
 
+/**
+ * APLOGNO() should be used at the start of the format string passed
+ * to ap_log_error() and friends. The argument must be a 5 digit decimal
+ * number. It creates a tag of the form "AH02182: "
+ * See docs/log-message-tags/README for details.
+ */
+#define APLOGNO(n)              "AH" #n ": "
+
 extern int AP_DECLARE_DATA ap_default_loglevel;
 
 #define APLOG_MARK	__FILE__,__LINE__
