Skip to content

Commit 5a6f97e

Browse files
authored
openapi3: fix #796 infinite loop during document validation (#797)
1 parent fd52f34 commit 5a6f97e

File tree

4 files changed

+9355
-37
lines changed

4 files changed

+9355
-37
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ jobs:
126126
- if: runner.os == 'Linux'
127127
name: Missing validation of unknown fields in extensions
128128
run: |
129-
[[ $(git grep -InF 'return validateExtensions' -- openapi3 | wc -l) -eq $(git grep -InE '^\s+Extensions.+`' -- openapi3 | wc -l) ]]
129+
[[ $(git grep -InE 'return .*validateExtensions' -- openapi3 | wc -l) -eq $(git grep -InE '^\s+Extensions.+`' -- openapi3 | wc -l) ]]
130130
131131
- if: runner.os == 'Linux'
132132
name: Style around Extensions embedding

openapi3/issue796_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package openapi3
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestIssue796(t *testing.T) {
10+
var old int
11+
// Need to set CircularReferenceCounter to > 10
12+
old, CircularReferenceCounter = CircularReferenceCounter, 20
13+
defer func() { CircularReferenceCounter = old }()
14+
15+
loader := NewLoader()
16+
doc, err := loader.LoadFromFile("testdata/issue796.yml")
17+
require.NoError(t, err)
18+
19+
err = doc.Validate(loader.Context)
20+
require.NoError(t, err)
21+
}

openapi3/schema.go

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -847,60 +847,70 @@ func (schema *Schema) IsEmpty() bool {
847847
// Validate returns an error if Schema does not comply with the OpenAPI spec.
848848
func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) error {
849849
ctx = WithValidationOptions(ctx, opts...)
850-
return schema.validate(ctx, []*Schema{})
850+
_, err := schema.validate(ctx, []*Schema{})
851+
return err
851852
}
852853

853-
func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
854+
// returns the updated stack and an error if Schema does not comply with the OpenAPI spec.
855+
func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) {
854856
validationOpts := getValidationOptions(ctx)
855857

856858
for _, existing := range stack {
857859
if existing == schema {
858-
return nil
860+
return stack, nil
859861
}
860862
}
861863
stack = append(stack, schema)
862864

863865
if schema.ReadOnly && schema.WriteOnly {
864-
return errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true")
866+
return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true")
865867
}
866868

867869
for _, item := range schema.OneOf {
868870
v := item.Value
869871
if v == nil {
870-
return foundUnresolvedRef(item.Ref)
872+
return stack, foundUnresolvedRef(item.Ref)
871873
}
872-
if err := v.validate(ctx, stack); err != nil {
873-
return err
874+
875+
var err error
876+
if stack, err = v.validate(ctx, stack); err != nil {
877+
return stack, err
874878
}
875879
}
876880

877881
for _, item := range schema.AnyOf {
878882
v := item.Value
879883
if v == nil {
880-
return foundUnresolvedRef(item.Ref)
884+
return stack, foundUnresolvedRef(item.Ref)
881885
}
882-
if err := v.validate(ctx, stack); err != nil {
883-
return err
886+
887+
var err error
888+
if stack, err = v.validate(ctx, stack); err != nil {
889+
return stack, err
884890
}
885891
}
886892

887893
for _, item := range schema.AllOf {
888894
v := item.Value
889895
if v == nil {
890-
return foundUnresolvedRef(item.Ref)
896+
return stack, foundUnresolvedRef(item.Ref)
891897
}
892-
if err := v.validate(ctx, stack); err != nil {
893-
return err
898+
899+
var err error
900+
if stack, err = v.validate(ctx, stack); err != nil {
901+
return stack, err
894902
}
895903
}
896904

897905
if ref := schema.Not; ref != nil {
898906
v := ref.Value
899907
if v == nil {
900-
return foundUnresolvedRef(ref.Ref)
908+
return stack, foundUnresolvedRef(ref.Ref)
901909
}
902-
if err := v.validate(ctx, stack); err != nil {
903-
return err
910+
911+
var err error
912+
if stack, err = v.validate(ctx, stack); err != nil {
913+
return stack, err
904914
}
905915
}
906916

@@ -914,7 +924,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
914924
case "float", "double":
915925
default:
916926
if validationOpts.schemaFormatValidationEnabled {
917-
return unsupportedFormat(format)
927+
return stack, unsupportedFormat(format)
918928
}
919929
}
920930
}
@@ -924,7 +934,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
924934
case "int32", "int64":
925935
default:
926936
if validationOpts.schemaFormatValidationEnabled {
927-
return unsupportedFormat(format)
937+
return stack, unsupportedFormat(format)
928938
}
929939
}
930940
}
@@ -946,31 +956,33 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
946956
default:
947957
// Try to check for custom defined formats
948958
if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
949-
return unsupportedFormat(format)
959+
return stack, unsupportedFormat(format)
950960
}
951961
}
952962
}
953963
if schema.Pattern != "" && !validationOpts.schemaPatternValidationDisabled {
954964
if err := schema.compilePattern(); err != nil {
955-
return err
965+
return stack, err
956966
}
957967
}
958968
case TypeArray:
959969
if schema.Items == nil {
960-
return errors.New("when schema type is 'array', schema 'items' must be non-null")
970+
return stack, errors.New("when schema type is 'array', schema 'items' must be non-null")
961971
}
962972
case TypeObject:
963973
default:
964-
return fmt.Errorf("unsupported 'type' value %q", schemaType)
974+
return stack, fmt.Errorf("unsupported 'type' value %q", schemaType)
965975
}
966976

