Package: docker.io / 1.11.2~ds1-6

22000--ignore-invalid-host-header.patch Patch series | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
Origin: https://github.com/docker/docker/pull/22000, https://github.com/docker/docker/pull/22888, https://github.com/docker/docker/pull/23046
Author: Antonio Murdaca <runcom@redhat.com>, Darren Shepherd <darren@rancher.com>, Shijiang Wei <mountkin@gmail.com>, Tianon Gravi <tianon@debian.org> (rebased for 1.11)
Subject: Ignore invalid "Host:" header between go1.6 and old docker clients

diff --git a/docker/daemon.go b/docker/daemon.go
index bee921c..7cd8adc 100644
--- a/docker/daemon.go
+++ b/docker/daemon.go
@@ -250,13 +250,14 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
 		if len(protoAddrParts) != 2 {
 			logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
 		}
-		l, err := listeners.Init(protoAddrParts[0], protoAddrParts[1], serverConfig.SocketGroup, serverConfig.TLSConfig)
+		ls, err := listeners.Init(protoAddrParts[0], protoAddrParts[1], serverConfig.SocketGroup, serverConfig.TLSConfig)
 		if err != nil {
 			logrus.Fatal(err)
 		}
+		ls = wrapListeners(protoAddrParts[0], ls)
 
 		logrus.Debugf("Listener created for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
-		api.Accept(protoAddrParts[1], l...)
+		api.Accept(protoAddrParts[1], ls...)
 	}
 
 	if err := migrateKey(); err != nil {
diff --git a/docker/daemon_unix.go b/docker/daemon_unix.go
index b65eb1f..4e4b498 100644
--- a/docker/daemon_unix.go
+++ b/docker/daemon_unix.go
@@ -4,10 +4,13 @@ package main
 
 import (
 	"fmt"
+	"net"
 	"os"
 	"os/signal"
 	"syscall"
 
+	"github.com/docker/docker/docker/hack"
+
 	"github.com/Sirupsen/logrus"
 	apiserver "github.com/docker/docker/api/server"
 	"github.com/docker/docker/daemon"
@@ -80,3 +83,15 @@ func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
 	}
 	return opts
 }
