github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/mediatype/parse.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package mediatype 19 20 import ( 21 "encoding/json" 22 "io" 23 "reflect" 24 "sync" 25 26 ispec "github.com/opencontainers/image-spec/specs-go/v1" 27 "github.com/pkg/errors" 28 ) 29 30 // ParseFunc is a parser that is registered for a given mediatype and called 31 // to parse a blob if it is encountered. If possible, the blob should be 32 // represented as a native Go object (with all Descriptors represented as 33 // ispec.Descriptor objects) -- this will allow umoci to recursively discover 34 // blob dependencies. 35 // 36 // Currently, we require the returned interface{} to be a raw struct 37 // (unexpected behaviour may occur otherwise). 38 // 39 // NOTE: Your ParseFunc must be able to accept a nil Reader (the error 40 // 41 // value is not relevant). This is used during registration in order to 42 // determine the type of the struct (thus you must return a struct that 43 // you would return in a non-nil reader scenario). Go doesn't have a way 44 // for us to enforce this. 45 type ParseFunc func(io.Reader) (interface{}, error) 46 47 var ( 48 lock sync.RWMutex 49 50 // parsers is a mapping of media-type to parser function. 51 parsers = map[string]ParseFunc{} 52 53 // packages is the set of package paths which have been registered. 54 packages = map[string]struct{}{} 55 56 // targets is the set of media-types which are treated as "targets" for the 57 // purposes of reference resolution (resolution terminates at these targets 58 // as well as any un-parseable blob types). 59 targets = map[string]struct{}{} 60 ) 61 62 // IsRegisteredPackage returns whether a parser which returns a type from the 63 // given package path was registered. This is only useful to allow restricting 64 // reflection recursion (as a first-pass to limit how deep reflection goes). 65 func IsRegisteredPackage(pkgPath string) bool { 66 lock.RLock() 67 _, ok := packages[pkgPath] 68 lock.RUnlock() 69 return ok 70 } 71 72 // GetParser returns the ParseFunc that was previously registered for the given 73 // media-type with RegisterParser (or nil if the media-type is unknown). 74 func GetParser(mediaType string) ParseFunc { 75 lock.RLock() 76 fn := parsers[mediaType] 77 lock.RUnlock() 78 return fn 79 } 80 81 // RegisterParser registers a new ParseFunc to be used when the given 82 // media-type is encountered during parsing or recursive walks of blobs. See 83 // the documentation of ParseFunc for more detail. 84 func RegisterParser(mediaType string, parser ParseFunc) { 85 // Get the return type so we know what packages are white-listed for 86 // recursion. #nosec G104 87 v, _ := parser(nil) 88 t := reflect.TypeOf(v) 89 90 // Register the parser and package. 91 lock.Lock() 92 _, old := parsers[mediaType] 93 parsers[mediaType] = parser 94 packages[t.PkgPath()] = struct{}{} 95 lock.Unlock() 96 97 // This should never happen, and is a programmer bug. 98 if old { 99 panic("RegisterParser() called with already-registered media-type: " + mediaType) 100 } 101 } 102 103 // IsTarget returns whether the given media-type should be treated as a "target 104 // media-type" for the purposes of reference resolution. This means that either 105 // the media-type has been registered as a target (using RegisterTarget) or has 106 // not been registered as parseable (using RegisterParser). 107 func IsTarget(mediaType string) bool { 108 lock.RLock() 109 _, isParseable := parsers[mediaType] 110 _, isTarget := targets[mediaType] 111 lock.RUnlock() 112 return isTarget || !isParseable 113 } 114 115 // RegisterTarget registers that a given *parseable* media-type (meaning that 116 // there is a parser already registered using RegisterParser) should be treated 117 // as a "target" for the purposes of reference resolution. This means that if 118 // this media-type is encountered during a reference resolution walk, a 119 // DescriptorPath to *that* blob will be returned and resolution will not 120 // recurse any deeper. All un-parseable blobs are treated as targets, so this 121 // is only useful for blobs that have also been given parsers. 122 func RegisterTarget(mediaType string) { 123 lock.Lock() 124 targets[mediaType] = struct{}{} 125 lock.Unlock() 126 } 127 128 // CustomJSONParser creates a custom ParseFunc which JSON-decodes blob data 129 // into the type of the given value (which *must* be a struct, otherwise 130 // CustomJSONParser will panic). This is intended to make ergonomic use of 131 // RegisterParser much simpler. 132 func CustomJSONParser(v interface{}) ParseFunc { 133 t := reflect.TypeOf(v) 134 // These should never happen and are programmer bugs. 135 if t == nil { 136 panic("CustomJSONParser() called with nil interface!") 137 } 138 if t.Kind() != reflect.Struct { 139 panic("CustomJSONParser() called with non-struct kind!") 140 } 141 return func(rdr io.Reader) (_ interface{}, err error) { 142 ptr := reflect.New(t) 143 if rdr != nil { 144 err = json.NewDecoder(rdr).Decode(ptr.Interface()) 145 } 146 ret := reflect.Indirect(ptr) 147 return ret.Interface(), err 148 } 149 } 150 151 func indexParser(rdr io.Reader) (interface{}, error) { 152 // Construct a fake struct which contains bad fields. CVE-2021-41190 153 var index struct { 154 ispec.Index 155 Config json.RawMessage `json:"config,omitempty"` 156 Layers json.RawMessage `json:"layers,omitempty"` 157 } 158 if rdr != nil { 159 if err := json.NewDecoder(rdr).Decode(&index); err != nil { 160 return nil, err 161 } 162 } 163 if index.MediaType != "" && index.MediaType != ispec.MediaTypeImageIndex { 164 return nil, errors.Errorf("malicious image detected: index contained incorrect mediaType: %s", index.MediaType) 165 } 166 if len(index.Config) != 0 { 167 return nil, errors.New("malicious image detected: index contained forbidden 'config' field") 168 } 169 if len(index.Layers) != 0 { 170 return nil, errors.New("malicious image detected: index contained forbidden 'layers' field") 171 } 172 return index.Index, nil 173 } 174 175 func manifestParser(rdr io.Reader) (interface{}, error) { 176 // Construct a fake struct which contains bad fields. CVE-2021-41190 177 var manifest struct { 178 ispec.Manifest 179 Manifests json.RawMessage `json:"manifests,omitempty"` 180 } 181 if rdr != nil { 182 if err := json.NewDecoder(rdr).Decode(&manifest); err != nil { 183 return nil, err 184 } 185 } 186 if manifest.MediaType != "" && manifest.MediaType != ispec.MediaTypeImageManifest { 187 return nil, errors.Errorf("malicious manifest detected: manifest contained incorrect mediaType: %s", manifest.MediaType) 188 } 189 if len(manifest.Manifests) != 0 { 190 return nil, errors.New("malicious manifest detected: manifest contained forbidden 'manifests' field") 191 } 192 return manifest.Manifest, nil 193 } 194 195 // Register the core image-spec types. 196 func init() { 197 RegisterParser(ispec.MediaTypeDescriptor, CustomJSONParser(ispec.Descriptor{})) 198 RegisterParser(ispec.MediaTypeImageIndex, indexParser) 199 RegisterParser(ispec.MediaTypeImageConfig, CustomJSONParser(ispec.Image{})) 200 201 RegisterTarget(ispec.MediaTypeImageManifest) 202 RegisterParser(ispec.MediaTypeImageManifest, manifestParser) 203 }