Blame SOURCES/haproxy-dont-update-msg-sov.patch

d58b22
From b4d05093bc89f71377230228007e69a1434c1a0c Mon Sep 17 00:00:00 2001
d58b22
From: Willy Tarreau <w@1wt.eu>
d58b22
Date: Mon, 1 Sep 2014 20:35:55 +0200
d58b22
Subject: [PATCH] BUG/CRITICAL: http: don't update msg->sov once data start to
d58b22
 leave the buffer
d58b22
d58b22
Commit bb2e669 ("BUG/MAJOR: http: correctly rewind the request body
d58b22
after start of forwarding") was incorrect/incomplete. It used to rely on
d58b22
CF_READ_ATTACHED to stop updating msg->sov once data start to leave the
d58b22
buffer, but this is unreliable because since commit a6eebb3 ("[BUG]
d58b22
session: clear BF_READ_ATTACHED before next I/O") merged in 1.5-dev1,
d58b22
this flag is only ephemeral and is cleared once all analysers have
d58b22
seen it. So we can start updating msg->sov again each time we pass
d58b22
through this place with new data. With a sufficiently large amount of
d58b22
data, it is possible to make msg->sov wrap and validate the if()
d58b22
condition at the top, causing the buffer to advance by about 2GB and
d58b22
crash the process.
d58b22
d58b22
Note that the offset cannot be controlled by the attacker because it is
d58b22
a sum of millions of small random sizes depending on how many bytes were
d58b22
read by the server and how many were left in the buffer, only because
d58b22
of the speed difference between reading and writing. Also, nothing is
d58b22
written, the invalid pointer resulting from this operation is only read.
d58b22
d58b22
Many thanks to James Dempsey for reporting this bug and to Chris Forbes for
d58b22
narrowing down the faulty area enough to make its root cause analysable.
d58b22
d58b22
This fix must be backported to haproxy 1.5.
d58b22
(cherry picked from commit a2d002aeb669a9bbca2dcd3e6be71615f435e9e6)
d58b22
---
d58b22
 include/types/channel.h | 2 +-
d58b22
 src/proto_http.c        | 8 ++++----
d58b22
 src/stream_interface.c  | 4 ++--
d58b22
 3 files changed, 7 insertions(+), 7 deletions(-)
d58b22
d58b22
diff --git a/include/types/channel.h b/include/types/channel.h
d58b22
index 88a52a4..8bc3958 100644
d58b22
--- a/include/types/channel.h
d58b22
+++ b/include/types/channel.h
d58b22
@@ -105,7 +105,7 @@
d58b22
 #define CF_STREAMER       0x00010000  /* the producer is identified as streaming data */
d58b22
 #define CF_STREAMER_FAST  0x00020000  /* the consumer seems to eat the stream very fast */
d58b22
 
d58b22
-/* unused: 0x00040000 */
d58b22
+#define CF_WROTE_DATA     0x00040000  /* some data were sent from this buffer */
d58b22
 #define CF_ANA_TIMEOUT    0x00080000  /* the analyser timeout has expired */
d58b22
 #define CF_READ_ATTACHED  0x00100000  /* the read side is attached for the first time */
d58b22
 #define CF_KERN_SPLICING  0x00200000  /* kernel splicing desired for this channel */
d58b22
diff --git a/src/proto_http.c b/src/proto_http.c
d58b22
index a47f0a1..4d27b2c 100644
d58b22
--- a/src/proto_http.c
d58b22
+++ b/src/proto_http.c
d58b22
@@ -4886,8 +4886,8 @@ void http_end_txn_clean_session(struct session *s)
d58b22
 	s->req->cons->conn_retries = 0;  /* used for logging too */
d58b22
 	s->req->cons->exp       = TICK_ETERNITY;
d58b22
 	s->req->cons->flags    &= SI_FL_DONT_WAKE; /* we're in the context of process_session */
d58b22
-	s->req->flags &= ~(CF_SHUTW|CF_SHUTW_NOW|CF_AUTO_CONNECT|CF_WRITE_ERROR|CF_STREAMER|CF_STREAMER_FAST|CF_NEVER_WAIT|CF_WAKE_CONNECT);
d58b22
-	s->rep->flags &= ~(CF_SHUTR|CF_SHUTR_NOW|CF_READ_ATTACHED|CF_READ_ERROR|CF_READ_NOEXP|CF_STREAMER|CF_STREAMER_FAST|CF_WRITE_PARTIAL|CF_NEVER_WAIT);
d58b22
+	s->req->flags &= ~(CF_SHUTW|CF_SHUTW_NOW|CF_AUTO_CONNECT|CF_WRITE_ERROR|CF_STREAMER|CF_STREAMER_FAST|CF_NEVER_WAIT|CF_WAKE_CONNECT|CF_WROTE_DATA);
d58b22
+	s->rep->flags &= ~(CF_SHUTR|CF_SHUTR_NOW|CF_READ_ATTACHED|CF_READ_ERROR|CF_READ_NOEXP|CF_STREAMER|CF_STREAMER_FAST|CF_WRITE_PARTIAL|CF_NEVER_WAIT|CF_WROTE_DATA);
d58b22
 	s->flags &= ~(SN_DIRECT|SN_ASSIGNED|SN_ADDR_SET|SN_BE_ASSIGNED|SN_FORCE_PRST|SN_IGNORE_PRST);
d58b22
 	s->flags &= ~(SN_CURR_SESS|SN_REDIRECTABLE|SN_SRV_REUSED);
d58b22
 
d58b22
@@ -5430,7 +5430,7 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
d58b22
 			 * such as last chunk of data or trailers.
d58b22
 			 */
d58b22
 			b_adv(req->buf, msg->next);
d58b22
-			if (unlikely(!(s->rep->flags & CF_READ_ATTACHED)))
d58b22
+			if (unlikely(!(s->req->flags & CF_WROTE_DATA)))
d58b22
 				msg->sov -= msg->next;
d58b22
 			msg->next = 0;
d58b22
 
d58b22
@@ -5482,7 +5482,7 @@ int http_request_forward_body(struct session *s, struct channel *req, int an_bit
d58b22
  missing_data:
d58b22
 	/* we may have some pending data starting at req->buf->p */
d58b22
 	b_adv(req->buf, msg->next);
d58b22
-	if (unlikely(!(s->rep->flags & CF_READ_ATTACHED)))
d58b22
+	if (unlikely(!(s->req->flags & CF_WROTE_DATA)))
d58b22
 		msg->sov -= msg->next + MIN(msg->chunk_len, req->buf->i);
d58b22
 
d58b22
 	msg->next = 0;
d58b22
diff --git a/src/stream_interface.c b/src/stream_interface.c
d58b22
index 67a5234..9f7e979 100644
d58b22
--- a/src/stream_interface.c
d58b22
+++ b/src/stream_interface.c
d58b22
@@ -658,7 +658,7 @@ static void si_conn_send(struct connection *conn)
d58b22
 	if (chn->pipe && conn->xprt->snd_pipe) {
d58b22
 		ret = conn->xprt->snd_pipe(conn, chn->pipe);
d58b22
 		if (ret > 0)
d58b22
-			chn->flags |= CF_WRITE_PARTIAL;
d58b22
+			chn->flags |= CF_WRITE_PARTIAL | CF_WROTE_DATA;
d58b22
 
d58b22
 		if (!chn->pipe->data) {
d58b22
 			put_pipe(chn->pipe);
d58b22
@@ -702,7 +702,7 @@ static void si_conn_send(struct connection *conn)
d58b22
 
d58b22
 		ret = conn->xprt->snd_buf(conn, chn->buf, send_flag);
d58b22
 		if (ret > 0) {
d58b22
-			chn->flags |= CF_WRITE_PARTIAL;
d58b22
+			chn->flags |= CF_WRITE_PARTIAL | CF_WROTE_DATA;
d58b22
 
d58b22
 			if (!chn->buf->o) {
d58b22
 				/* Always clear both flags once everything has been sent, they're one-shot */
d58b22
-- 
d58b22
1.9.3
d58b22