[BUG] stream_sock: BUF_INFINITE_FORWARD broke splice on 64-bit platforms
Yohan Tordjman at Dstorage found that upgrading haproxy to 1.4-dev4
caused truncated objects to be returned. An strace quickly exhibited
the issue which was 100% reproducible :
4297 epoll_wait(0, {}, 10, 0) = 0
4297 epoll_wait(0, {{EPOLLIN, {u32=7, u64=7}}}, 10, 1000) = 1
4297 splice(0x7, 0, 0x5, 0, 0xffffffffffffffff, 0x3) = -1 EINVAL (Invalid argument)
4297 shutdown(7, 1 /* send */) = 0
4297 close(7) = 0
4297 shutdown(2, 1 /* send */) = 0
4297 close(2) = 0
This is caused by the fact that the forward length is taken from
BUF_INFINITE_FORWARD, which is -1. The problem does not appear
in 32-bit mode because this value is first cast to an unsigned
long, truncating it to 32-bit (4 GB). Setting an upper bound
fixes the issue.
Also, a second error check has been added for splice. If EINVAL
is returned, we fall back to recv().
diff --git a/src/stream_sock.c b/src/stream_sock.c
index 479222a..c1f0dfa 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -84,6 +84,10 @@
*/
#define SPLICE_FULL_HINT 16*1448
+/* how many data we attempt to splice at once when the buffer is configured for
+ * infinite forwarding */
+#define MAX_SPLICE_AT_ONCE (1<<30)
+
/* Returns :
* -1 if splice is not possible or not possible anymore and we must switch to
* user-land copy (eg: to_forward reached)
@@ -138,7 +142,11 @@
/* At this point, b->pipe is valid */
while (1) {
- max = b->to_forward;
+ if (b->to_forward == BUF_INFINITE_FORWARD)
+ max = MAX_SPLICE_AT_ONCE;
+ else
+ max = b->to_forward;
+
if (!max) {
/* It looks like the buffer + the pipe already contain
* the maximum amount of data to be transferred. Try to
@@ -188,7 +196,7 @@
break;
}
- if (errno == ENOSYS) {
+ if (errno == ENOSYS || errno == EINVAL) {
/* splice not supported on this end, disable it */
b->flags &= ~BF_KERN_SPLICING;
si->flags &= ~SI_FL_CAP_SPLICE;