Browse code

Remove panic in nat package on invalid hostport

Closes #14621

This one grew to be much more than I expected so here's the story... :-)
- when a bad port string (e.g. xxx80) is passed into container.create()
via the API it wasn't being checked until we tried to start the container.
- While starting the container we trid to parse 'xxx80' in nat.Int()
and would panic on the strconv.ParseUint(). We should (almost) never panic.
- In trying to remove the panic I decided to make it so that we, instead,
checked the string during the NewPort() constructor. This means that
I had to change all casts from 'string' to 'Port' to use NewPort() instead.
Which is a good thing anyway, people shouldn't assume they know the
internal format of types like that, in general.
- This meant I had to go and add error checks on all calls to NewPort().
To avoid changing the testcases too much I create newPortNoError() **JUST**
for the testcase uses where we know the port string is ok.
- After all of that I then went back and added a check during container.create()
to check the port string so we'll report the error as soon as we get the
data.
- If, somehow, the bad string does get into the metadata we will generate
an error during container.start() but I can't test for that because
the container.create() catches it now. But I did add a testcase for that.

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2015/07/16 12:45:48
Showing 11 changed files
... ...
@@ -48,7 +48,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
48 48
 			proto = parts[1]
49 49
 		}
50 50
 		natPort := port + "/" + proto
51
-		if frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
51
+		newP, err := nat.NewPort(proto, port)
52
+		if err != nil {
53
+			return err
54
+		}
55
+		if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
52 56
 			for _, frontend := range frontends {
53 57
 				fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
54 58
 			}
