Skip to content

Commit a6ced55

Browse files
andrewpollockCharlyReux
authored andcommitted
Sanity check to prevent duplicate commits (#2127)
This adds a check for the same commit being added to more than once when converting versions to commits. #1984 surfaced that OpenSSL CVEs are being converted with the same commit as both `introduced` and `fixed` (due to incorrect normalization of OpenSSL versions). While the underlying defect is being addressed, add some sanity checking so that if the same commit hash can't be added to a GIT range being generated when converting versions to commits.
1 parent 9a2aaa8 commit a6ced55

File tree

2 files changed

+72
-26
lines changed

2 files changed

+72
-26
lines changed

vulnfeeds/cmd/nvd-cve-osv/main.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ func InScopeGitRepo(repoURL string) bool {
131131
// Examines repos and tries to convert versions to commits by treating them as Git tags.
132132
// Takes a CVE ID string (for logging), cves.VersionInfo with AffectedVersions and
133133
// typically no AffectedCommits and attempts to add AffectedCommits (including Fixed commits) where there aren't any.
134+
// Refuses to add the same commit to AffectedCommits more than once.
134135
func GitVersionsToCommits(CVE cves.CVEID, versions cves.VersionInfo, repos []string, cache git.RepoTagsCache) (v cves.VersionInfo, e error) {
135136
// versions is a VersionInfo with AffectedVersions and typically no AffectedCommits
136137
// v is a VersionInfo with AffectedCommits (containing Fixed commits) included
@@ -148,7 +149,11 @@ func GitVersionsToCommits(CVE cves.CVEID, versions cves.VersionInfo, repos []str
148149
Logger.Warnf("[%s]: Failed to get a Git commit for introduced version %q from %q: %v", CVE, av.Introduced, repo, err)
149150
} else {
150151
Logger.Infof("[%s]: Successfully derived %+v for introduced version %q", CVE, ac, av.Introduced)
151-
v.AffectedCommits = append(v.AffectedCommits, ac)
152+
if v.Duplicated(ac) {
153+
Logger.Warnf("[%s]: Duplicate: %#v already present in %#v", CVE, ac, v)
154+
} else {
155+
v.AffectedCommits = append(v.AffectedCommits, ac)
156+
}
152157
}
153158
}
154159
// Only try and convert fixed versions to commits via tags if there aren't any Fixed commits already.
@@ -163,7 +168,11 @@ func GitVersionsToCommits(CVE cves.CVEID, versions cves.VersionInfo, repos []str
163168
Logger.Warnf("[%s]: Failed to get a Git commit for fixed version %q from %q: %v", CVE, av.Fixed, repo, err)
164169
} else {
165170
Logger.Infof("[%s]: Successfully derived %+v for fixed version %q", CVE, ac, av.Fixed)
166-
v.AffectedCommits = append(v.AffectedCommits, ac)
171+
if v.Duplicated(ac) {
172+
Logger.Warnf("[%s]: Duplicate: %#v already present in %#v", CVE, ac, v)
173+
} else {
174+
v.AffectedCommits = append(v.AffectedCommits, ac)
175+
}
167176
}
168177
}
169178
// Only try and convert last_affected versions to commits via tags if there aren't any Fixed commits already (to maintain schema compliance).
@@ -175,7 +184,11 @@ func GitVersionsToCommits(CVE cves.CVEID, versions cves.VersionInfo, repos []str
175184
Logger.Warnf("[%s]: Failed to get a Git commit for last_affected version %q from %q: %v", CVE, av.LastAffected, repo, err)
176185
} else {
177186
Logger.Infof("[%s]: Successfully derived %+v for last_affected version %q", CVE, ac, av.LastAffected)
178-
v.AffectedCommits = append(v.AffectedCommits, ac)
187+
if v.Duplicated(ac) {
188+
Logger.Warnf("[%s]: Duplicate: %#v already present in %#v", CVE, ac, v)
189+
} else {
190+
v.AffectedCommits = append(v.AffectedCommits, ac)
191+
}
179192
}
180193
}
181194
}

vulnfeeds/cves/versions.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"net/http"
2222
"net/url"
2323
"path"
24+
"reflect"
2425
"regexp"
2526
"strings"
2627
"time"
@@ -88,50 +89,82 @@ func (vi *VersionInfo) HasLastAffectedVersions() bool {
8889
}
8990

