(Cc: semarie@ added)

On Wed, Jul 22, 2015 at 10:04:30PM -0400, Jean-Philippe Ouellet wrote:
> If you have a CGI script that produces multiple headers in separate writes,
> then they may be delivered to httpd as separate FastCGI records, depending on
> a race between the CGI script writing more headers and slowcgi framing them
> and passing them on to httpd.
> 
> The fcgi code erroneously believes all headers will appear in the first chunk.
> 
> From server_fcgi.c v1.58:
>     545                         case FCGI_STDOUT:
>     546                                 if (++clt->clt_chunk == 1) {
>     547                                         if (server_fcgi_header(clt,
> 
> The The net effect is subsequent headers being leaked into the body of the
> http reply.

Thanks, nice find.
The intention was to only get the Status header from fcgi, there
is even a comment alluding to that:
        /* Write initial header (fcgi might append more) */
Don't know if that ever worked or if it broke once we started messing
with the headers ourself.

I first tried to restore the original intention but that won't work
with chunked encoding - we would chunk encode part of the headers.

This is the big hammer solution: read all http headers comming from fcgi.

OK?

Index: httpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
retrieving revision 1.92
diff -u -p -r1.92 httpd.h
--- httpd.h     19 Jul 2015 05:17:27 -0000      1.92
+++ httpd.h     25 Jul 2015 21:00:52 -0000
@@ -316,6 +316,12 @@ struct client {
        int                      clt_fcgi_type;
        int                      clt_fcgi_chunked;
        int                      clt_fcgi_end;
+       struct evbuffer         *clt_fcgi_http_header_evb;
+       int                      clt_fcgi_http_header_state;
+#define FCGI_HTTP_HEADER_ERROR         -1
+#define FCGI_HTTP_HEADER_UNREAD                 0
+#define FCGI_HTTP_HEADER_READ           1
+#define FCGI_HTTP_HEADER_DONE           2
        char                    *clt_remote_user;
        struct evbuffer         *clt_srvevb;
 
Index: server.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server.c,v
retrieving revision 1.71
diff -u -p -r1.71 server.c
--- server.c    18 Jul 2015 22:19:50 -0000      1.71
+++ server.c    25 Jul 2015 21:00:52 -0000
@@ -1093,6 +1093,8 @@ server_close(struct client *clt, const c
                evbuffer_free(clt->clt_output);
        if (clt->clt_srvevb != NULL)
                evbuffer_free(clt->clt_srvevb);
+       if (clt->clt_fcgi_http_header_evb != NULL)
+               evbuffer_free(clt->clt_fcgi_http_header_evb);
 
        if (clt->clt_srvbev != NULL)
                bufferevent_free(clt->clt_srvbev);
Index: server_fcgi.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v
retrieving revision 1.58
diff -u -p -r1.58 server_fcgi.c
--- server_fcgi.c       19 Jul 2015 16:34:35 -0000      1.58
+++ server_fcgi.c       25 Jul 2015 21:00:52 -0000
@@ -76,6 +76,7 @@ struct server_fcgi_param {
        uint8_t         buf[FCGI_RECORD_SIZE];
 };
 
+int    server_fcgi_read_header(struct client *);
 int    server_fcgi_header(struct client *, u_int);
 void   server_fcgi_read(struct bufferevent *, void *);
 int    server_fcgi_writeheader(struct client *, struct kv *, void *);
@@ -154,6 +155,15 @@ server_fcgi(struct httpd *env, struct cl
                goto fail;
        }
 
+       if (clt->clt_fcgi_http_header_evb != NULL)
+               evbuffer_free(clt->clt_fcgi_http_header_evb);
+
+       clt->clt_fcgi_http_header_evb = evbuffer_new();
+       if (clt->clt_fcgi_http_header_evb == NULL) {
+               errstr = "failed to allocate evbuffer";
+               goto fail;
+       }
+
        close(clt->clt_fd);
        clt->clt_fd = fd;
 
@@ -543,7 +553,22 @@ server_fcgi_read(struct bufferevent *bev
                                }
                                break;
                        case FCGI_STDOUT:
-                               if (++clt->clt_chunk == 1) {
+                               clt->clt_chunk++;
+                               if (clt->clt_fcgi_http_header_state ==
+                                   FCGI_HTTP_HEADER_UNREAD) {
+                                       clt->clt_fcgi_http_header_state =
+                                           server_fcgi_read_header(clt);
+                                       if (clt->clt_fcgi_http_header_state ==
+                                           FCGI_HTTP_HEADER_ERROR) {
+                                               server_abort_http(clt,
+                                                   500, "out of memory");
+                                               return;
+                                       }
+                               }
+                               if (clt->clt_fcgi_http_header_state == 
+                                   FCGI_HTTP_HEADER_READ) {
+                                       clt->clt_fcgi_http_header_state = 
+                                           FCGI_HTTP_HEADER_DONE;
                                        if (server_fcgi_header(clt,
                                            server_fcgi_getheaders(clt))
                                            == -1) {
@@ -553,7 +578,8 @@ server_fcgi_read(struct bufferevent *bev
                                        }
                                        if (!EVBUFFER_LENGTH(clt->clt_srvevb))
                                                break;
-                               }
+                               } else
+                                       break;
                                /* FALLTHROUGH */
                        case FCGI_END_REQUEST:
                                if (server_fcgi_writechunk(clt) == -1) {
@@ -587,6 +613,35 @@ server_fcgi_read(struct bufferevent *bev
 }
 
 int
+server_fcgi_read_header(struct client *clt)
+{
+       size_t   len;
+       int      ret = FCGI_HTTP_HEADER_UNREAD;
+       u_char  *ptr;
+       
+       if ((ptr = evbuffer_find(clt->clt_srvevb, "\r\n\r\n", 4)) !=  NULL) {
+               len = ptr - EVBUFFER_DATA(clt->clt_srvevb) + 4;
+               if (evbuffer_add(clt->clt_fcgi_http_header_evb,
+                   EVBUFFER_DATA(clt->clt_srvevb), len) == -1)
+                       ret = FCGI_HTTP_HEADER_ERROR;
+               ret = FCGI_HTTP_HEADER_READ;
+       } else if ((ptr = evbuffer_find(clt->clt_srvevb, "\n\n", 2)) !=  NULL) {
+               len = ptr - EVBUFFER_DATA(clt->clt_srvevb) + 2;
+               if (evbuffer_add(clt->clt_fcgi_http_header_evb,
+                   EVBUFFER_DATA(clt->clt_srvevb), len) == -1)
+                       ret = FCGI_HTTP_HEADER_ERROR;
+               ret = FCGI_HTTP_HEADER_READ;
+       } else {
+               len = EVBUFFER_LENGTH(clt->clt_srvevb);
+               if (evbuffer_add_buffer(clt->clt_fcgi_http_header_evb,
+                   clt->clt_srvevb) == -1)
+                       ret = FCGI_HTTP_HEADER_ERROR;
+       }
+       evbuffer_drain(clt->clt_srvevb, len);
+       return (ret);
+}
+
+int
 server_fcgi_header(struct client *clt, u_int code)
 {
        struct http_descriptor  *desc = clt->clt_descreq;
@@ -639,7 +694,7 @@ server_fcgi_header(struct client *clt, u
            kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
                return (-1);
 
-       /* Write initial header (fcgi might append more) */
+       /* Write header */
        if (server_writeresponse_http(clt) == -1 ||
            server_bufferevent_print(clt, "\r\n") == -1 ||
            server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
@@ -730,7 +785,7 @@ int
 server_fcgi_getheaders(struct client *clt)
 {
        struct http_descriptor  *resp = clt->clt_descresp;
-       struct evbuffer         *evb = clt->clt_srvevb;
+       struct evbuffer         *evb = clt->clt_fcgi_http_header_evb;
        int                      code = 200;
        char                    *line, *key, *value;
        const char              *errstr;
Index: server_http.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v
retrieving revision 1.93
diff -u -p -r1.93 server_http.c
--- server_http.c       23 Jul 2015 09:36:32 -0000      1.93
+++ server_http.c       25 Jul 2015 21:00:52 -0000
@@ -632,6 +632,7 @@ server_reset_http(struct client *clt)
        clt->clt_line = 0;
        clt->clt_done = 0;
        clt->clt_chunk = 0;
+       clt->clt_fcgi_http_header_state = FCGI_HTTP_HEADER_UNREAD;
        free(clt->clt_remote_user);
        clt->clt_remote_user = NULL;
        clt->clt_bev->readcb = server_read_http;


-- 
I'm not entirely sure you are real.

Reply via email to