(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.