9091
func (vi *VersionInfo) HasIntroducedCommits(repo string) bool {
91-
for _, av := range vi.AffectedCommits {
92-
if av.Repo == repo && av.Introduced != "" {
92+
for _, ac := range vi.AffectedCommits {
93+
if ac.Repo == repo && ac.Introduced != "" {
9394
return true
9495
}
9596
}
9697
return false
9798
}
9899

99100
func (vi *VersionInfo) HasFixedCommits(repo string) bool {
100-
for _, av := range vi.AffectedCommits {
101-
if av.Repo == repo && av.Fixed != "" {
101+
for _, ac := range vi.AffectedCommits {
102+
if ac.Repo == repo && ac.Fixed != "" {
102103
return true
103104
}
104105
}
105106
return false
106107
}
107108

108109
func (vi *VersionInfo) HasLastAffectedCommits(repo string) bool {
109-
for _, av := range vi.AffectedCommits {
110-
if av.Repo == repo && av.LastAffected != "" {
110+
for _, ac := range vi.AffectedCommits {
111+
if ac.Repo == repo && ac.LastAffected != "" {
111112
return true
112113
}
113114
}
114115
return false
115116
}
116117

117118
func (vi *VersionInfo) FixedCommits(repo string) (FixedCommits []string) {
118-
for _, av := range vi.AffectedCommits {
119-
if av.Repo == repo && av.Fixed != "" {
120-
FixedCommits = append(FixedCommits, av.Fixed)
119+
for _, ac := range vi.AffectedCommits {
120+
if ac.Repo == repo && ac.Fixed != "" {
121+
FixedCommits = append(FixedCommits, ac.Fixed)
121122
}
122123
}
123124
return FixedCommits
124125
}
125126

126127
func (vi *VersionInfo) LastAffectedCommits(repo string) (LastAffectedCommits []string) {
127-
for _, av := range vi.AffectedCommits {
128-
if av.Repo == repo && av.LastAffected != "" {
129-
LastAffectedCommits = append(LastAffectedCommits, av.Fixed)
128+
for _, ac := range vi.AffectedCommits {
129+
if ac.Repo == repo && ac.LastAffected != "" {
130+
LastAffectedCommits = append(LastAffectedCommits, ac.Fixed)
130131
}
131132
}
132133
return LastAffectedCommits
133134
}
134135

136+
// Check if the same commit appears in multiple fields of the AffectedCommits array.
137+
// See https://github.com/google/osv.dev/issues/1984 for more context.
138+
func (vi *VersionInfo) Duplicated(candidate AffectedCommit) bool {
139+
fieldsToCheck := []string{"Introduced", "LastAffected", "Limit", "Fixed"}
140+
141+
// Get the commit hash to look for.
142+
v := reflect.ValueOf(&candidate).Elem()
143+
144+
commit := ""
145+
for _, field := range fieldsToCheck {
146+
commit = v.FieldByName(field).String()
147+
if commit != "" {
148+
break
149+
}
150+
}
151+
if commit == "" {
152+
return false
153+
}
154+
155+
// Look through what is already present.
156+
for _, ac := range vi.AffectedCommits {
157+
v = reflect.ValueOf(&ac).Elem()
158+
for _, field := range fieldsToCheck {
159+
existingCommit := v.FieldByName(field).String()
160+
if existingCommit == commit {
161+
return true
162+
}
163+
}
164+
}
165+
return false
166+
}
167+
135168
// Synthetic enum of supported commit types.
136169
type CommitType int
137170

@@ -492,18 +525,18 @@ var (
492525
)
493526

494527
func repoGitWeb(parsedURL *url.URL) (string, error) {
495-
params := strings.Split(parsedURL.RawQuery, ";")
496-
for _, param := range params {
497-
if !strings.HasPrefix(param, "p=") {
498-
continue
499-
}
500-
repo, err := url.JoinPath(strings.TrimSuffix(strings.TrimSuffix(parsedURL.Path, "/gitweb.cgi"), "cgi-bin"), strings.Split(param, "=")[1])
501-
if err != nil {
502-
return "", err
503-
}
504-
return fmt.Sprintf("git://%s%s", parsedURL.Hostname(), repo), nil
528+
params := strings.Split(parsedURL.RawQuery, ";")
529+
for _, param := range params {
530+
if !strings.HasPrefix(param, "p=") {
531+
continue
505532
}
506-
return "", fmt.Errorf("unsupported GitWeb URL: %s", parsedURL.String())
533+
repo, err := url.JoinPath(strings.TrimSuffix(strings.TrimSuffix(parsedURL.Path, "/gitweb.cgi"), "cgi-bin"), strings.Split(param, "=")[1])
534+
if err != nil {
535+
return "", err
536+
}
537+
return fmt.Sprintf("git://%s%s", parsedURL.Hostname(), repo), nil
538+
}
539+
return "", fmt.Errorf("unsupported GitWeb URL: %s", parsedURL.String())
507540
}
508541

509542
// Returns the base repository URL for supported repository hosts.

0 commit comments

Comments
 (0)