Browse code

runconfig: ContainerDecoder(): fix handling of invalid JSON

Implement similar logic as is used in httputils.ReadJSON(). Before
this patch, endpoints using the ContainerDecoder would incorrectly
return a 500 (internal server error) status.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2022/04/09 06:27:50
Showing 4 changed files
... ...
@@ -17,6 +17,8 @@ func TestContainerInvalidJSON(t *testing.T) {
17 17
 
18 18
 	// POST endpoints that accept / expect a JSON body;
19 19
 	endpoints := []string{
20
+		"/commit",
21
+		"/containers/create",
20 22
 		"/containers/foobar/exec",
21 23
 		"/containers/foobar/update",
22 24
 		"/exec/foobar/start",
... ...
@@ -26,7 +28,8 @@ func TestContainerInvalidJSON(t *testing.T) {
26 26
 	if runtime.GOOS != "windows" {
27 27
 		endpoints = append(
28 28
 			endpoints,
29
-			"/v1.23/containers/foobar/copy", // deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
29
+			"/v1.23/containers/foobar/copy",  // deprecated since 1.8 (API v1.20), errors out since 1.12 (API v1.24)
30
+			"/v1.23/containers/foobar/start", // accepts a body on API < v1.24
30 31
 		)
31 32
 	}
32 33
 
... ...
@@ -40,7 +40,7 @@ func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig
40 40
 // it's your business to do so
41 41
 func decodeContainerConfig(src io.Reader, si *sysinfo.SysInfo) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
42 42
 	var w ContainerConfigWrapper
43
-	if err := json.NewDecoder(src).Decode(&w); err != nil {
43
+	if err := loadJSON(src, &w); err != nil {
44 44
 		return nil, nil, nil, err
45 45
 	}
46 46
 
... ...
@@ -72,3 +72,18 @@ func decodeContainerConfig(src io.Reader, si *sysinfo.SysInfo) (*container.Confi
72 72
 	}
73 73
 	return w.Config, hc, w.NetworkingConfig, nil
74 74
 }
75
+
76
+// loadJSON is similar to api/server/httputils.ReadJSON()
77
+func loadJSON(src io.Reader, out interface{}) error {
78
+	dec := json.NewDecoder(src)
79
+	if err := dec.Decode(&out); err != nil {
80
+		if err == io.EOF {
81
+			return validationError("invalid JSON: got EOF while reading request body")
82
+		}
83
+		return validationError("invalid JSON: " + err.Error())
84
+	}
85
+	if dec.More() {
86
+		return validationError("unexpected content after JSON")
87
+	}
88
+	return nil
89
+}
... ...
@@ -42,27 +42,30 @@ func TestDecodeContainerConfig(t *testing.T) {
42 42
 	}
43 43
 
44 44
 	for _, f := range fixtures {
45
-		b, err := os.ReadFile(f.file)
46
-		if err != nil {
47
-			t.Fatal(err)
48
-		}
45
+		f := f
46
+		t.Run(f.file, func(t *testing.T) {
47
+			b, err := os.ReadFile(f.file)
48
+			if err != nil {
49
+				t.Fatal(err)
50
+			}
49 51
 
50
-		c, h, _, err := decodeContainerConfig(bytes.NewReader(b), sysinfo.New())
51
-		if err != nil {
52
-			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
53
-		}
52
+			c, h, _, err := decodeContainerConfig(bytes.NewReader(b), sysinfo.New())
53
+			if err != nil {
54
+				t.Fatal(err)
55
+			}
54 56
 
55
-		if c.Image != image {
56
-			t.Fatalf("Expected %s image, found %s\n", image, c.Image)
57
-		}
57
+			if c.Image != image {
58
+				t.Fatalf("Expected %s image, found %s", image, c.Image)
59
+			}
58 60
 
59
-		if len(c.Entrypoint) != len(f.entrypoint) {
60
-			t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
61
-		}
61
+			if len(c.Entrypoint) != len(f.entrypoint) {
62
+				t.Fatalf("Expected %v, found %v", f.entrypoint, c.Entrypoint)
63
+			}
62 64
 
63
-		if h != nil && h.Memory != 1000 {
64
-			t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
65
-		}
65
+			if h != nil && h.Memory != 1000 {
66
+				t.Fatalf("Expected memory to be 1000, found %d", h.Memory)
67
+			}
68
+		})
66 69
 	}
67 70
 }
68 71
 
... ...
@@ -1,7 +1,6 @@
1 1
 package runconfig // import "github.com/docker/docker/runconfig"
2 2
 
3 3
 import (
4
-	"encoding/json"
5 4
 	"io"
6 5
 	"strings"
7 6
 
... ...
@@ -12,7 +11,7 @@ import (
12 12
 // It assumes the content of the reader will be JSON, and decodes it.
13 13
 func decodeHostConfig(src io.Reader) (*container.HostConfig, error) {
14 14
 	var w ContainerConfigWrapper
15
-	if err := json.NewDecoder(src).Decode(&w); err != nil {
15
+	if err := loadJSON(src, &w); err != nil {
16 16
 		return nil, err
17 17
 	}
18 18
 	return w.getHostConfig(), nil