[BUG] http: fix content-length handling on 32-bit platforms
Despite much care around handling the content-length as a 64-bit integer,
forwarding was broken on 32-bit platforms due to the 32-bit nature of
the ->to_forward member of the "buffer" struct. The issue is that this
member is declared as a long, so while it works OK on 64-bit platforms,
32-bit truncate the content-length to the lower 32-bits.
One solution could consist in turning to_forward to a long long, but it
is used a lot in the critical path, so it's not acceptable to perform
all buffer size computations on 64-bit there.
The fix consists in changing the to_forward member to a strict 32-bit
integer and ensure in buffer_forward() that only the amount of bytes
that can fit into it is considered. Callers of buffer_forward() are
responsible for checking that their data were taken into account. We
arbitrarily ensure we never consider more than 2G at once.
That's the way it was intended to work on 32-bit platforms except that
it did not.
This issue was tracked down hard at Exosec with Bertrand Jacquin,
Thierry Fournier and Julien Thomas. It remained undetected for a long
time because files larger than 4G are almost always transferred in
chunked-encoded format, and most platforms dealing with huge contents
these days run on 64-bit.
The bug affects all 1.5 and 1.4 versions, and must be backported.
diff --git a/src/proto_http.c b/src/proto_http.c
index 057cd4f..5e3975e 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -4410,15 +4410,17 @@
}
while (1) {
+ int bytes;
+
http_silent_debug(__LINE__, s);
/* we may have some data pending */
- if (msg->chunk_len || msg->som != msg->sov) {
- int bytes = msg->sov - msg->som;
- if (bytes < 0) /* sov may have wrapped at the end */
- bytes += req->size;
- buffer_forward(req, bytes + msg->chunk_len);
- msg->chunk_len = 0; /* don't forward that again */
+ bytes = msg->sov - msg->som;
+ if (msg->chunk_len || bytes) {
msg->som = msg->sov;
+ if (likely(bytes < 0)) /* sov may have wrapped at the end */
+ bytes += req->size;
+ msg->chunk_len += (unsigned int)bytes;
+ msg->chunk_len -= buffer_forward(req, msg->chunk_len);
}
if (msg->msg_state == HTTP_MSG_DATA) {
@@ -5421,6 +5423,7 @@
{
struct http_txn *txn = &s->txn;
struct http_msg *msg = &s->txn.rsp;
+ int bytes;
if (unlikely(msg->msg_state < HTTP_MSG_BODY))
return 0;
@@ -5454,17 +5457,20 @@
}
while (1) {
+ int bytes;
+
http_silent_debug(__LINE__, s);
/* we may have some data pending */
- if (msg->chunk_len || msg->som != msg->sov) {
- int bytes = msg->sov - msg->som;
- if (bytes < 0) /* sov may have wrapped at the end */
- bytes += res->size;
- buffer_forward(res, bytes + msg->chunk_len);
- msg->chunk_len = 0; /* don't forward that again */
+ bytes = msg->sov - msg->som;
+ if (msg->chunk_len || bytes) {
msg->som = msg->sov;
+ if (likely(bytes < 0)) /* sov may have wrapped at the end */
+ bytes += res->size;
+ msg->chunk_len += (unsigned int)bytes;
+ msg->chunk_len -= buffer_forward(res, msg->chunk_len);
}
+
if (msg->msg_state == HTTP_MSG_DATA) {
/* must still forward */
if (res->to_forward)
@@ -5573,14 +5579,14 @@
if (!s->req->analysers)
goto return_bad_res;
- /* forward the chunk size as well as any pending data */
- if (msg->chunk_len || msg->som != msg->sov) {
- int bytes = msg->sov - msg->som;
- if (bytes < 0) /* sov may have wrapped at the end */
- bytes += res->size;
- buffer_forward(res, bytes + msg->chunk_len);
- msg->chunk_len = 0; /* don't forward that again */
+ /* forward any pending data */
+ bytes = msg->sov - msg->som;
+ if (msg->chunk_len || bytes) {
msg->som = msg->sov;
+ if (likely(bytes < 0)) /* sov may have wrapped at the end */
+ bytes += res->size;
+ msg->chunk_len += (unsigned int)bytes;
+ msg->chunk_len -= buffer_forward(res, msg->chunk_len);
}
/* When TE: chunked is used, we need to get there again to parse remaining