Added a safety check to resolver

If the number of relatives exceeds 500  deep when resolving, libopenapi will now log a warning and escape that path from continuing. There is no reason on earth for a depth this large. It most likely indicates a circular reference that was ignored and then resolved.

Signed-off-by: quobix <dave@quobix.com>
This commit is contained in:
quobix
2023-12-03 09:15:19 -05:00
parent 82c9e21df1
commit afe5c1213b
3 changed files with 1444 additions and 1392 deletions

View File

@@ -310,7 +310,7 @@ func (resolver *Resolver) VisitReference(ref *Reference, seen map[string]bool, j
}
journey = append(journey, ref)
relatives := resolver.extractRelatives(ref, ref.Node, nil, seen, journey, resolve)
relatives := resolver.extractRelatives(ref, ref.Node, nil, seen, journey, resolve, 0)
seen = make(map[string]bool)
@@ -421,19 +421,33 @@ func (resolver *Resolver) isInfiniteCircularDependency(ref *Reference, visitedDe
func (resolver *Resolver) extractRelatives(ref *Reference, node, parent *yaml.Node,
foundRelatives map[string]bool,
journey []*Reference, resolve bool) []*Reference {
journey []*Reference, resolve bool, depth int) []*Reference {
if len(journey) > 100 {
return nil
}
// this is a safety check to prevent a stack overflow.
if depth > 500 {
def := "unknown"
if ref != nil {
def = ref.FullDefinition
}
if resolver.specIndex != nil && resolver.specIndex.logger != nil {
resolver.specIndex.logger.Warn("libopenapi resolver: relative depth exceeded 500 levels, "+
"check for circular references - resolving may be incomplete",
"reference", def)
}
return nil
}
var found []*Reference
if len(node.Content) > 0 {
for i, n := range node.Content {
if utils.IsNodeMap(n) || utils.IsNodeArray(n) {
found = append(found, resolver.extractRelatives(ref, n, node, foundRelatives, journey, resolve)...)
depth++
found = append(found, resolver.extractRelatives(ref, n, node, foundRelatives, journey, resolve, depth)...)
}
if i%2 == 0 && n.Value == "$ref" {

View File

@@ -1,11 +1,13 @@
package index
import (
"bytes"
"errors"
"fmt"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/utils"
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"log/slog"
"net/http"
"net/url"
"os"
@@ -432,7 +434,43 @@ func TestResolver_DeepJourney(t *testing.T) {
}
idx := NewSpecIndexWithConfig(nil, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
assert.Nil(t, resolver.extractRelatives(nil, nil, nil, nil, journey, false))
assert.Nil(t, resolver.extractRelatives(nil, nil, nil, nil, journey, false, 0))
}
func TestResolver_DeepDepth(t *testing.T) {
var refA, refB *yaml.Node
refA = &yaml.Node{
Value: "A",
Tag: "!!seq",
}
refB = &yaml.Node{
Value: "B",
Tag: "!!seq",
}
refA.Content = append(refA.Content, refB)
refB.Content = append(refB.Content, refA)
idx := NewSpecIndexWithConfig(nil, CreateClosedAPIIndexConfig())
resolver := NewResolver(idx)
// add a logger
var log []byte
buf := bytes.NewBuffer(log)
logger := slog.New(slog.NewTextHandler(buf, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
idx.logger = logger
ref := &Reference{
FullDefinition: "#/components/schemas/A",
}
found := resolver.extractRelatives(ref, refA, nil, nil, nil, false, 0)
assert.Nil(t, found)
assert.Contains(t, buf.String(), "libopenapi resolver: relative depth exceeded 500 levels")
}
func TestResolver_ResolveComponents_Stripe_NoRolodex(t *testing.T) {