Merge pull request #23185 from mheon/fix_cve_2024_37298_441crio

[v4.4.1-crio] Update gorilla/schema to v1.4.1 to fix CVE-2024-37298
This commit is contained in:
openshift-merge-bot[bot] 2024-07-18 18:15:56 +00:00 committed by GitHub
commit 41b14a68d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 307 additions and 24 deletions

2
go.mod
View file

@ -34,7 +34,7 @@ require (
github.com/google/uuid v1.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/gorilla/schema v1.4.1
github.com/hashicorp/go-multierror v1.1.1
github.com/json-iterator/go v1.1.12
github.com/mattn/go-shellwords v1.0.12

4
go.sum
View file

@ -625,8 +625,8 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

20
vendor/github.com/gorilla/schema/.editorconfig generated vendored Normal file
View file

@ -0,0 +1,20 @@
; https://editorconfig.org/
root = true
[*]
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
indent_style = tab
indent_size = 4
[*.md]
indent_size = 4
trim_trailing_whitespace = false
eclint_indent_style = unset

1
vendor/github.com/gorilla/schema/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
coverage.coverprofile

View file

@ -1,4 +1,4 @@
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
Copyright (c) 2023 The Gorilla Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are

34
vendor/github.com/gorilla/schema/Makefile generated vendored Normal file
View file

@ -0,0 +1,34 @@
GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
GO_SEC=$(shell which gosec 2> /dev/null || echo '')
GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
.PHONY: golangci-lint
golangci-lint:
$(if $(GO_LINT), ,go install $(GO_LINT_URI))
@echo "##### Running golangci-lint"
golangci-lint run -v
.PHONY: gosec
gosec:
$(if $(GO_SEC), ,go install $(GO_SEC_URI))
@echo "##### Running gosec"
gosec ./...
.PHONY: govulncheck
govulncheck:
$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
@echo "##### Running govulncheck"
govulncheck ./...
.PHONY: verify
verify: golangci-lint gosec govulncheck
.PHONY: test
test:
@echo "##### Running tests"
go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...

View file

@ -1,8 +1,12 @@
schema
======
[![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) [![Build Status](https://travis-ci.org/gorilla/schema.png?branch=master)](https://travis-ci.org/gorilla/schema)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
# gorilla/schema
![testing](https://github.com/gorilla/schema/actions/workflows/test.yml/badge.svg)
[![codecov](https://codecov.io/github/gorilla/schema/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/schema)
[![godoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema)
[![sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)
Package gorilla/schema converts structs to and from form values.
@ -83,7 +87,32 @@ The supported field types in the struct are:
Unsupported types are simply ignored, however custom types can be registered to be converted.
More examples are available on the Gorilla website: https://www.gorillatoolkit.org/pkg/schema
## Setting Defaults
It is possible to set default values when encoding/decoding by using the `default` tag option. The value of `default` is applied when a field has a zero value, a pointer has a nil value, or a slice is empty.
```go
type Person struct {
Phone string `schema:"phone,default:+123456"` // custom name
Age int `schema:"age,default:21"`
Admin bool `schema:"admin,default:false"`
Balance float64 `schema:"balance,default:10.0"`
Friends []string `schema:friends,default:john|bob`
}
```
The `default` tag option is supported for the following types:
* bool
* float variants (float32, float64)
* int variants (int, int8, int16, int32, int64)
* uint variants (uint, uint8, uint16, uint32, uint64)
* string
* a slice of the above types. As shown in the example above, `|` should be used to separate between slice items.
* a pointer to one of the above types (pointer to slice and slice of pointers are not supported).
> [!NOTE]
> Because primitive types like int, float, bool, unint and their variants have their default (or zero) values set by Golang, it is not possible to distinguish them from a provided value when decoding/encoding form values. In this case, the value provided by the `default` option tag will be always applied. For example, let's assume that the value submitted in the form for `balance` is `0.0` then the default of `10.0` will be applied, even if `0.0` is part of the form data for the `balance` field. In such cases, it is highly recommended to use pointers to allow schema to distinguish between when a form field has no provided value and when a form has a value equal to the corresponding default set by Golang for a particular type. If the type of the `Balance` field above is changed to `*float64`, then the zero value would be `nil`. In this case, if the form data value for `balance` is `0.0`, then the default will not be applied.
## License

View file

@ -12,7 +12,7 @@ import (
"sync"
)
var invalidPath = errors.New("schema: invalid path")
var errInvalidPath = errors.New("schema: invalid path")
// newCache returns a new cache.
func newCache() *cache {
@ -53,13 +53,13 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
keys := strings.Split(p, ".")
for i := 0; i < len(keys); i++ {
if t.Kind() != reflect.Struct {
return nil, invalidPath
return nil, errInvalidPath
}
if struc = c.get(t); struc == nil {
return nil, invalidPath
return nil, errInvalidPath
}
if field = struc.get(keys[i]); field == nil {
return nil, invalidPath
return nil, errInvalidPath
}
// Valid field. Append index.
path = append(path, field.name)
@ -72,10 +72,10 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
// So checking i+2 is not necessary anymore.
i++
if i+1 > len(keys) {
return nil, invalidPath
return nil, errInvalidPath
}
if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
return nil, invalidPath
return nil, errInvalidPath
}
parts = append(parts, pathPart{
path: path,
@ -197,6 +197,7 @@ func (c *cache) createField(field reflect.StructField, parentAlias string) *fiel
isSliceOfStructs: isSlice && isStruct,
isAnonymous: field.Anonymous,
isRequired: options.Contains("required"),
defaultValue: options.getDefaultOptionValue(),
}
}
@ -246,8 +247,9 @@ type fieldInfo struct {
// isSliceOfStructs indicates if the field type is a slice of structs.
isSliceOfStructs bool
// isAnonymous indicates whether the field is embedded in the struct.
isAnonymous bool
isRequired bool
isAnonymous bool
isRequired bool
defaultValue string
}
func (f *fieldInfo) paths(prefix string) []string {
@ -303,3 +305,13 @@ func (o tagOptions) Contains(option string) bool {
}
return false
}
func (o tagOptions) getDefaultOptionValue() string {
for _, s := range o {
if strings.HasPrefix(s, "default:") {
return strings.Split(s, ":")[1]
}
}
return ""
}

View file

@ -143,3 +143,80 @@ func convertUint64(value string) reflect.Value {
}
return invalidValue
}
func convertPointer(k reflect.Kind, value string) reflect.Value {
switch k {
case boolType:
if v := convertBool(value); v.IsValid() {
converted := v.Bool()
return reflect.ValueOf(&converted)
}
case float32Type:
if v := convertFloat32(value); v.IsValid() {
converted := float32(v.Float())
return reflect.ValueOf(&converted)
}
case float64Type:
if v := convertFloat64(value); v.IsValid() {
converted := float64(v.Float())
return reflect.ValueOf(&converted)
}
case intType:
if v := convertInt(value); v.IsValid() {
converted := int(v.Int())
return reflect.ValueOf(&converted)
}
case int8Type:
if v := convertInt8(value); v.IsValid() {
converted := int8(v.Int())
return reflect.ValueOf(&converted)
}
case int16Type:
if v := convertInt16(value); v.IsValid() {
converted := int16(v.Int())
return reflect.ValueOf(&converted)
}
case int32Type:
if v := convertInt32(value); v.IsValid() {
converted := int32(v.Int())
return reflect.ValueOf(&converted)
}
case int64Type:
if v := convertInt64(value); v.IsValid() {
converted := int64(v.Int())
return reflect.ValueOf(&converted)
}
case stringType:
if v := convertString(value); v.IsValid() {
converted := v.String()
return reflect.ValueOf(&converted)
}
case uintType:
if v := convertUint(value); v.IsValid() {
converted := uint(v.Uint())
return reflect.ValueOf(&converted)
}
case uint8Type:
if v := convertUint8(value); v.IsValid() {
converted := uint8(v.Uint())
return reflect.ValueOf(&converted)
}
case uint16Type:
if v := convertUint16(value); v.IsValid() {
converted := uint16(v.Uint())
return reflect.ValueOf(&converted)
}
case uint32Type:
if v := convertUint32(value); v.IsValid() {
converted := uint32(v.Uint())
return reflect.ValueOf(&converted)
}
case uint64Type:
if v := convertUint64(value); v.IsValid() {
converted := uint64(v.Uint())
return reflect.ValueOf(&converted)
}
}
return invalidValue
}

View file

@ -12,9 +12,13 @@ import (
"strings"
)
const (
defaultMaxSize = 16000
)
// NewDecoder returns a new Decoder.
func NewDecoder() *Decoder {
return &Decoder{cache: newCache()}
return &Decoder{cache: newCache(), maxSize: defaultMaxSize}
}
// Decoder decodes values from a map[string][]string to a struct.
@ -22,6 +26,7 @@ type Decoder struct {
cache *cache
zeroEmpty bool
ignoreUnknownKeys bool
maxSize int
}
// SetAliasTag changes the tag used to locate custom field aliases.
@ -54,6 +59,13 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) {
d.ignoreUnknownKeys = i
}
// MaxSize limits the size of slices for URL nested arrays or object arrays.
// Choose MaxSize carefully; large values may create many zero-value slice elements.
// Example: "items.100000=apple" would create a slice with 100,000 empty strings.
func (d *Decoder) MaxSize(size int) {
d.maxSize = size
}
// RegisterConverter registers a converter function for a custom type.
func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
d.cache.registerConverter(value, converterFunc)
@ -84,6 +96,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
errors[path] = UnknownKeyError{Key: path}
}
}
errors.merge(d.setDefaults(t, v))
errors.merge(d.checkRequired(t, src))
if len(errors) > 0 {
return errors
@ -91,6 +104,88 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
return nil
}
// setDefaults sets the default values when the `default` tag is specified,
// default is supported on basic/primitive types and their pointers,
// nested structs can also have default tags
func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError {
struc := d.cache.get(t)
if struc == nil {
// unexpect, cache.get never return nil
return MultiError{"default-" + t.Name(): errors.New("cache fail")}
}
errs := MultiError{}
if v.Type().Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous {
field.Set(reflect.New(field.Type().Elem()))
}
}
}
for _, f := range struc.fields {
vCurrent := v.FieldByName(f.name)
if vCurrent.Type().Kind() == reflect.Struct && f.defaultValue == "" {
errs.merge(d.setDefaults(vCurrent.Type(), vCurrent))
} else if isPointerToStruct(vCurrent) && f.defaultValue == "" {
errs.merge(d.setDefaults(vCurrent.Elem().Type(), vCurrent.Elem()))
}
if f.defaultValue != "" && f.isRequired {
errs.merge(MultiError{"default-" + f.name: errors.New("required fields cannot have a default value")})
} else if f.defaultValue != "" && vCurrent.IsZero() && !f.isRequired {
if f.typ.Kind() == reflect.Struct {
errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
} else if f.typ.Kind() == reflect.Slice {
vals := strings.Split(f.defaultValue, "|")
// check if slice has one of the supported types for defaults
if _, ok := builtinConverters[f.typ.Elem().Kind()]; !ok {
errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
continue
}
defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals))
for _, val := range vals {
// this check is to handle if the wrong value is provided
convertedVal := builtinConverters[f.typ.Elem().Kind()](val)
if !convertedVal.IsValid() {
errs.merge(MultiError{"default-" + f.name: fmt.Errorf("failed setting default: %s is not compatible with field %s type", val, f.name)})
break
}
defaultSlice = reflect.Append(defaultSlice, convertedVal)
}
vCurrent.Set(defaultSlice)
} else if f.typ.Kind() == reflect.Ptr {
t1 := f.typ.Elem()
if t1.Kind() == reflect.Struct || t1.Kind() == reflect.Slice {
errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
}
// this check is to handle if the wrong value is provided
if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() {
vCurrent.Set(convertedVal)
}
} else {
// this check is to handle if the wrong value is provided
if convertedVal := builtinConverters[f.typ.Kind()](f.defaultValue); convertedVal.IsValid() {
vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue))
}
}
}
}
return errs
}
func isPointerToStruct(v reflect.Value) bool {
return !v.IsZero() && v.Type().Kind() == reflect.Ptr && v.Elem().Type().Kind() == reflect.Struct
}
// checkRequired checks whether required fields are empty
//
// check type t recursively if t has struct fields.
@ -193,7 +288,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
if v.Type().Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous == true {
if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous {
field.Set(reflect.New(field.Type().Elem()))
}
}
@ -219,6 +314,10 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
// Slice of structs. Let's go recursive.
if len(parts) > 1 {
idx := parts[0].index
// a defensive check to avoid creating a large slice based on user input index
if idx > d.maxSize {
return fmt.Errorf("%v index %d is larger than the configured maxSize %d", v.Kind(), idx, d.maxSize)
}
if v.IsNil() || v.Len() < idx+1 {
value := reflect.MakeSlice(t, idx+1, idx+1)
if v.Len() < idx+1 {

View file

@ -93,8 +93,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
}
// Encode struct pointer types if the field is a valid pointer and a struct.
if isValidStructPointer(v.Field(i)) {
e.encode(v.Field(i).Elem(), dst)
if isValidStructPointer(v.Field(i)) && !e.hasCustomEncoder(v.Field(i).Type()) {
err := e.encode(v.Field(i).Elem(), dst)
if err != nil {
errors[v.Field(i).Elem().Type().String()] = err
}
continue
}
@ -112,7 +115,10 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
}
if v.Field(i).Type().Kind() == reflect.Struct {
e.encode(v.Field(i), dst)
err := e.encode(v.Field(i), dst)
if err != nil {
errors[v.Field(i).Type().String()] = err
}
continue
}
@ -142,6 +148,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
return nil
}
func (e *Encoder) hasCustomEncoder(t reflect.Type) bool {
_, exists := e.regenc[t]
return exists
}
func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc {
if f, ok := reg[t]; ok {
return f

4
vendor/modules.txt vendored
View file

@ -555,8 +555,8 @@ github.com/gorilla/handlers
# github.com/gorilla/mux v1.8.0
## explicit; go 1.12
github.com/gorilla/mux
# github.com/gorilla/schema v1.2.0
## explicit
# github.com/gorilla/schema v1.4.1
## explicit; go 1.20
github.com/gorilla/schema
# github.com/hashicorp/errwrap v1.1.0
## explicit