I found it and fixed it!

It was me again who added a bug in 1.7 with the optimizations for large
headers and large requests. If certain conditions are met, we could read
the \r from previous data (as we guessed) and complain that the next byte
was not an LF once the remaining part arrived.

I'm intending to merge the attached patch. "cas", I'm willing to add you
as the reporter here since you provided lots of very valuable information,
but for this it would be nice if you had a name :-)

I'll backport it to 1.7. Unfortunately there's no easy workaround on an
existing configuration, so I'll produce 1.7.2 soon I guess. I'll try to
add the remaining missing information to make dumps more accurate regarding
the expected state (this would definitely had helped here).

Cheers,
Willy
>From 5afd6967423caa2d55d4c5a5ae0e296b35e2292d Mon Sep 17 00:00:00 2001
From: Willy Tarreau <w...@1wt.eu>
Date: Wed, 4 Jan 2017 14:44:46 +0100
Subject: BUG/MAJOR: http: fix risk of getting invalid reports of bad requests
X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4

Commits 5f10ea3 ("OPTIM: http: improve parsing performance of long URIs")
and 0431f9d ("OPTIM: http: improve parsing performance of long header lines")
introduced a bug in the HTTP parser : when a partial request is read, the
first part ends up on a 8-bytes boundary (or 4-byte on 32-bit machines), the
end lies in the header field value part, and the buffer used to contain a CR
character exactly after the last block, then the parser could be confused and
read this CR character as being part of the current request, then switch to a
new state waiting for an LF character. Then when the next part of the request
appeared, it would read the character following what was erroneously mistaken
for a CR, see that it is not an LF and fail on a bad request. The reason is
that there's no control of and of parsing just after breaking out of the loop.

One way to reproduce it is with this config :

  global
      stats socket /tmp/sock1 mode 666 level admin
      stats timeout 1d

  frontend  px
      bind :8001
      mode http
      timeout client 10s
      redirect location /

And sending requests this way :

  $ tcploop 8001 C P S:"$(dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr 
'\000' '\r')"
  $ tcploop 8001 C P S:"$(dd if=/dev/zero bs=16384 count=1 2>/dev/null | tr 
'\000' '\r')"
  $ tcploop 8001 C P \
    S:"GET / HTTP/1.0\r\nX-padding: 
0123456789.123456789.123456789.123456789.123456789.123456789.1234567" P \
    S:"89.123456789\r\n\r\n" P

Then a "show errors" on the socket will report :

  $ echo "show errors" | socat - /tmp/sock1
  Total events captured on [04/Jan/2017:15:09:15.755] : 32

  [04/Jan/2017:15:09:13.050] frontend px (#2): invalid request
    backend <NONE> (#-1), server <NONE> (#-1), event #31
    src 127.0.0.1:59716, session #91, session flags 0x00000080
    HTTP msg state 17, msg flags 0x00000000, tx flags 0x00000000
    HTTP chunk len 0 bytes, HTTP body len 0 bytes
    buffer flags 0x00808002, out 0 bytes, total 111 bytes
    pending 111 bytes, wrapping at 16384, error at position 107:

    00000  GET / HTTP/1.0\r\n
    00016  X-padding: 
0123456789.123456789.123456789.123456789.123456789.12345678
    00086+ 9.123456789.123456789\r\n
    00109  \r\n

This fix must be backported to 1.7.
---
 src/proto_http.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/proto_http.c b/src/proto_http.c
index aa8d997..d894ef6 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1557,6 +1557,10 @@ const char *http_parse_reqline(struct http_msg *msg,
                        ptr += sizeof(int);
                }
 #endif
+               if (ptr >= end) {
+                       state = HTTP_MSG_RQURI;
+                       goto http_msg_ood;
+               }
        http_msg_rquri2:
                if (likely((unsigned char)(*ptr - 33) <= 93)) /* 33 to 126 
included */
                        EAT_AND_JUMP_OR_RETURN(http_msg_rquri2, HTTP_MSG_RQURI);
@@ -1983,6 +1987,10 @@ void http_msg_analyzer(struct http_msg *msg, struct 
hdr_idx *idx)
                        ptr += sizeof(int);
                }
 #endif
+               if (ptr >= end) {
+                       state = HTTP_MSG_HDR_VAL;
+                       goto http_msg_ood;
+               }
        http_msg_hdr_val2:
                if (likely(!HTTP_IS_CRLF(*ptr)))
                        EAT_AND_JUMP_OR_RETURN(http_msg_hdr_val2, 
HTTP_MSG_HDR_VAL);
-- 
1.7.12.1

Reply via email to