#1077064 reportbug: squid truncating large files with chunked transfer encoding

Package:
squid
Source:
squid
Description:
Full featured Web Proxy cache (HTTP proxy GnuTLS flavour)
Submitter:
Christophe Beauregard
Date:
2024-07-25 17:30:04 UTC
Severity:
normal
#1077064#5
Date:
2024-07-25 17:26:30 UTC
From:
To:
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]