Dear Maintainer,
I have a CGI script which serves up some larger (60+MB seems to be the
sweet spot) files via Apache through a squid caching reverse proxy. We've
recently been seeing those files being truncated (even when the content
doesn't change).
My hypothesis is the patch related to CVE-2023-46846 has either
broken chunked transfer encoding, or Apache is generating "bad" chunked
transfers which previous versions of squid were lax about handling.
I've reliably replicated the problem in a bullseye VM using fairly
minimal configurations:
/usr/lib/cgi-bin/bigfile.cgi:
#!/bin/sh
BS=16384
BC=4000
CL=`expr ${BS} \* ${BC}`
TF=/tmp/bigfile.out
if ! -f ${TF}
then
/usr/bin/dd bs=${BS} if=/dev/urandom of=${TF} count=${BC} 2>/dev/null
fi
/usr/bin/echo -e -n "Status: 200 OK\r\n"
/usr/bin/echo -e -n "Content-type: application/octet-stream\r\n"
/usr/bin/echo -e -n "Content-length: ${CL}\r\n"
/usr/bin/echo -e -n "Date: " `date -R -u` "\r\n"
/usr/bin/echo -e -n "\r\n"
exec /usr/bin/cat ${TF}
/etc/apache2/sites-enabled/test.conf:
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Require all granted
</Directory>
</VirtualHost>
cd /etc/apache2/mods-enabled/ && ln -s ../mods-available/cgi.load
/etc/squid/squid.conf:
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log squid
cache_store_log none
icp_port 3130
coredump_dir /var/spool/squid
cache_mem 768 MB
cache_dir ufs /var/spool/squid 1024 16 256
memory_pools on
request_header_access X-Forwarded-Proto allow all
request_header_access Other deny all
cache_peer http-test parent 80 0 no-query originserver name=localaccel
acl our_sites dstdomain http-test
http_access allow our_sites
cache_peer_access localaccel allow our_sites
http_port 8080 accel vhost
restart apache2 and squid.
A normal request directly to Apache looks like:
test@http-test:~$ wget -Ox -S http://http-test:80/cgi-bin/bigfile.cgi
--2024-07-25 12:04:31-- http://http-test/cgi-bin/bigfile.cgi
Resolving http-test (http-test)... 127.0.1.1
Connecting to http-test (http-test)|127.0.1.1|:80... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Date: Thu, 25 Jul 2024 16:04:31 GMT
Server: Apache/2.4.61 (Debian)
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/octet-stream
Length: unspecified [application/octet-stream]
Saving to: ‘x’
x [ <=> ] 62.50M 220MB/s in 0.3s
2024-07-25 12:04:32 (220 MB/s) - ‘x’ saved [65536000]
Same request through the reverse proxy:
test@http-test:~$ wget -Ox -S http://http-test:8080/cgi-bin/bigfile.cgi
--2024-07-25 12:05:01-- http://http-test:8080/cgi-bin/bigfile.cgi
Resolving http-test (http-test)... 127.0.1.1
Connecting to http-test (http-test)|127.0.1.1|:8080... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Date: Thu, 25 Jul 2024 16:05:01 GMT
Server: Apache/2.4.61 (Debian)
Content-Type: application/octet-stream
X-Cache: MISS from http-test
X-Cache-Lookup: MISS from http-test:8080
Transfer-Encoding: chunked
Via: 1.1 http-test (squid/4.13)
Connection: keep-alive
Length: unspecified [application/octet-stream]
Saving to: ‘x’
x [ <=> ] 62.50M 98.0MB/s in 0.6s
2024-07-25 12:05:02 (98.0 MB/s) - ‘x’ saved [65536000]
But sometimes, the request gets silently truncated:
test@http-test:~$ wget -Ox -S http://http-test:8080/cgi-bin/bigfile.cgi
--2024-07-25 12:05:52-- http://http-test:8080/cgi-bin/bigfile.cgi
Resolving http-test (http-test)... 127.0.1.1
Connecting to http-test (http-test)|127.0.1.1|:8080... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Date: Thu, 25 Jul 2024 16:05:52 GMT
Server: Apache/2.4.61 (Debian)
Content-Type: application/octet-stream
X-Cache: MISS from http-test
X-Cache-Lookup: MISS from http-test:8080
Transfer-Encoding: chunked
Via: 1.1 http-test (squid/4.13)
Connection: keep-alive
Length: unspecified [application/octet-stream]
Saving to: ‘x’
x [ <=> ] 10.33M --.-KB/s in 0.1s
2024-07-25 12:05:54 (89.1 MB/s) - ‘x’ saved [10835159]
The rate of truncated requests is wildly variable. In my test VM it
might be 20% of the time, on our operational systems it's more like 80%.
When the request truncation happens, Apache sees it as a client disconnect
(add a %X to the Apache LogFormat). More interestingly, if you add a:
debug_options ALL,1 05,3 11,3
to squid.conf, the truncated downloads will start to spit out:
2024/07/25 12:10:48.122 kid1| Exception error:garbage instead of CRLF line terminator
exception location: Parser.cc(73) skipLineTerminator
at which point they close down the connection.
To make matters worse, if your CGI script has decent cache control,
squid appears to be treating these truncated files as "good", and will
serve them up as a cache hit.
The problem can be mitigated by adding:
SetEnv ap_trust_cgilike_cl 1
to the Apache cgi-bin Directory section, assuming the CGI script in
question generated a Content-length header. This prevents Apache from treating
it as chunked, and the resulting request is:
test@http-test:~$ wget -Ox -S http://http-test:8080/cgi-bin/bigfile.cgi
--2024-07-25 12:18:13-- http://http-test:8080/cgi-bin/bigfile.cgi
Resolving http-test (http-test)... 127.0.1.1
Connecting to http-test (http-test)|127.0.1.1|:8080... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Date: Thu, 25 Jul 2024 16:18:13 GMT
Server: Apache/2.4.61 (Debian)
Content-Length: 65536000
Content-Type: application/octet-stream
X-Cache: MISS from http-test
X-Cache-Lookup: MISS from http-test:8080
Via: 1.1 http-test (squid/4.13)
Connection: keep-alive
Length: 65536000 (62M) [application/octet-stream]
Saving to: ‘x’
x 100%[===================>] 62.50M 29.9MB/s in 2.1s
2024-07-25 12:18:15 (29.9 MB/s) - ‘x’ saved [65536000/65536000]