... ...
@@ -521,7 +521,10 @@ func (container *Container) buildPortMapInfo(n libnetwork.Network, ep libnetwork
521 521
 	if expData, ok := driverInfo[netlabel.ExposedPorts]; ok {
522 522
 		if exposedPorts, ok := expData.([]types.TransportPort); ok {
523 523
 			for _, tp := range exposedPorts {
524
-				natPort := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
524
+				natPort, err := nat.NewPort(tp.Proto.String(), strconv.Itoa(int(tp.Port)))
525
+				if err != nil {
526
+					return nil, fmt.Errorf("Error parsing Port value(%s):%v", tp.Port, err)
527
+				}
525 528
 				networkSettings.Ports[natPort] = nil
526 529
 			}
527 530
 		}
... ...
@@ -534,7 +537,10 @@ func (container *Container) buildPortMapInfo(n libnetwork.Network, ep libnetwork
534 534
 
535 535
 	if portMapping, ok := mapData.([]types.PortBinding); ok {
536 536
 		for _, pp := range portMapping {
537
-			natPort := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
537
+			natPort, err := nat.NewPort(pp.Proto.String(), strconv.Itoa(int(pp.Port)))
538
+			if err != nil {
539
+				return nil, err
540
+			}
538 541
 			natBndg := nat.PortBinding{HostIp: pp.HostIP.String(), HostPort: strconv.Itoa(int(pp.HostPort))}
539 542
 			networkSettings.Ports[natPort] = append(networkSettings.Ports[natPort], natBndg)
540 543
 		}
... ...
@@ -710,7 +716,11 @@ func (container *Container) buildCreateEndpointOptions() ([]libnetwork.EndpointO
710 710
 		binding := bindings[port]
711 711
 		for i := 0; i < len(binding); i++ {
712 712
 			pbCopy := pb.GetCopy()
713
-			pbCopy.HostPort = uint16(nat.Port(binding[i].HostPort).Int())
713
+			newP, err := nat.NewPort(nat.SplitProtoPort(binding[i].HostPort))
714
+			if err != nil {
715
+				return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err)
716
+			}
717
+			pbCopy.HostPort = uint16(newP.Int())
714 718
 			pbCopy.HostIP = net.ParseIP(binding[i].HostIp)
715 719
 			pbList = append(pbList, pbCopy)
716 720
 		}
... ...
@@ -132,7 +132,13 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
132 132
 	for port := range hostConfig.PortBindings {
133 133
 		_, portStr := nat.SplitProtoPort(string(port))
134 134
 		if _, err := nat.ParsePort(portStr); err != nil {
135
-			return warnings, fmt.Errorf("Invalid port specification: %s", portStr)
135
+			return warnings, fmt.Errorf("Invalid port specification: %q", portStr)
136
+		}
137
+		for _, pb := range hostConfig.PortBindings[port] {
138
+			_, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort))
139
+			if err != nil {
140
+				return warnings, fmt.Errorf("Invalid port specification: %q", pb.HostPort)
141
+			}
136 142
 		}
137 143
 	}
138 144
 	if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
... ...
@@ -158,7 +158,10 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
158 158
 
159 159
 		newC.Ports = []types.Port{}
160 160
 		for port, bindings := range container.NetworkSettings.Ports {
161
-			p, _ := nat.ParsePort(port.Port())
161
+			p, err := nat.ParsePort(port.Port())
162
+			if err != nil {
163
+				return err
164
+			}
162 165
 			if len(bindings) == 0 {
163 166
 				newC.Ports = append(newC.Ports, types.Port{
164 167
 					PrivatePort: p,
... ...
@@ -167,7 +170,10 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
167 167
 				continue
168 168
 			}
169 169
 			for _, binding := range bindings {
170
-				h, _ := nat.ParsePort(binding.HostPort)
170
+				h, err := nat.ParsePort(binding.HostPort)
171
+				if err != nil {
172
+					return err
173
+				}
171 174
 				newC.Ports = append(newC.Ports, types.Port{
172 175
 					PrivatePort: p,
173 176
 					PublicPort:  h,
... ...
@@ -872,6 +872,32 @@ func (s *DockerSuite) TestContainerApiCommitWithLabelInConfig(c *check.C) {
872 872
 	dockerCmd(c, "run", img.Id, "ls", "/test")
873 873
 }
874 874
 
875
+func (s *DockerSuite) TestContainerApiBadPort(c *check.C) {
876
+	config := map[string]interface{}{
877
+		"Image": "busybox",
878
+		"Cmd":   []string{"/bin/sh", "-c", "echo test"},
879
+		"PortBindings": map[string]interface{}{
880
+			"8080/tcp": []map[string]interface{}{
881
+				{
882
+					"HostIp":   "",
883
+					"HostPort": "aa80",
884
+				},
885
+			},
886
+		},
887
+	}
888
+
889
+	jsonData := bytes.NewBuffer(nil)
890
+	json.NewEncoder(jsonData).Encode(config)
891
+
892
+	status, b, err := sockRequest("POST", "/containers/create", config)
893
+	c.Assert(err, check.IsNil)
894
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
895
+
896
+	if strings.TrimSpace(string(b)) != `Invalid port specification: "aa80"` {
897
+		c.Fatalf("Incorrect error msg: %s", string(b))
898
+	}
899
+}
900
+
875 901
 func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
876 902
 	config := map[string]interface{}{
877 903
 		"Image": "busybox",
... ...
@@ -8,9 +8,15 @@ import (
8 8
 	"github.com/docker/docker/pkg/nat"
9 9
 )
10 10
 
11
+// Just to make life easier
12
+func newPortNoError(proto, port string) nat.Port {
13
+	p, _ := nat.NewPort(proto, port)
14
+	return p
15
+}
16
+
11 17
 func TestLinkNaming(t *testing.T) {
12 18
 	ports := make(nat.PortSet)
13
-	ports[nat.Port("6379/tcp")] = struct{}{}
19
+	ports[newPortNoError("tcp", "6379")] = struct{}{}
14 20
 
15 21
 	link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports)
16 22
 	if err != nil {
... ...
@@ -40,7 +46,7 @@ func TestLinkNaming(t *testing.T) {
40 40
 
41 41
 func TestLinkNew(t *testing.T) {
42 42
 	ports := make(nat.PortSet)
43
-	ports[nat.Port("6379/tcp")] = struct{}{}
43
+	ports[newPortNoError("tcp", "6379")] = struct{}{}
44 44
 
45 45
 	link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports)
46 46
 	if err != nil {
... ...
@@ -63,7 +69,7 @@ func TestLinkNew(t *testing.T) {
63 63
 		t.Fail()
64 64
 	}
65 65
 	for _, p := range link.Ports {
66
-		if p != nat.Port("6379/tcp") {
66
+		if p != newPortNoError("tcp", "6379") {
67 67
 			t.Fail()
68 68
 		}
69 69
 	}
... ...
@@ -71,7 +77,7 @@ func TestLinkNew(t *testing.T) {
71 71
 
72 72
 func TestLinkEnv(t *testing.T) {
73 73
 	ports := make(nat.PortSet)
74
-	ports[nat.Port("6379/tcp")] = struct{}{}
74
+	ports[newPortNoError("tcp", "6379")] = struct{}{}
75 75
 
76 76
 	link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
77 77
 	if err != nil {
... ...
@@ -112,9 +118,9 @@ func TestLinkEnv(t *testing.T) {
112 112
 
113 113
 func TestLinkMultipleEnv(t *testing.T) {
114 114
 	ports := make(nat.PortSet)
115
-	ports[nat.Port("6379/tcp")] = struct{}{}
116
-	ports[nat.Port("6380/tcp")] = struct{}{}
117
-	ports[nat.Port("6381/tcp")] = struct{}{}
115
+	ports[newPortNoError("tcp", "6379")] = struct{}{}
116
+	ports[newPortNoError("tcp", "6380")] = struct{}{}
117
+	ports[newPortNoError("tcp", "6381")] = struct{}{}
118 118
 
119 119
 	link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
120 120
 	if err != nil {
... ...
@@ -161,9 +167,9 @@ func TestLinkMultipleEnv(t *testing.T) {
161 161
 
162 162
 func TestLinkPortRangeEnv(t *testing.T) {
163 163
 	ports := make(nat.PortSet)
164
-	ports[nat.Port("6379/tcp")] = struct{}{}
165
-	ports[nat.Port("6380/tcp")] = struct{}{}
166
-	ports[nat.Port("6381/tcp")] = struct{}{}
164
+	ports[newPortNoError("tcp", "6379")] = struct{}{}
165
+	ports[newPortNoError("tcp", "6380")] = struct{}{}
166
+	ports[newPortNoError("tcp", "6381")] = struct{}{}
167 167
 
168 168
 	link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports)
169 169
 	if err != nil {
... ...
@@ -29,8 +29,16 @@ type PortSet map[Port]struct{}
29 29
 // 80/tcp
30 30
 type Port string
31 31
 
32
-func NewPort(proto, port string) Port {
33
-	return Port(fmt.Sprintf("%s/%s", port, proto))
32
+func NewPort(proto, port string) (Port, error) {
33
+	// Check for parsing issues on "port" now so we can avoid having
34
+	// to check it later on.
35
+
36
+	portInt, err := ParsePort(port)
37
+	if err != nil {
38
+		return "", err
39
+	}
40
+
41
+	return Port(fmt.Sprintf("%d/%s", portInt, proto)), nil
34 42
 }
35 43
 
36 44
 func ParsePort(rawPort string) (int, error) {
... ...
@@ -55,11 +63,15 @@ func (p Port) Port() string {
55 55
 }
56 56
 
57 57
 func (p Port) Int() int {
58
-	port, err := ParsePort(p.Port())
59
-	if err != nil {
60
-		panic(err)
58
+	portStr := p.Port()
59
+	if len(portStr) == 0 {
60
+		return 0
61 61
 	}
62
-	return port
62
+
63
+	// We don't need to check for an error because we're going to
64
+	// assume that any error would have been found, and reported, in NewPort()
65
+	port, _ := strconv.ParseUint(portStr, 10, 16)
66
+	return int(port)
63 67
 }
64 68
 
65 69
 // Splits a port in the format of proto/port
... ...
@@ -152,7 +164,10 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
152 152
 			if len(hostPort) > 0 {
153 153
 				hostPort = strconv.FormatUint(startHostPort+i, 10)
154 154
 			}
155
-			port := NewPort(strings.ToLower(proto), containerPort)
155
+			port, err := NewPort(strings.ToLower(proto), containerPort)
156
+			if err != nil {
157
+				return nil, nil, err
158
+			}
156 159
 			if _, exists := exposedPorts[port]; !exists {
157 160
 				exposedPorts[port] = struct{}{}
158 161
 			}
... ...
@@ -42,7 +42,11 @@ func TestParsePort(t *testing.T) {
42 42
 }
43 43
 
44 44
 func TestPort(t *testing.T) {
45
-	p := NewPort("tcp", "1234")
45
+	p, err := NewPort("tcp", "1234")
46
+
47
+	if err != nil {
48
+		t.Fatalf("tcp, 1234 had a parsing issue: %v", err)
49
+	}
46 50
 
47 51
 	if string(p) != "1234/tcp" {
48 52
 		t.Fatal("tcp, 1234 did not result in the string 1234/tcp")
... ...
@@ -59,6 +63,11 @@ func TestPort(t *testing.T) {
59 59
 	if p.Int() != 1234 {
60 60
 		t.Fatal("port int value was not 1234")
61 61
 	}
62
+
63
+	p, err = NewPort("tcp", "asd1234")
64
+	if err == nil {
65
+		t.Fatal("tcp, asd1234 was supposed to fail")
66
+	}
62 67
 }
63 68
 
64 69
 func TestSplitProtoPort(t *testing.T) {
... ...
@@ -6,17 +6,23 @@ import (
6 6
 	"github.com/docker/docker/pkg/nat"
7 7
 )
8 8
 
9
+// Just to make life easier
10
+func newPortNoError(proto, port string) nat.Port {
11
+	p, _ := nat.NewPort(proto, port)
12
+	return p
13
+}
14
+
9 15
 func TestCompare(t *testing.T) {
10 16
 	ports1 := make(nat.PortSet)
11
-	ports1[nat.Port("1111/tcp")] = struct{}{}
12
-	ports1[nat.Port("2222/tcp")] = struct{}{}
17
+	ports1[newPortNoError("tcp", "1111")] = struct{}{}
18
+	ports1[newPortNoError("tcp", "2222")] = struct{}{}
13 19
 	ports2 := make(nat.PortSet)
14
-	ports2[nat.Port("3333/tcp")] = struct{}{}
15
-	ports2[nat.Port("4444/tcp")] = struct{}{}
20
+	ports2[newPortNoError("tcp", "3333")] = struct{}{}
21
+	ports2[newPortNoError("tcp", "4444")] = struct{}{}
16 22
 	ports3 := make(nat.PortSet)
17
-	ports3[nat.Port("1111/tcp")] = struct{}{}
18
-	ports3[nat.Port("2222/tcp")] = struct{}{}
19
-	ports3[nat.Port("5555/tcp")] = struct{}{}
23
+	ports3[newPortNoError("tcp", "1111")] = struct{}{}
24
+	ports3[newPortNoError("tcp", "2222")] = struct{}{}
25
+	ports3[newPortNoError("tcp", "5555")] = struct{}{}
20 26
 	volumes1 := make(map[string]struct{})
21 27
 	volumes1["/test1"] = struct{}{}
22 28
 	volumes2 := make(map[string]struct{})
... ...
@@ -11,8 +11,8 @@ func TestMerge(t *testing.T) {
11 11
 	volumesImage["/test1"] = struct{}{}
12 12
 	volumesImage["/test2"] = struct{}{}
13 13
 	portsImage := make(nat.PortSet)
14
-	portsImage[nat.Port("1111/tcp")] = struct{}{}
15
-	portsImage[nat.Port("2222/tcp")] = struct{}{}
14
+	portsImage[newPortNoError("tcp", "1111")] = struct{}{}
15
+	portsImage[newPortNoError("tcp", "2222")] = struct{}{}
16 16
 	configImage := &Config{
17 17
 		ExposedPorts: portsImage,
18 18
 		Env:          []string{"VAR1=1", "VAR2=2"},
... ...
@@ -20,8 +20,8 @@ func TestMerge(t *testing.T) {
20 20
 	}
21 21
 
22 22
 	portsUser := make(nat.PortSet)
23
-	portsUser[nat.Port("2222/tcp")] = struct{}{}
24
-	portsUser[nat.Port("3333/tcp")] = struct{}{}
23
+	portsUser[newPortNoError("tcp", "2222")] = struct{}{}
24
+	portsUser[newPortNoError("tcp", "3333")] = struct{}{}
25 25
 	volumesUser := make(map[string]struct{})
26 26
 	volumesUser["/test3"] = struct{}{}
27 27
 	configUser := &Config{
... ...
@@ -267,7 +267,10 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
267 267
 			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
268 268
 		}
269 269
 		for i := start; i <= end; i++ {
270
-			p := nat.NewPort(proto, strconv.FormatUint(i, 10))
270
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
271
+			if err != nil {
272
+				return nil, nil, cmd, err
273
+			}
271 274
 			if _, exists := ports[p]; !exists {
272 275
 				ports[p] = struct{}{}
273 276
 			}