manifest: validate document type before unmarshal
Signed-off-by: Samuel Karp <skarp@amazon.com>master
							parent
							
								
									6248a88d03
								
							
						
					
					
						commit
						b59a6f8279
					
				|  | @ -54,6 +54,9 @@ func init() { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { | 	imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { | ||||||
|  | 		if err := validateIndex(b); err != nil { | ||||||
|  | 			return nil, distribution.Descriptor{}, err | ||||||
|  | 		} | ||||||
| 		m := new(DeserializedManifestList) | 		m := new(DeserializedManifestList) | ||||||
| 		err := m.UnmarshalJSON(b) | 		err := m.UnmarshalJSON(b) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -221,3 +224,23 @@ func (m DeserializedManifestList) Payload() (string, []byte, error) { | ||||||
| 
 | 
 | ||||||
| 	return mediaType, m.canonical, nil | 	return mediaType, m.canonical, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // unknownDocument represents a manifest, manifest list, or index that has not
 | ||||||
|  | // yet been validated
 | ||||||
|  | type unknownDocument struct { | ||||||
|  | 	Config interface{} `json:"config,omitempty"` | ||||||
|  | 	Layers interface{} `json:"layers,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // validateIndex returns an error if the byte slice is invalid JSON or if it
 | ||||||
|  | // contains fields that belong to a manifest
 | ||||||
|  | func validateIndex(b []byte) error { | ||||||
|  | 	var doc unknownDocument | ||||||
|  | 	if err := json.Unmarshal(b, &doc); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if doc.Config != nil || doc.Layers != nil { | ||||||
|  | 		return errors.New("index: expected index but found manifest") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -7,6 +7,8 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/distribution/distribution/v3" | 	"github.com/distribution/distribution/v3" | ||||||
|  | 	"github.com/distribution/distribution/v3/manifest/ocischema" | ||||||
|  | 
 | ||||||
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -327,3 +329,33 @@ func TestMediaTypes(t *testing.T) { | ||||||
| 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex, false) | 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex, false) | ||||||
| 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex+"XXX", true) | 	mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex+"XXX", true) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestValidateManifest(t *testing.T) { | ||||||
|  | 	manifest := ocischema.Manifest{ | ||||||
|  | 		Config: distribution.Descriptor{Size: 1}, | ||||||
|  | 		Layers: []distribution.Descriptor{{Size: 2}}, | ||||||
|  | 	} | ||||||
|  | 	index := ManifestList{ | ||||||
|  | 		Manifests: []ManifestDescriptor{ | ||||||
|  | 			{Descriptor: distribution.Descriptor{Size: 3}}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	t.Run("valid", func(t *testing.T) { | ||||||
|  | 		b, err := json.Marshal(index) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error marshaling index", err) | ||||||
|  | 		} | ||||||
|  | 		if err := validateIndex(b); err != nil { | ||||||
|  | 			t.Error("index should be valid", err) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid", func(t *testing.T) { | ||||||
|  | 		b, err := json.Marshal(manifest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error marshaling manifest", err) | ||||||
|  | 		} | ||||||
|  | 		if err := validateIndex(b); err == nil { | ||||||
|  | 			t.Error("manifest should not be valid") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -22,6 +22,9 @@ var ( | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
| 	ocischemaFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { | 	ocischemaFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { | ||||||
|  | 		if err := validateManifest(b); err != nil { | ||||||
|  | 			return nil, distribution.Descriptor{}, err | ||||||
|  | 		} | ||||||
| 		m := new(DeserializedManifest) | 		m := new(DeserializedManifest) | ||||||
| 		err := m.UnmarshalJSON(b) | 		err := m.UnmarshalJSON(b) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -122,3 +125,22 @@ func (m *DeserializedManifest) MarshalJSON() ([]byte, error) { | ||||||
| func (m DeserializedManifest) Payload() (string, []byte, error) { | func (m DeserializedManifest) Payload() (string, []byte, error) { | ||||||
| 	return v1.MediaTypeImageManifest, m.canonical, nil | 	return v1.MediaTypeImageManifest, m.canonical, nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // unknownDocument represents a manifest, manifest list, or index that has not
 | ||||||
|  | // yet been validated
 | ||||||
|  | type unknownDocument struct { | ||||||
|  | 	Manifests interface{} `json:"manifests,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // validateManifest returns an error if the byte slice is invalid JSON or if it
 | ||||||
|  | // contains fields that belong to a index
 | ||||||
|  | func validateManifest(b []byte) error { | ||||||
|  | 	var doc unknownDocument | ||||||
|  | 	if err := json.Unmarshal(b, &doc); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if doc.Manifests != nil { | ||||||
|  | 		return errors.New("ocimanifest: expected manifest but found index") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/distribution/distribution/v3" | 	"github.com/distribution/distribution/v3" | ||||||
| 	"github.com/distribution/distribution/v3/manifest" | 	"github.com/distribution/distribution/v3/manifest" | ||||||
|  | 	"github.com/distribution/distribution/v3/manifest/manifestlist" | ||||||
|  | 
 | ||||||
| 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | 	v1 "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -182,3 +184,33 @@ func TestMediaTypes(t *testing.T) { | ||||||
| 	mediaTypeTest(t, v1.MediaTypeImageManifest, false) | 	mediaTypeTest(t, v1.MediaTypeImageManifest, false) | ||||||
| 	mediaTypeTest(t, v1.MediaTypeImageManifest+"XXX", true) | 	mediaTypeTest(t, v1.MediaTypeImageManifest+"XXX", true) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestValidateManifest(t *testing.T) { | ||||||
|  | 	manifest := Manifest{ | ||||||
|  | 		Config: distribution.Descriptor{Size: 1}, | ||||||
|  | 		Layers: []distribution.Descriptor{{Size: 2}}, | ||||||
|  | 	} | ||||||
|  | 	index := manifestlist.ManifestList{ | ||||||
|  | 		Manifests: []manifestlist.ManifestDescriptor{ | ||||||
|  | 			{Descriptor: distribution.Descriptor{Size: 3}}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	t.Run("valid", func(t *testing.T) { | ||||||
|  | 		b, err := json.Marshal(manifest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error marshaling manifest", err) | ||||||
|  | 		} | ||||||
|  | 		if err := validateManifest(b); err != nil { | ||||||
|  | 			t.Error("manifest should be valid", err) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	t.Run("invalid", func(t *testing.T) { | ||||||
|  | 		b, err := json.Marshal(index) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal("unexpected error marshaling index", err) | ||||||
|  | 		} | ||||||
|  | 		if err := validateManifest(b); err == nil { | ||||||
|  | 			t.Error("index should not be valid") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue