Hi tech@ reader,
I am running OpenBSD 5.8 on amd64 and I have encountered a non-deterministic
and incorrect behavior of httpd and slowcgi. I traced the problem to be that
httpd is assuming that only first FastCGI FCGI_STDOUT record will contain
headers and all subsequent records contain body (regardless of occurence of
two newline characters that should separate header and body section), while
for slowcgi it is not true.
This is intentional in httpd. See server_fcgi.c, line 549:
-----------------------------------------------------------------------
case FCGI_STDOUT:
if (++clt->clt_chunk == 1) {
if (server_fcgi_header(clt,
server_fcgi_getheaders(clt))
== -1) {
server_abort_http(clt, 500,
"malformed fcgi headers");
return;
}
-----------------------------------------------------------------------
Are there any reasons for such assumption?
Instructions for replication are below.
My server config is:
-----------------------------------------------------------------------
ext_addr="*"
chroot "/var/www"
logdir "/var/www/logs"
prefork 3
types {
include "/usr/share/misc/mime.types"
}
[... other unrelated server blocks ...]
server "test.domain" {
listen on $ext_addr port 80
fastcgi socket "/run/slowcgi.sock"
root "/cgi-bin/test.cgi"
log access "access-test.log"
log error "error-test.log"
}
-----------------------------------------------------------------------
/var/www/cgi-bin/test.cgi:
-----------------------------------------------------------------------
#!/bin/sh
echo Header1: value
echo Header2: value
echo
echo Body
-----------------------------------------------------------------------
Of course /var/www/bin/sh is present, too.
Request is always the same:
-----------------------------------------------------------------------
GET / HTTP/1.1
Host: test.domain:80
-----------------------------------------------------------------------
But it generates different results. Most of the time results are
incorrect, with Header2 placed in the Body section but in separate
http chunks:
-----------------------------------------------------------------------
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sat, 24 Oct 2015 19:08:48 GMT
Header1: value
Server: OpenBSD httpd
Transfer-Encoding: chunked
f
Header2: value
6
Body
0
-----------------------------------------------------------------------
Sometimes they are in one chunk:
-----------------------------------------------------------------------
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sat, 24 Oct 2015 19:08:49 GMT
Header1: value
Server: OpenBSD httpd
Transfer-Encoding: chunked
15
Header2: value
Body
0
-----------------------------------------------------------------------
And sometimes (rarely) the result is correct:
-----------------------------------------------------------------------
HTTP/1.1 200 OK
Connection: keep-alive
Date: Sat, 24 Oct 2015 19:09:28 GMT
Header1: value
Header2: value
Server: OpenBSD httpd
Transfer-Encoding: chunked
6
Body
0
-----------------------------------------------------------------------
This of course is wrong and prevents a real-life server (cgit) from
working, as sometimes headers generated by cgit.cgi go into the body
part of http response (that's why I started digging).
Sniffing on the slowcgi socket with socat:
mv fastcgi.sock fastcgi-orig.sock
socat -t100 -x -v UNIX-LISTEN:fastcgi.sock,mode=777,reuseaddr,fork \
UNIX-CONNECT:fastcgi-orig.sock
revealed that sometimes slowcgi sends:
- FCGI_STDOUT record with the first header
- FCGI_STDOUT record with the second header and body
- FCGI_STDOUT with empty data
- FCGI_STDERR with empty data
- FCGI_END_REQUEST
-----------------------------------------------------------------------
< 2015/10/24 21:19:47.072936 length=23 from=0 to=22
01 06 00 01 00 0f 00 00 48 65 61 64 65 72 31 3a ........Header1:
20 76 61 6c 75 65 0a value.
--
< 2015/10/24 21:19:47.073572 length=29 from=23 to=51
01 06 00 01 00 15 00 00 48 65 61 64 65 72 32 3a ........Header2:
20 76 61 6c 75 65 0a value.
0a .
42 6f 64 79 0a Body.
--
< 2015/10/24 21:19:47.075855 length=32 from=52 to=83
01 06 00 01 00 00 00 00 01 07 00 01 00 00 00 00 ................
01 03 00 01 00 08 00 00 00 00 00 00 00 00 00 00 ................
-----------------------------------------------------------------------
In the case that is handled correctly by the httpd, it sends:
- FCGI_STDOUT record with both headers and body
- FCGI_STDERR with empty data
- FCGI_STDOUT with empty data
- FCGI_END_REQUEST
-----------------------------------------------------------------------
< 2015/10/24 21:21:05.291454 length=44 from=0 to=43
01 06 00 01 00 24 00 00 48 65 61 64 65 72 31 3a .....$..Header1:
20 76 61 6c 75 65 0a value.
48 65 61 64 65 72 32 3a 20 76 61 6c 75 65 0a Header2: value.
0a .
42 6f 64 79 0a Body.
--
< 2015/10/24 21:21:05.292280 length=32 from=44 to=75
01 07 00 01 00 00 00 00 01 06 00 01 00 00 00 00 ................
01 03 00 01 00 08 00 00 00 00 00 00 00 00 00 00 ................
-----------------------------------------------------------------------
I believe that slowcgi behavior, although being based on a race
condition (reading as much data as CGI script will generate and
pushing it in FCGI_STDOUT record) is correct in both cases and httpd
makes a false assumption, mentioned above, that all headers will be
sent in one FCGI_STDOUT record.
Maybe it should be changed?
Kind regards.
--
"qui hic minxerit aut cacaverit, habeat deos superos et inferos iratos"
http://www.chmurka.net/