Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
core: Use port ranges to avoid OOM with bad inputs (#2859)
* fix OOM issue caught by fuzzing * use ParsedAddress as the struct name for the result of ParseNetworkAddress * simplify code using the ParsedAddress type * minor cleanups
Mohammed Al Sahaf committed
Nov 12, 2019 at 01:33 UTC
93bc1b72e3cd566e6447ad7a1f832474aad5dfcc
8 files changed
+201
-130
admin.go
+6
-10
@@ -48,23 +48,19 @@ type AdminConfig struct {
48
49
// listenAddr extracts a singular listen address from ac.Listen,
50
// returning the network and the address of the listener.
51
-func (admin AdminConfig) listenAddr() (netw string, addr string, err error) {
52
- var listenAddrs []string
51
+func (admin AdminConfig) listenAddr() (string, string, error) {
52
input := admin.Listen
53
if input == "" {
54
input = DefaultAdminListen
55
}
57
- netw, listenAddrs, err = ParseNetworkAddress(input)
56
+ listenAddr, err := ParseNetworkAddress(input)
57
if err != nil {
59
- err = fmt.Errorf("parsing admin listener address: %v", err)
60
- return
58
+ return "", "", fmt.Errorf("parsing admin listener address: %v", err)
59
}
62
- if len(listenAddrs) != 1 {
63
- err = fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddrs)
64
- return
60
+ if listenAddr.PortRangeSize() != 1 {
61
+ return "", "", fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
62
}
66
- addr = listenAddrs[0]
67
- return
63
+ return listenAddr.Network, listenAddr.JoinHostPort(0), nil
64
}
65
66
// newAdminHandler reads admin's config and returns an http.Handler suitable
listeners.go
+75
-26
@@ -257,52 +257,94 @@ type globalListener struct {
257
pc net.PacketConn
258
}
259
260
-var (
261
- listeners = make(map[string]*globalListener)
262
- listenersMu sync.Mutex
263
-)
260
+// ParsedAddress contains the individual components
261
+// for a parsed network address of the form accepted
262
+// by ParseNetworkAddress(). Network should be a
263
+// network value accepted by Go's net package. Port
264
+// ranges are given by [StartPort, EndPort].
265
+type ParsedAddress struct {
266
+ Network string
267
+ Host string
268
+ StartPort uint
269
+ EndPort uint
270
+}
271
+
272
+// JoinHostPort is like net.JoinHostPort, but where the port
273
+// is StartPort + offset.
274
+func (l ParsedAddress) JoinHostPort(offset uint) string {
275
+ return net.JoinHostPort(l.Host, strconv.Itoa(int(l.StartPort+offset)))
276
+}
277
265
-// ParseNetworkAddress parses addr, a string of the form "network/host:port"
266
-// (with any part optional) into its component parts. Because a port can
267
-// also be a port range, there may be multiple addresses returned.
268
-func ParseNetworkAddress(addr string) (network string, addrs []string, err error) {
278
+// PortRangeSize returns how many ports are in
279
+// pa's port range. Port ranges are inclusive,
280
+// so the size is the difference of start and
281
+// end ports plus one.
282
+func (pa ParsedAddress) PortRangeSize() uint {
283
+ return (pa.EndPort - pa.StartPort) + 1
284
+}
285
+
286
+// String reconstructs the address string to the form expected
287
+// by ParseNetworkAddress().
288
+func (pa ParsedAddress) String() string {
289
+ port := strconv.FormatUint(uint64(pa.StartPort), 10)
290
+ if pa.StartPort != pa.EndPort {
291
+ port += "-" + strconv.FormatUint(uint64(pa.EndPort), 10)
292
+ }
293
+ return JoinNetworkAddress(pa.Network, pa.Host, port)
294
+}
295
+
296
+// ParseNetworkAddress parses addr into its individual
297
+// components. The input string is expected to be of
298
+// the form "network/host:port-range" where any part is
299
+// optional. The default network, if unspecified, is tcp.
300
+// Port ranges are inclusive.
301
+//
302
+// Network addresses are distinct from URLs and do not
303
+// use URL syntax.
304
+func ParseNetworkAddress(addr string) (ParsedAddress, error) {
305
var host, port string
270
- network, host, port, err = SplitNetworkAddress(addr)
306
+ network, host, port, err := SplitNetworkAddress(addr)
307
if network == "" {
308
network = "tcp"
309
}
310
if err != nil {
275
- return
311
+ return ParsedAddress{}, err
312
}
313
if network == "unix" || network == "unixgram" || network == "unixpacket" {
278
- addrs = []string{host}
279
- return
314
+ return ParsedAddress{
315
+ Network: network,
316
+ Host: host,
317
+ }, nil
318
}
319
ports := strings.SplitN(port, "-", 2)
320
if len(ports) == 1 {
321
ports = append(ports, ports[0])
322
}
285
- var start, end int
286
- start, err = strconv.Atoi(ports[0])
323
+ var start, end uint64
324
+ start, err = strconv.ParseUint(ports[0], 10, 16)
325
if err != nil {
288
- return
326
+ return ParsedAddress{}, fmt.Errorf("invalid start port: %v", err)
327
}
290
- end, err = strconv.Atoi(ports[1])
328
+ end, err = strconv.ParseUint(ports[1], 10, 16)
329
if err != nil {
292
- return
330
+ return ParsedAddress{}, fmt.Errorf("invalid end port: %v", err)
331
}
332
if end < start {
295
- err = fmt.Errorf("end port must be greater than start port")
296
- return
333
+ return ParsedAddress{}, fmt.Errorf("end port must not be less than start port")
334
}
298
- for p := start; p <= end; p++ {
299
- addrs = append(addrs, net.JoinHostPort(host, fmt.Sprintf("%d", p)))
335
+ if (end - start) > maxPortSpan {
336
+ return ParsedAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
337
}
301
- return
338
+ return ParsedAddress{
339
+ Network: network,
340
+ Host: host,
341
+ StartPort: uint(start),
342
+ EndPort: uint(end),
343
+ }, nil
344
}
345
346
// SplitNetworkAddress splits a into its network, host, and port components.
305
-// Note that port may be a port range, or omitted for unix sockets.
347
+// Note that port may be a port range (:X-Y), or omitted for unix sockets.
348
func SplitNetworkAddress(a string) (network, host, port string, err error) {
349
if idx := strings.Index(a, "/"); idx >= 0 {
350
network = strings.ToLower(strings.TrimSpace(a[:idx]))
@@ -317,9 +359,9 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
359
}
360
361
// JoinNetworkAddress combines network, host, and port into a single
320
-// address string of the form "network/host:port". Port may be a
321
-// port range. For unix sockets, the network should be "unix" and
322
-// the path to the socket should be given in the host argument.
362
+// address string of the form accepted by ParseNetworkAddress(). For unix sockets, the network
363
+// should be "unix" and the path to the socket should be given as the
364
+// host parameter.
365
func JoinNetworkAddress(network, host, port string) string {
366
var a string
367
if network != "" {
@@ -332,3 +374,10 @@ func JoinNetworkAddress(network, host, port string) string {
374
}
375
return a
376
}
377
+
378
+var (
379
+ listeners = make(map[string]*globalListener)
380
+ listenersMu sync.Mutex
381
+)
382
+
383
+const maxPortSpan = 65535
listeners_fuzz.go
+1
-1
@@ -18,7 +18,7 @@
18
package caddy
19
20
func FuzzParseNetworkAddress(data []byte) int {
21
- _, _, err := ParseNetworkAddress(string(data))
21
+ _, err := ParseNetworkAddress(string(data))
22
if err != nil {
23
return 0
24
}
listeners_test.go
+66
-39
@@ -152,74 +152,101 @@ func TestJoinNetworkAddress(t *testing.T) {
152
153
func TestParseNetworkAddress(t *testing.T) {
154
for i, tc := range []struct {
155
- input string
156
- expectNetwork string
157
- expectAddrs []string
158
- expectErr bool
155
+ input string
156
+ expectAddr ParsedAddress
157
+ expectErr bool
158
}{
159
{
161
- input: "",
162
- expectNetwork: "tcp",
163
- expectErr: true,
160
+ input: "",
161
+ expectErr: true,
162
},
163
{
166
- input: ":",
167
- expectNetwork: "tcp",
168
- expectErr: true,
164
+ input: ":",
165
+ expectErr: true,
166
},
167
{
171
- input: ":1234",
172
- expectNetwork: "tcp",
173
- expectAddrs: []string{":1234"},
168
+ input: ":1234",
169
+ expectAddr: ParsedAddress{
170
+ Network: "tcp",
171
+ Host: "",
172
+ StartPort: 1234,
173
+ EndPort: 1234,
174
+ },
175
},
176
{
176
- input: "tcp/:1234",
177
- expectNetwork: "tcp",
178
- expectAddrs: []string{":1234"},
177
+ input: "tcp/:1234",
178
+ expectAddr: ParsedAddress{
179
+ Network: "tcp",
180
+ Host: "",
181
+ StartPort: 1234,
182
+ EndPort: 1234,
183
+ },
184
},
185
{
181
- input: "tcp6/:1234",
182
- expectNetwork: "tcp6",
183
- expectAddrs: []string{":1234"},
186
+ input: "tcp6/:1234",
187
+ expectAddr: ParsedAddress{
188
+ Network: "tcp6",
189
+ Host: "",
190
+ StartPort: 1234,
191
+ EndPort: 1234,
192
+ },
193
},
194
{
186
- input: "tcp4/localhost:1234",
187
- expectNetwork: "tcp4",
188
- expectAddrs: []string{"localhost:1234"},
195
+ input: "tcp4/localhost:1234",
196
+ expectAddr: ParsedAddress{
197
+ Network: "tcp4",
198
+ Host: "localhost",
199
+ StartPort: 1234,
200
+ EndPort: 1234,
201
+ },
202
},
203
{
191
- input: "unix//foo/bar",
192
- expectNetwork: "unix",
193
- expectAddrs: []string{"/foo/bar"},
204
+ input: "unix//foo/bar",
205
+ expectAddr: ParsedAddress{
206
+ Network: "unix",
207
+ Host: "/foo/bar",
208
+ },
209
},
210
{
196
- input: "localhost:1234-1234",
197
- expectNetwork: "tcp",
198
- expectAddrs: []string{"localhost:1234"},
211
+ input: "localhost:1234-1234",
212
+ expectAddr: ParsedAddress{
213
+ Network: "tcp",
214
+ Host: "localhost",
215
+ StartPort: 1234,
216
+ EndPort: 1234,
217
+ },
218
},
219
{
201
- input: "localhost:2-1",
202
- expectNetwork: "tcp",
203
- expectErr: true,
220
+ input: "localhost:2-1",
221
+ expectErr: true,
222
},
223
{
206
- input: "localhost:0",
207
- expectNetwork: "tcp",
208
- expectAddrs: []string{"localhost:0"},
224
+ input: "localhost:0",
225
+ expectAddr: ParsedAddress{
226
+ Network: "tcp",
227
+ Host: "localhost",
228
+ StartPort: 0,
229
+ EndPort: 0,
230
+ },
231
+ },
232
+ {
233
+ input: "localhost:1-999999999999",
234
+ expectErr: true,
235
},
236
} {
211
- actualNetwork, actualAddrs, err := ParseNetworkAddress(tc.input)
237
+ actualAddr, err := ParseNetworkAddress(tc.input)
238
if tc.expectErr && err == nil {
239
t.Errorf("Test %d: Expected error but got: %v", i, err)
240
}
241
if !tc.expectErr && err != nil {
242
t.Errorf("Test %d: Expected no error but got: %v", i, err)
243
}
218
- if actualNetwork != tc.expectNetwork {
219
- t.Errorf("Test %d: Expected network '%s' but got '%s'", i, tc.expectNetwork, actualNetwork)
244
+
245
+ if actualAddr.Network != tc.expectAddr.Network {
246
+ t.Errorf("Test %d: Expected network '%v' but got '%v'", i, tc.expectAddr, actualAddr)
247
}
221
- if !reflect.DeepEqual(tc.expectAddrs, actualAddrs) {
222
- t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddrs, actualAddrs)
248
+ if !reflect.DeepEqual(tc.expectAddr, actualAddr) {
249
+ t.Errorf("Test %d: Expected addresses %v but got %v", i, tc.expectAddr, actualAddr)
250
}
251
}
252
}
modules/caddyhttp/caddyhttp.go
+18
-15
@@ -135,15 +135,18 @@ func (app *App) Validate() error {
135
lnAddrs := make(map[string]string)
136
for srvName, srv := range app.Servers {
137
for _, addr := range srv.Listen {
138
- netw, expanded, err := caddy.ParseNetworkAddress(addr)
138
+ listenAddr, err := caddy.ParseNetworkAddress(addr)
139
if err != nil {
140
return fmt.Errorf("invalid listener address '%s': %v", addr, err)
141
}
142
- for _, a := range expanded {
143
- if sn, ok := lnAddrs[netw+a]; ok {
144
- return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, a, sn)
142
+ // check that every address in the port range is unique to this server;
143
+ // we do not use <= here because PortRangeSize() adds 1 to EndPort for us
144
+ for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
145
+ addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
146
+ if sn, ok := lnAddrs[addr]; ok {
147
+ return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
148
}
146
- lnAddrs[netw+a] = srvName
149
+ lnAddrs[addr] = srvName
150
}
151
}
152
}
@@ -176,14 +179,15 @@ func (app *App) Start() error {
179
}
180
181
for _, lnAddr := range srv.Listen {
179
- network, addrs, err := caddy.ParseNetworkAddress(lnAddr)
182
+ listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
183
if err != nil {
184
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
185
}
183
- for _, addr := range addrs {
184
- ln, err := caddy.Listen(network, addr)
186
+ for i := uint(0); i <= listenAddr.PortRangeSize(); i++ {
187
+ hostport := listenAddr.JoinHostPort(i)
188
+ ln, err := caddy.Listen(listenAddr.Network, hostport)
189
if err != nil {
186
- return fmt.Errorf("%s: listening on %s: %v", network, addr, err)
190
+ return fmt.Errorf("%s: listening on %s: %v", listenAddr.Network, hostport, err)
191
}
192
193
// enable HTTP/2 by default
@@ -194,11 +198,10 @@ func (app *App) Start() error {
198
}
199
200
// enable TLS
197
- _, port, _ := net.SplitHostPort(addr)
198
- if len(srv.TLSConnPolicies) > 0 && port != strconv.Itoa(app.httpPort()) {
201
+ if len(srv.TLSConnPolicies) > 0 && int(i) != app.httpPort() {
202
tlsCfg, err := srv.TLSConnPolicies.TLSConfig(app.ctx)
203
if err != nil {
201
- return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err)
204
+ return fmt.Errorf("%s/%s: making TLS configuration: %v", listenAddr.Network, hostport, err)
205
}
206
ln = tls.NewListener(ln, tlsCfg)
207
@@ -206,15 +209,15 @@ func (app *App) Start() error {
209
// TODO: HTTP/3 support is experimental for now
210
if srv.ExperimentalHTTP3 {
211
app.logger.Info("enabling experimental HTTP/3 listener",
209
- zap.String("addr", addr),
212
+ zap.String("addr", hostport),
213
)
211
- h3ln, err := caddy.ListenPacket("udp", addr)
214
+ h3ln, err := caddy.ListenPacket("udp", hostport)
215
if err != nil {
216
return fmt.Errorf("getting HTTP/3 UDP listener: %v", err)
217
}
218
h3srv := &http3.Server{
219
Server: &http.Server{
217
- Addr: addr,
220
+ Addr: hostport,
221
Handler: srv,
222
TLSConfig: tlsCfg,
223
},
modules/caddyhttp/reverseproxy/healthchecks.go
+5
-5
@@ -102,7 +102,7 @@ func (h *Handler) doActiveHealthChecksForAllHosts() {
102
host := value.(Host)
103
104
go func(networkAddr string, host Host) {
105
- network, addrs, err := caddy.ParseNetworkAddress(networkAddr)
105
+ addr, err := caddy.ParseNetworkAddress(networkAddr)
106
if err != nil {
107
h.HealthChecks.Active.logger.Error("bad network address",
108
zap.String("address", networkAddr),
@@ -110,20 +110,20 @@ func (h *Handler) doActiveHealthChecksForAllHosts() {
110
)
111
return
112
}
113
- if len(addrs) != 1 {
113
+ if addr.PortRangeSize() != 1 {
114
h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)",
115
zap.String("address", networkAddr),
116
)
117
return
118
}
119
- hostAddr := addrs[0]
120
- if network == "unix" || network == "unixgram" || network == "unixpacket" {
119
+ hostAddr := addr.JoinHostPort(0)
120
+ if addr.Network == "unix" || addr.Network == "unixgram" || addr.Network == "unixpacket" {
121
// this will be used as the Host portion of a http.Request URL, and
122
// paths to socket files would produce an error when creating URL,
123
// so use a fake Host value instead; unix sockets are usually local
124
hostAddr = "localhost"
125
}
126
- err = h.doActiveHealthCheck(DialInfo{Network: network, Address: addrs[0]}, hostAddr, host)
126
+ err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: hostAddr}, hostAddr, host)
127
if err != nil {
128
h.HealthChecks.Active.logger.Error("active health check failed",
129
zap.String("address", networkAddr),
modules/caddyhttp/reverseproxy/hosts.go
+8
-16
@@ -16,8 +16,7 @@ package reverseproxy
16
17
import (
18
"fmt"
19
- "net"
20
- "strings"
19
+ "strconv"
20
"sync/atomic"
21
22
"github.com/caddyserver/caddy/v2"
@@ -193,27 +192,20 @@ func (di DialInfo) String() string {
192
// the given Replacer. Note that the returned value is not a pointer.
193
func fillDialInfo(upstream *Upstream, repl caddy.Replacer) (DialInfo, error) {
194
dial := repl.ReplaceAll(upstream.Dial, "")
196
- netw, addrs, err := caddy.ParseNetworkAddress(dial)
195
+ addr, err := caddy.ParseNetworkAddress(dial)
196
if err != nil {
197
return DialInfo{}, fmt.Errorf("upstream %s: invalid dial address %s: %v", upstream.Dial, dial, err)
198
}
200
- if len(addrs) != 1 {
199
+ if numPorts := addr.PortRangeSize(); numPorts != 1 {
200
return DialInfo{}, fmt.Errorf("upstream %s: dial address must represent precisely one socket: %s represents %d",
202
- upstream.Dial, dial, len(addrs))
203
- }
204
- var dialHost, dialPort string
205
- if !strings.Contains(netw, "unix") {
206
- dialHost, dialPort, err = net.SplitHostPort(addrs[0])
207
- if err != nil {
208
- dialHost = addrs[0] // assume there was no port
209
- }
201
+ upstream.Dial, dial, numPorts)
202
}
203
return DialInfo{
204
Upstream: upstream,
213
- Network: netw,
214
- Address: addrs[0],
215
- Host: dialHost,
216
- Port: dialPort,
205
+ Network: addr.Network,
206
+ Address: addr.JoinHostPort(0),
207
+ Host: addr.Host,
208
+ Port: strconv.Itoa(int(addr.StartPort)),
209
}, nil
210
}
211
modules/caddyhttp/server.go
+22
-18
@@ -242,40 +242,44 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next
242
// listeners in s that use a port which is not otherPort.
243
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
244
for _, lnAddr := range s.Listen {
245
- _, addrs, err := caddy.ParseNetworkAddress(lnAddr)
246
- if err == nil {
247
- for _, a := range addrs {
248
- _, port, err := net.SplitHostPort(a)
249
- if err == nil && port != strconv.Itoa(otherPort) {
250
- return true
251
- }
252
- }
245
+ laddrs, err := caddy.ParseNetworkAddress(lnAddr)
246
+ if err != nil {
247
+ continue
248
+ }
249
+ if uint(otherPort) > laddrs.EndPort || uint(otherPort) < laddrs.StartPort {
250
+ return true
251
}
252
}
253
return false
254
}
255
256
+// hasListenerAddress returns true if s has a listener
257
+// at the given address fullAddr. Currently, fullAddr
258
+// must represent exactly one socket address (port
259
+// ranges are not supported)
260
func (s *Server) hasListenerAddress(fullAddr string) bool {
259
- netw, addrs, err := caddy.ParseNetworkAddress(fullAddr)
261
+ laddrs, err := caddy.ParseNetworkAddress(fullAddr)
262
if err != nil {
263
return false
264
}
263
- if len(addrs) != 1 {
264
- return false
265
+ if laddrs.PortRangeSize() != 1 {
266
+ return false // TODO: support port ranges
267
}
266
- addr := addrs[0]
268
+
269
for _, lnAddr := range s.Listen {
268
- thisNetw, thisAddrs, err := caddy.ParseNetworkAddress(lnAddr)
270
+ thisAddrs, err := caddy.ParseNetworkAddress(lnAddr)
271
if err != nil {
272
continue
273
}
272
- if thisNetw != netw {
274
+ if thisAddrs.Network != laddrs.Network {
275
continue
276
}
275
- for _, a := range thisAddrs {
276
- if a == addr {
277
- return true
278
- }
277
+
278
+ // host must be the same and port must fall within port range
279
+ if (thisAddrs.Host == laddrs.Host) &&
280
+ (laddrs.StartPort <= thisAddrs.EndPort) &&
281
+ (laddrs.StartPort >= thisAddrs.StartPort) {
282
+ return true
283
}
284
}
285
return false