967977
if ref := schema.Items; ref != nil {
968978
v := ref.Value
969979
if v == nil {
970-
return foundUnresolvedRef(ref.Ref)
980+
return stack, foundUnresolvedRef(ref.Ref)
971981
}
972-
if err := v.validate(ctx, stack); err != nil {
973-
return err
982+
983+
var err error
984+
if stack, err = v.validate(ctx, stack); err != nil {
985+
return stack, err
974986
}
975987
}
976988

@@ -983,45 +995,49 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
983995
ref := schema.Properties[name]
984996
v := ref.Value
985997
if v == nil {
986-
return foundUnresolvedRef(ref.Ref)
998+
return stack, foundUnresolvedRef(ref.Ref)
987999
}
988-
if err := v.validate(ctx, stack); err != nil {
989-
return err
1000+
1001+
var err error
1002+
if stack, err = v.validate(ctx, stack); err != nil {
1003+
return stack, err
9901004
}
9911005
}
9921006

9931007
if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {
994-
return errors.New("additionalProperties are set to both boolean and schema")
1008+
return stack, errors.New("additionalProperties are set to both boolean and schema")
9951009
}
9961010
if ref := schema.AdditionalProperties.Schema; ref != nil {
9971011
v := ref.Value
9981012
if v == nil {
999-
return foundUnresolvedRef(ref.Ref)
1013+
return stack, foundUnresolvedRef(ref.Ref)
10001014
}
1001-
if err := v.validate(ctx, stack); err != nil {
1002-
return err
1015+
1016+
var err error
1017+
if stack, err = v.validate(ctx, stack); err != nil {
1018+
return stack, err
10031019
}
10041020
}
10051021

10061022
if v := schema.ExternalDocs; v != nil {
10071023
if err := v.Validate(ctx); err != nil {
1008-
return fmt.Errorf("invalid external docs: %w", err)
1024+
return stack, fmt.Errorf("invalid external docs: %w", err)
10091025
}
10101026
}
10111027

10121028
if v := schema.Default; v != nil && !validationOpts.schemaDefaultsValidationDisabled {
10131029
if err := schema.VisitJSON(v); err != nil {
1014-
return fmt.Errorf("invalid default: %w", err)
1030+
return stack, fmt.Errorf("invalid default: %w", err)
10151031
}
10161032
}
10171033

10181034
if x := schema.Example; x != nil && !validationOpts.examplesValidationDisabled {
10191035
if err := validateExampleValue(ctx, x, schema); err != nil {
1020-
return fmt.Errorf("invalid example: %w", err)
1036+
return stack, fmt.Errorf("invalid example: %w", err)
10211037
}
10221038
}
10231039

1024-
return validateExtensions(ctx, schema.Extensions)
1040+
return stack, validateExtensions(ctx, schema.Extensions)
10251041
}
10261042

10271043
func (schema *Schema) IsMatching(value interface{}) bool {

0 commit comments

Comments
 (0)