Closed neonics closed 6 months ago
Thanks for the detail report ! Indeed there is an issue. And the same may happen with empty payload in H1 if the message is chunked. In fact, EOT
(end-of-trailers) HTX block is not properly handled in the FCGI multiplexer. All versions from the 2.4 are affected. I will fix it soon.
Fix pushed in 3.0-dev. It will be backported as usual. Thanks !
Detailed Description of the Problem
When doing a POST, PUT or PATCH request without a payload over HTTP/2, HAProxy does not send the empty FCGI_STDIN record causing some FCGI servers to wait indefinitely to start processing the request, resulting in a gateway timeout and risking DOS.
HAProxy does send the empty STDIN record properly when doing, for example:
Here are pcap dumps of some of the above requests (zipped because pcap files aren't accepted): pcap-dumps.zip
A screenshot for reference of the POST over HTTP/1.1:
A screenshot for reference of the POST over HTTP/2: Note that here, in line 4, the FCGI_STDIN is not sent.
There may be other request methods that have the same problem. It is probable that this is also a problem with HTTP/3 although I have been unable to test this.
Expected Behavior
Have HAProxy constently send the empty FCGI STDIN record indicating the end of the request data, regardless of the HTTP version used to make the request at the frontend or the request method used (GET, POST, PUT, PATCH etc).
Steps to Reproduce the Behavior
These steps show how to capture the fcgi requests using wireshark or tcpdump. Note that when using this method, the fcgi app will respond because it uses libfcgi which seems to start processing when the empty FCGI_PARAMS record is received. However, it does demonstrate that the FCGI_STDIN is not sent.
run haproxy with the given configuration or similar, with a proper SSL certificate chain file configured as required for HTTP/2.
prepare a fastcgi app (optional):
on Debian, as root:
apt-get install libfcgi-dev spawn-fcgi
Then create fcgiapp.c:
and compile with
gcc fcgiapp.c -lfcgi -o fcgiapp
run the fastcgi app:
spawn-fcgi -n -p 9000 ./fcgiapp
alternatively, because we're only capturing packets, you could skip step 2 and simply run netcat:nc -l -p 9000 > /dev/null
run wireshark or tcpdump, for example
tcpdump -i lo port 9000 -X --print -w /tmp/dump.pcap
make an empty POST request over HTTP/2:
curl -v -k -d "" --http2 https://127.0.0.1/
Observe the missing FCGI_STDIN in the capture.
Do you have any idea what may have caused this?
No response
Do you have an idea how to solve the issue?
I don't have a solution but I do have a pointer in the right direction.
I added the following to the haproxy.conf:
then did two requests:
curl -v -k -d "" --http1.1 https://127.0.0.1/
andcurl -v -k -d "" --http2 https://127.0.0.1/
(part of these logs is attached) haproxy-bug-report-http1.txt haproxy-bug-report-http2.txtand compared the logs. The following section is present in the http1.1 log but not in the http2 log:
which tells me that this line of code: https://github.com/haproxy/haproxy/blob/v2.9.0/src/mux_fcgi.c#L4023 is not executed. Both logs show this line right before the above snippet:
so
ret
must be != 0 (because this line in fcgi_strm_send_empty_params is executed) and thegoto
on line 4020 is not executed. That can only mean that theif (htx_is_unique_blk(htx, blk) && (htx->flags & HTX_FL_EOM))
on line 4022 evaluated to false.What is your configuration?
Output of
haproxy -vv
Last Outputs and Backtraces
No response
Additional Information
Debian Bookworm, using the apt repository http://haproxy.debian.net bookworm-backports-2.9 main from the wizard at https://haproxy.debian.net/ .
I encountered this problem while working on my own fastcgi server implementation which depends on the empty FCGI_STDIN record being sent for each request to initiate processing the request. There are of course workarounds possible, such as checking the content length request header and start processing upon receiving the empty FCGI_PARAMS record. However, this complicates things. The fact that the record is supposed to be sent for every request according to the FCGI specification, that it is sent for GET requests which have no body payload and that it is sent when using HTTP/1.1 led me to file this bug report.