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/

Reply via email to