Skip to content

Commit eb9ba7e

Browse files
committed
images: validate document type before unmarshal
Signed-off-by: Samuel Karp <skarp@amazon.com>
1 parent f44d4cf commit eb9ba7e

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

images/image.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package images
1919
import (
2020
"context"
2121
"encoding/json"
22+
"fmt"
2223
"sort"
2324
"time"
2425

@@ -154,6 +155,10 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
154155
return nil, err
155156
}
156157

158+
if err := validateMediaType(p, desc.MediaType); err != nil {
159+
return nil, errors.Wrapf(err, "manifest: invalid desc %s", desc.Digest)
160+
}
161+
157162
var manifest ocispec.Manifest
158163
if err := json.Unmarshal(p, &manifest); err != nil {
159164
return nil, err
@@ -194,6 +199,10 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
194199
return nil, err
195200
}
196201

202+
if err := validateMediaType(p, desc.MediaType); err != nil {
203+
return nil, errors.Wrapf(err, "manifest: invalid desc %s", desc.Digest)
204+
}
205+
197206
var idx ocispec.Index
198207
if err := json.Unmarshal(p, &idx); err != nil {
199208
return nil, err
@@ -336,6 +345,10 @@ func Children(ctx context.Context, provider content.Provider, desc ocispec.Descr
336345
return nil, err
337346
}
338347

348+
if err := validateMediaType(p, desc.MediaType); err != nil {
349+
return nil, errors.Wrapf(err, "children: invalid desc %s", desc.Digest)
350+
}
351+
339352
// TODO(stevvooe): We just assume oci manifest, for now. There may be
340353
// subtle differences from the docker version.
341354
var manifest ocispec.Manifest
@@ -351,6 +364,10 @@ func Children(ctx context.Context, provider content.Provider, desc ocispec.Descr
351364
return nil, err
352365
}
353366

367+
if err := validateMediaType(p, desc.MediaType); err != nil {
368+
return nil, errors.Wrapf(err, "children: invalid desc %s", desc.Digest)
369+
}
370+
354371
var index ocispec.Index
355372
if err := json.Unmarshal(p, &index); err != nil {
356373
return nil, err
@@ -368,6 +385,44 @@ func Children(ctx context.Context, provider content.Provider, desc ocispec.Descr
368385
return descs, nil
369386
}
370387

388+
// unknownDocument represents a manifest, manifest list, or index that has not
389+
// yet been validated.
390+
type unknownDocument struct {
391+
MediaType string `json:"mediaType,omitempty"`
392+
Config json.RawMessage `json:"config,omitempty"`
393+
Layers json.RawMessage `json:"layers,omitempty"`
394+
Manifests json.RawMessage `json:"manifests,omitempty"`
395+
FSLayers json.RawMessage `json:"fsLayers,omitempty"` // schema 1
396+
}
397+
398+
// validateMediaType returns an error if the byte slice is invalid JSON or if
399+
// the media type identifies the blob as one format but it contains elements of
400+
// another format.
401+
func validateMediaType(b []byte, mt string) error {
402+
var doc unknownDocument
403+
if err := json.Unmarshal(b, &doc); err != nil {
404+
return err
405+
}
406+
if len(doc.FSLayers) != 0 {
407+
return fmt.Errorf("media-type: schema 1 not supported")
408+
}
409+
switch mt {
410+
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
411+
if len(doc.Manifests) != 0 ||
412+
doc.MediaType == MediaTypeDockerSchema2ManifestList ||
413+
doc.MediaType == ocispec.MediaTypeImageIndex {
414+
return fmt.Errorf("media-type: expected manifest but found index (%s)", mt)
415+
}
416+
case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
417+
if len(doc.Config) != 0 || len(doc.Layers) != 0 ||
418+
doc.MediaType == MediaTypeDockerSchema2Manifest ||
419+
doc.MediaType == ocispec.MediaTypeImageManifest {
420+
return fmt.Errorf("media-type: expected index but found manifest (%s)", mt)
421+
}
422+
}
423+
return nil
424+
}
425+
371426
// RootFS returns the unpacked diffids that make up and images rootfs.
372427
//
373428
// These are used to verify that a set of layers unpacked to the expected

images/image_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package images
18+
19+
import (
20+
"encoding/json"
21+
"testing"
22+
23+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestValidateMediaType(t *testing.T) {
29+
docTests := []struct {
30+
mt string
31+
index bool
32+
}{
33+
{MediaTypeDockerSchema2Manifest, false},
34+
{ocispec.MediaTypeImageManifest, false},
35+
{MediaTypeDockerSchema2ManifestList, true},
36+
{ocispec.MediaTypeImageIndex, true},
37+
}
38+
for _, tc := range docTests {
39+
t.Run("manifest-"+tc.mt, func(t *testing.T) {
40+
manifest := ocispec.Manifest{
41+
Config: ocispec.Descriptor{Size: 1},
42+
Layers: []ocispec.Descriptor{{Size: 2}},
43+
}
44+
b, err := json.Marshal(manifest)
45+
require.NoError(t, err, "failed to marshal manifest")
46+
47+
err = validateMediaType(b, tc.mt)
48+
if tc.index {
49+
assert.Error(t, err, "manifest should not be a valid index")
50+
} else {
51+
assert.NoError(t, err, "manifest should be valid")
52+
}
53+
})
54+
t.Run("index-"+tc.mt, func(t *testing.T) {
55+
index := ocispec.Index{
56+
Manifests: []ocispec.Descriptor{{Size: 1}},
57+
}
58+
b, err := json.Marshal(index)
59+
require.NoError(t, err, "failed to marshal index")
60+
61+
err = validateMediaType(b, tc.mt)
62+
if tc.index {
63+
assert.NoError(t, err, "index should be valid")
64+
} else {
65+
assert.Error(t, err, "index should not be a valid manifest")
66+
}
67+
})
68+
}
69+
70+
mtTests := []struct {
71+
mt string
72+
valid []string
73+
invalid []string
74+
}{{
75+
MediaTypeDockerSchema2Manifest,
76+
[]string{MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest},
77+
[]string{MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex},
78+
}, {
79+
ocispec.MediaTypeImageManifest,
80+
[]string{MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest},
81+
[]string{MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex},
82+
}, {
83+
MediaTypeDockerSchema2ManifestList,
84+
[]string{MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex},
85+
[]string{MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest},
86+
}, {
87+
ocispec.MediaTypeImageIndex,
88+
[]string{MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex},
89+
[]string{MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest},
90+
}}
91+
for _, tc := range mtTests {
92+
for _, v := range tc.valid {
93+
t.Run("valid-"+tc.mt+"-"+v, func(t *testing.T) {
94+
doc := struct {
95+
MediaType string `json:"mediaType"`
96+
}{MediaType: v}
97+
b, err := json.Marshal(doc)
98+
require.NoError(t, err, "failed to marshal document")
99+
100+
err = validateMediaType(b, tc.mt)
101+
assert.NoError(t, err, "document should be valid")
102+
})
103+
}
104+
for _, iv := range tc.invalid {
105+
t.Run("invalid-"+tc.mt+"-"+iv, func(t *testing.T) {
106+
doc := struct {
107+
MediaType string `json:"mediaType"`
108+
}{MediaType: iv}
109+
b, err := json.Marshal(doc)
110+
require.NoError(t, err, "failed to marshal document")
111+
112+
err = validateMediaType(b, tc.mt)
113+
assert.Error(t, err, "document should not be valid")
114+
})
115+
}
116+
}
117+
t.Run("schema1", func(t *testing.T) {
118+
doc := struct {
119+
FSLayers []string `json:"fsLayers"`
120+
}{FSLayers: []string{"1"}}
121+
b, err := json.Marshal(doc)
122+
require.NoError(t, err, "failed to marshal document")
123+
124+
err = validateMediaType(b, "")
125+
assert.Error(t, err, "document should not be valid")
126+
})
127+
}

0 commit comments

Comments
 (0)