From 45b34b7c9642c0c41ae96a25c38badc2d79ec8bc Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Mon, 3 Aug 2026 11:17:24 +0900 Subject: [PATCH] ovalutil: guard a test element with no object reference Both DefsToVulns loops read ObjectRef()[0] with no length check, while the StateRef() right below is guarded by len(stateRefs) > 0. The object reference is required by the OVAL schema but the parser does not enforce it, so a feed carrying an rpminfo_test or dpkginfo_test without an child panics the updater with index out of range [0] with length 0 Skip the criterion instead, the same way an object lookup failure is already handled. Signed-off-by: Arpit Jain --- pkg/ovalutil/dpkg.go | 5 +++ pkg/ovalutil/objectref_test.go | 61 ++++++++++++++++++++++++++++++++++ pkg/ovalutil/rpm.go | 5 +++ 3 files changed, 71 insertions(+) create mode 100644 pkg/ovalutil/objectref_test.go diff --git a/pkg/ovalutil/dpkg.go b/pkg/ovalutil/dpkg.go index 89f7bd0e3..37d809d09 100644 --- a/pkg/ovalutil/dpkg.go +++ b/pkg/ovalutil/dpkg.go @@ -64,6 +64,11 @@ func DpkgDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulns // // thus we *should* only need to care about a single dpkginfo_object and optionally a state object providing the package's fixed-in version. + if len(objRefs) == 0 { + stats.Obj++ + continue + } + objRef := objRefs[0].ObjectRef object, err := dpkgObjectLookup(root, objRef) switch { diff --git a/pkg/ovalutil/objectref_test.go b/pkg/ovalutil/objectref_test.go new file mode 100644 index 000000000..c28968706 --- /dev/null +++ b/pkg/ovalutil/objectref_test.go @@ -0,0 +1,61 @@ +package ovalutil + +import ( + "context" + "encoding/xml" + "fmt" + "testing" + + "github.com/quay/claircore" + "github.com/quay/goval-parser/oval" +) + +// A test element is only required to carry an object reference; a feed that +// omits it still parses. +const missingObjectRef = ` + + + example + + + + + + + <%s id="oval:com.example:tst:1" check="at least one" comment="package is installed"/> + +` + +func protoVuln(oval.Definition) ([]*claircore.Vulnerability, error) { + return []*claircore.Vulnerability{{Name: "example"}}, nil +} + +func TestDefsToVulnsWithoutObjectRef(t *testing.T) { + for _, kind := range []string{"rpminfo_test", "dpkginfo_test"} { + t.Run(kind, func(t *testing.T) { + root := &oval.Root{} + if err := xml.Unmarshal([]byte(fmt.Sprintf(missingObjectRef, kind)), root); err != nil { + t.Fatalf("parsing the oval document: %v", err) + } + + var ( + vulns []*claircore.Vulnerability + err error + ) + switch kind { + case "rpminfo_test": + vulns, err = RPMDefsToVulns(context.Background(), root, protoVuln) + case "dpkginfo_test": + vulns, err = DpkgDefsToVulns(context.Background(), root, protoVuln, func(_ oval.Definition, name *oval.DpkgName) []string { + return []string{name.Body} + }) + } + if err != nil { + t.Fatalf("got an error: %v", err) + } + if len(vulns) != 0 { + t.Errorf("expected the criterion to be skipped, got %d vulnerabilities", len(vulns)) + } + }) + } +} diff --git a/pkg/ovalutil/rpm.go b/pkg/ovalutil/rpm.go index df876f79b..99ac4c426 100644 --- a/pkg/ovalutil/rpm.go +++ b/pkg/ovalutil/rpm.go @@ -89,6 +89,11 @@ func RPMDefsToVulns(ctx context.Context, root *oval.Root, protoVulns ProtoVulnsF // // thus we *should* only need to care about a single rpminfo_object and optionally a state object providing the package's fixed-in version. + if len(objRefs) == 0 { + slog.DebugContext(ctx, "test has no object reference, moving to next criterion", "test_ref", criterion.TestRef) + continue + } + objRef := objRefs[0].ObjectRef object, err := rpmObjectLookup(root, objRef) switch {