+
+func wrapListeners(proto string, ls []net.Listener) []net.Listener {
+	switch proto {
+	case "unix":
+		ls[0] = &hack.MalformedHostHeaderOverride{ls[0]}
+	case "fd":
+		for i := range ls {
+			ls[i] = &hack.MalformedHostHeaderOverride{ls[i]}
+		}
+	}
+	return ls
+}
diff --git a/docker/daemon_windows.go b/docker/daemon_windows.go
index ae8d737..b5ffbf9 100644
--- a/docker/daemon_windows.go
+++ b/docker/daemon_windows.go
@@ -4,6 +4,7 @@ package main
 
 import (
 	"fmt"
+	"net"
 	"os"
 	"syscall"
 
@@ -62,3 +63,7 @@ func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(
 func (cli *DaemonCli) getPlatformRemoteOptions() []libcontainerd.RemoteOption {
 	return nil
 }
+
+func wrapListeners(proto string, ls []net.Listener) []net.Listener {
+	return ls
+}
diff --git a/docker/hack/malformed_host_override.go b/docker/hack/malformed_host_override.go
new file mode 100644
index 0000000..d4aa3dd
--- /dev/null
+++ b/docker/hack/malformed_host_override.go
@@ -0,0 +1,121 @@
+// +build !windows
+
+package hack
+
+import "net"
+
+// MalformedHostHeaderOverride is a wrapper to be able
+// to overcome the 400 Bad request coming from old docker
+// clients that send an invalid Host header.
+type MalformedHostHeaderOverride struct {
+	net.Listener
+}
+
+// MalformedHostHeaderOverrideConn wraps the underlying unix
+// connection and keeps track of the first read from http.Server
+// which just reads the headers.
+type MalformedHostHeaderOverrideConn struct {
+	net.Conn
+	first bool
+}
+
+var closeConnHeader = []byte("\r\nConnection: close\r")
+
+// Read reads the first *read* request from http.Server to inspect
+// the Host header. If the Host starts with / then we're talking to
+// an old docker client which send an invalid Host header. To not
+// error out in http.Server we rewrite the first bytes of the request
+// to sanitize the Host header itself.
+// In case we're not dealing with old docker clients the data is just passed
+// to the server w/o modification.
+func (l *MalformedHostHeaderOverrideConn) Read(b []byte) (n int, err error) {
+	// http.Server uses a 4k buffer
+	if l.first && len(b) == 4096 {
+		// This keeps track of the first read from http.Server which just reads
+		// the headers
+		l.first = false
+		// The first read of the connection by http.Server is done limited to
+		// DefaultMaxHeaderBytes (usually 1 << 20) + 4096.
+		// Here we do the first read which gets us all the http headers to
+		// be inspected and modified below.
+		c, err := l.Conn.Read(b)
+		if err != nil {
+			return c, err
+		}
+
+		var (
+			start, end    int
+			firstLineFeed = -1
+			buf           []byte
+		)
+		for i := 0; i <= c-1-7; i++ {
+			if b[i] == '\n' && firstLineFeed == -1 {
+				firstLineFeed = i
+			}
+			if b[i] != '\n' {
+				continue
+			}
+
+			if b[i+1] == '\r' && b[i+2] == '\n' {
+				return c, nil
+			}
+
+			if b[i+1] != 'H' {
+				continue
+			}
+			if b[i+2] != 'o' {
+				continue
+			}
+			if b[i+3] != 's' {
+				continue
+			}
+			if b[i+4] != 't' {
+				continue
+			}
+			if b[i+5] != ':' {
+				continue
+			}
+			if b[i+6] != ' ' {
+				continue
+			}
+			if b[i+7] != '/' {
+				continue
+			}
+			// ensure clients other than the docker clients do not get this hack
+			if i != firstLineFeed {
+				return c, nil
+			}
+			start = i + 7
+			// now find where the value ends
+			for ii, bbb := range b[start:c] {
+				if bbb == '\n' {
+					end = start + ii
+					break
+				}
+			}
+			buf = make([]byte, 0, c+len(closeConnHeader)-(end-start))
+			// strip the value of the host header and
+			// inject `Connection: close` to ensure we don't reuse this connection
+			buf = append(buf, b[:start]...)
+			buf = append(buf, closeConnHeader...)
+			buf = append(buf, b[end:c]...)
+			copy(b, buf)
+			break
+		}
+		if len(buf) == 0 {
+			return c, nil
+		}
+		return len(buf), nil
+	}
+	return l.Conn.Read(b)
+}
+
+// Accept makes the listener accepts connections and wraps the connection
+// in a MalformedHostHeaderOverrideConn initilizing first to true.
+func (l *MalformedHostHeaderOverride) Accept() (net.Conn, error) {
+	c, err := l.Listener.Accept()
+	if err != nil {
+		return c, err
+	}
+	return &MalformedHostHeaderOverrideConn{c, true}, nil
+}
diff --git a/docker/hack/malformed_host_override_test.go b/docker/hack/malformed_host_override_test.go
new file mode 100644
index 0000000..1a0a60b
--- /dev/null
+++ b/docker/hack/malformed_host_override_test.go
@@ -0,0 +1,124 @@
+// +build !windows
+
+package hack
+
+import (
+	"bytes"
+	"io"
+	"net"
+	"strings"
+	"testing"
+)
+
+type bufConn struct {
+	net.Conn
+	buf *bytes.Buffer
+}
+
+func (bc *bufConn) Read(b []byte) (int, error) {
+	return bc.buf.Read(b)
+}
+
+func TestHeaderOverrideHack(t *testing.T) {
+	tests := [][2][]byte{
+		{
+			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
+			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\n"),
+		},
+		{
+			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\nFoo: Bar\r\n"),
+			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\nFoo: Bar\r\n"),
+		},
+		{
+			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something!"),
+			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something!"),
+		},
+		{
+			[]byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
+			[]byte("GET /foo\nHost: \r\nConnection: close\r\nUser-Agent: Docker\r\n\r\ntest something! " + strings.Repeat("test", 15000)),
+		},
+		{
+			[]byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
+			[]byte("GET /foo\nFoo: Bar\nHost: /var/run/docker.sock\nUser-Agent: Docker\r\n\r\n"),
+		},
+	}
+
+	// Test for https://github.com/docker/docker/issues/23045
+	h0 := "GET /foo\nUser-Agent: Docker\r\n\r\n"
+	h0 = h0 + strings.Repeat("a", 4096-len(h0)-1) + "\n"
+	tests = append(tests, [2][]byte{[]byte(h0), []byte(h0)})
+
+	for _, pair := range tests {
+		read := make([]byte, 4096)
+		client := &bufConn{
+			buf: bytes.NewBuffer(pair[0]),
+		}
+		l := MalformedHostHeaderOverrideConn{client, true}
+
+		n, err := l.Read(read)
+		if err != nil && err != io.EOF {
+			t.Fatalf("read: %d - %d, err: %v\n%s", n, len(pair[0]), err, string(read[:n]))
+		}
+		if !bytes.Equal(read[:n], pair[1][:n]) {
+			t.Fatalf("\n%s\n%s\n", read[:n], pair[1][:n])
+		}
+	}
+}
+
+func BenchmarkWithHack(b *testing.B) {
+	client, srv := net.Pipe()
+	done := make(chan struct{})
+	req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
+	read := make([]byte, 4096)
+	b.SetBytes(int64(len(req) * 30))
+
+	l := MalformedHostHeaderOverrideConn{client, true}
+	go func() {
+		for {
+			if _, err := srv.Write(req); err != nil {
+				srv.Close()
+				break
+			}
+			l.first = true // make sure each subsequent run uses the hack parsing
+		}
+		close(done)
+	}()
+
+	for i := 0; i < b.N; i++ {
+		for i := 0; i < 30; i++ {
+			if n, err := l.Read(read); err != nil && err != io.EOF {
+				b.Fatalf("read: %d - %d, err: %v\n%s", n, len(req), err, string(read[:n]))
+			}
+		}
+	}
+	l.Close()
+	<-done
+}
+
+func BenchmarkNoHack(b *testing.B) {
+	client, srv := net.Pipe()
+	done := make(chan struct{})
+	req := []byte("GET /foo\nHost: /var/run/docker.sock\nUser-Agent: Docker\n")
+	read := make([]byte, 4096)
+	b.SetBytes(int64(len(req) * 30))
+
+	go func() {
+		for {
+			if _, err := srv.Write(req); err != nil {
+				srv.Close()
+				break
+			}
+		}
+		close(done)
+	}()
+
+	for i := 0; i < b.N; i++ {
+		for i := 0; i < 30; i++ {
+			if _, err := client.Read(read); err != nil && err != io.EOF {
+				b.Fatal(err)
+			}
+		}
+	}
+	client.Close()
+	<-done
+}