@marcel91200 / caddy Mirror · read-only / commits / 93bc1b72
Fork 0

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