internal/report: disallow overlapping version ranges

When a vulnerability is fixed in (for example) v1.1.1, v1.2.1, and
v1.3.0, OSV describes the vulnerability as applying to the ranges
[0,1.1.1) and [1.2.0,1.2.1). (1.3.0 is not listed, because it comes
after the fix in 1.2.1.)

Check for overlapping ranges in "vulnreport lint". Fix existing
reports with overlapping ranges.

Fixes golang/go#52855.

Change-Id: Ib285a205f5ce7e02485e9fa4763051f053e85000
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/405575
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Damien Neil <dneil@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/internal/report/lint.go b/internal/report/lint.go
index 0e7d068..de5cbe1 100644
--- a/internal/report/lint.go
+++ b/internal/report/lint.go
@@ -181,6 +181,7 @@
 		addPkgIssue := func(iss string) {
 			issues = append(issues, fmt.Sprintf("packages[%v]: %v", i, iss))
 		}
+		versions := p.Versions
 		if !stdlib.Contains(p.Module) {
 			if p.Module == "" {
 				addPkgIssue("missing module")
@@ -207,10 +208,10 @@
 				}
 			}
 			for _, v := range p.Versions {
-				if v.Introduced != "" && !strings.HasPrefix(v.Introduced, "v") {
+				if v.Introduced != "" && !semver.IsValid(v.Introduced) {
 					addPkgIssue(fmt.Sprintf("invalid semantic version: %q", v.Introduced))
 				}
-				if v.Fixed != "" && !strings.HasPrefix(v.Fixed, "v") {
+				if v.Fixed != "" && !semver.IsValid(v.Fixed) {
 					addPkgIssue(fmt.Sprintf("invalid semantic version: %q", v.Fixed))
 				}
 			}
@@ -218,13 +219,31 @@
 			if p.Package == "" {
 				addPkgIssue("missing package")
 			}
+			versions = nil // replace with actual semver versions
 			for _, v := range p.Versions {
-				if v.Introduced != "" && !strings.HasPrefix(v.Introduced, "go") {
+				introduced := "v" + strings.TrimPrefix(v.Introduced, "go")
+				fixed := "v" + strings.TrimPrefix(v.Fixed, "go")
+				if v.Introduced != "" && (!strings.HasPrefix(v.Introduced, "go") || !semver.IsValid(introduced)) {
 					addPkgIssue(fmt.Sprintf("invalid Go version: %q", v.Introduced))
 				}
-				if v.Fixed != "" && !strings.HasPrefix(v.Fixed, "go") {
+				if v.Fixed != "" && (!strings.HasPrefix(v.Fixed, "go") || !semver.IsValid(fixed)) {
 					addPkgIssue(fmt.Sprintf("invalid Go version: %q", v.Fixed))
 				}
+				versions = append(versions, VersionRange{
+					Introduced: introduced,
+					Fixed:      fixed,
+				})
+			}
+		}
+		for i, v1 := range versions {
+			if v1.Fixed != "" && semver.Compare(v1.Introduced, v1.Fixed) >= 0 {
+				addPkgIssue(fmt.Sprintf("version %q >= %q", p.Versions[i].Introduced, p.Versions[i].Fixed))
+				continue
+			}
+			for j, v2 := range versions[:i] {
+				if semver.Compare(v1.Fixed, v2.Introduced) > 0 && semver.Compare(v1.Introduced, v2.Fixed) < 0 {
+					addPkgIssue(fmt.Sprintf("version ranges overlap: [%v,%v), [%v,%v)", p.Versions[i].Introduced, p.Versions[i].Fixed, p.Versions[j].Introduced, p.Versions[j].Fixed))
+				}
 			}
 		}
 	}
diff --git a/internal/report/lint_test.go b/internal/report/lint_test.go
new file mode 100644
index 0000000..8f045c2
--- /dev/null
+++ b/internal/report/lint_test.go
@@ -0,0 +1,71 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package report
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+)
+
+func TestLint(t *testing.T) {
+	for _, test := range []struct {
+		report Report
+		want   []string
+	}{{
+		report: Report{
+			Packages: []Package{{
+				Module:  "std",
+				Package: "time",
+				Versions: []VersionRange{{
+					Fixed: "go1.2.1",
+				}, {
+					Fixed: "go1.3.2",
+				}},
+			}},
+		},
+		want: []string{"version ranges overlap"},
+	}, {
+		report: Report{
+			Packages: []Package{{
+				Module:  "std",
+				Package: "time",
+				Versions: []VersionRange{{
+					Introduced: "go1.3",
+					Fixed:      "go1.2.1",
+				}},
+			}},
+		},
+		want: []string{`version "go1.3" >= "go1.2.1"`},
+	}} {
+		got := test.report.Lint()
+		var missing []string
+		for _, w := range test.want {
+			found := false
+			for _, g := range got {
+				if strings.Contains(g, w) {
+					found = true
+					continue
+				}
+			}
+			if !found {
+				missing = append(missing, w)
+			}
+		}
+		if len(missing) > 0 {
+			var buf bytes.Buffer
+			if err := test.report.encode(&buf); err != nil {
+				t.Error(err)
+			}
+			t.Errorf("missing expected lint warnings in report:\n%v", buf.String())
+			for _, g := range got {
+				t.Errorf("  got:  %v", g)
+			}
+			for _, w := range missing {
+				t.Errorf("  want: %v", w)
+			}
+		}
+	}
+}
diff --git a/reports/GO-2021-0068.yaml b/reports/GO-2021-0068.yaml
index 5213275..e0632c7 100644
--- a/reports/GO-2021-0068.yaml
+++ b/reports/GO-2021-0068.yaml
@@ -4,7 +4,8 @@
     package: cmd/go
     versions:
       - fixed: go1.14.14
-      - fixed: go1.15.7
+      - introduced: go1.15.0
+        fixed: go1.15.7
 description: |
     The go command may execute arbitrary code at build time when using cgo on Windows.
     This can be triggered by running go get on a malicious module, or any other time
diff --git a/reports/GO-2021-0140.yaml b/reports/GO-2021-0140.yaml
index 77bbfe2..0c31df1 100644
--- a/reports/GO-2021-0140.yaml
+++ b/reports/GO-2021-0140.yaml
@@ -5,7 +5,8 @@
       - Certificate.Verify
     versions:
       - fixed: go1.13.13
-      - fixed: go1.14.5
+      - introduced: go1.14.0
+        fixed: go1.14.5
 description: |
     X509 Certificate verification does not validate KeyUsages EKU
     requirements on Windows if VerifyOptions.Roots is nil.
diff --git a/reports/GO-2021-0141.yaml b/reports/GO-2021-0141.yaml
index b238aaa..b4829bd 100644
--- a/reports/GO-2021-0141.yaml
+++ b/reports/GO-2021-0141.yaml
@@ -5,7 +5,8 @@
       - expectContinueReader.Read
     versions:
       - fixed: go1.13.13
-      - fixed: go1.14.5
+      - introduced: go1.14.0
+        fixed: go1.14.5
 description: |
     A Go HTTP server which reads from the request body while
     simultaneously writing a response can panic when clients
diff --git a/reports/GO-2021-0143.yaml b/reports/GO-2021-0143.yaml
index a664c95..12875ac 100644
--- a/reports/GO-2021-0143.yaml
+++ b/reports/GO-2021-0143.yaml
@@ -5,16 +5,16 @@
       - response.Write
     versions:
       - fixed: go1.14.8
-      - fixed: go1.15.1
-      - fixed: go1.16.0
+      - introduced: go1.15.0
+        fixed: go1.15.1
   - module: std
     package: net/http/fcgi
     symbols:
       - response.Write
     versions:
       - fixed: go1.14.8
-      - fixed: go1.15.1
-      - fixed: go1.16.0
+      - introduced: go1.15.0
+        fixed: go1.15.1
 description: |
     When a Handler does not explicitly set the Content-Type header,
     the net/http/cgi and net/http/fcgi packages default to "text/html",
diff --git a/reports/GO-2021-0163.yaml b/reports/GO-2021-0163.yaml
index a6b9cef..cbbf883 100644
--- a/reports/GO-2021-0163.yaml
+++ b/reports/GO-2021-0163.yaml
@@ -5,7 +5,8 @@
       - LoadLibrary
     versions:
       - fixed: go1.5.4
-      - fixed: go1.6.1
+      - introduced: go1.6.0
+        fixed: go1.6.1
 description: |
     Untrusted search path vulnerability on Windows related to LoadLibrary allows
     local users to gain privileges via a malicious DLL in the current working
diff --git a/reports/GO-2021-0172.yaml b/reports/GO-2021-0172.yaml
index d130765..f2538ba 100644
--- a/reports/GO-2021-0172.yaml
+++ b/reports/GO-2021-0172.yaml
@@ -5,8 +5,8 @@
       - Reader.readForm
     versions:
       - fixed: go1.6.4
-      - fixed: go1.7.4
-      - fixed: go1.8.0
+      - introduced: go1.7.0
+        fixed: go1.7.4
 description: |
     When parsing large multipart/form-data, an attacker can
     cause a HTTP server to open a large number of file
diff --git a/reports/GO-2021-0178.yaml b/reports/GO-2021-0178.yaml
index 40bd88e..b373d72 100644
--- a/reports/GO-2021-0178.yaml
+++ b/reports/GO-2021-0178.yaml
@@ -6,7 +6,7 @@
     versions:
       - introduced: go1.1
         fixed: go1.8.4
-      - introduced: go1.1
+      - introduced: go1.9.0
         fixed: go1.9.1
 description: |
     SMTP clients using net/smtp can use the PLAIN authentication scheme on
diff --git a/reports/GO-2021-0223.yaml b/reports/GO-2021-0223.yaml
index e4d842d..903ee5d 100644
--- a/reports/GO-2021-0223.yaml
+++ b/reports/GO-2021-0223.yaml
@@ -5,8 +5,8 @@
       - Certificate.systemVerify
     versions:
       - fixed: go1.13.13
-      - fixed: go1.14.5
-      - fixed: go1.15.0
+      - introduced: go1.14.0
+        fixed: go1.14.5
 description: |
     On Windows, if VerifyOptions.Roots is nil, Certificate.Verify
     does not check the EKU requirements specified in VerifyOptions.KeyUsages.
diff --git a/reports/GO-2021-0224.yaml b/reports/GO-2021-0224.yaml
index 0774836..8e5bd21 100644
--- a/reports/GO-2021-0224.yaml
+++ b/reports/GO-2021-0224.yaml
@@ -5,8 +5,8 @@
       - expectContinueReader.Read
     versions:
       - fixed: go1.13.13
-      - fixed: go1.14.5
-      - fixed: go1.15.0
+      - introduced: go1.14.0
+        fixed: go1.14.5
 description: |
     HTTP servers where the Handler concurrently reads the request
     body and writes a response can encounter a data race and crash.
diff --git a/reports/GO-2021-0225.yaml b/reports/GO-2021-0225.yaml
index b80a2e9..e52414b 100644
--- a/reports/GO-2021-0225.yaml
+++ b/reports/GO-2021-0225.yaml
@@ -5,7 +5,8 @@
       - ReadUvarint
     versions:
       - fixed: go1.13.15
-      - fixed: go1.14.7
+      - introduced: go1.14.0
+        fixed: go1.14.7
 description: |
     Certain invalid inputs to ReadUvarint or ReadVarint could cause those
     functions to read an unlimited number of bytes from the ByteReader argument
diff --git a/reports/GO-2021-0226.yaml b/reports/GO-2021-0226.yaml
index 9def5cf..dd0f4d7 100644
--- a/reports/GO-2021-0226.yaml
+++ b/reports/GO-2021-0226.yaml
@@ -7,7 +7,8 @@
       - response.writeCGIHeader
     versions:
       - fixed: go1.14.8
-      - fixed: go1.15.1
+      - introduced: go1.15.0
+        fixed: go1.15.1
   - module: std
     package: net/http/fcgi
     symbols:
@@ -16,7 +17,8 @@
       - response.writeCGIHeader
     versions:
       - fixed: go1.14.8
-      - fixed: go1.15.1
+      - introduced: go1.15.0
+        fixed: go1.15.1
 description: |
     When a Handler does not explicitly set the Content-Type header, the the
     package would default to “text/html”, which could cause a Cross-Site Scripting
diff --git a/reports/GO-2021-0234.yaml b/reports/GO-2021-0234.yaml
index ad246d6..63702f8 100644
--- a/reports/GO-2021-0234.yaml
+++ b/reports/GO-2021-0234.yaml
@@ -5,8 +5,8 @@
       - Decoder.Token
     versions:
       - fixed: go1.15.9
-      - fixed: go1.16.1
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.1
 description: |
     The Decode, DecodeElement, and Skip methods of an xml.Decoder
     provided by xml.NewTokenDecoder may enter an infinite loop when
diff --git a/reports/GO-2021-0235.yaml b/reports/GO-2021-0235.yaml
index 5046fc5..6795732 100644
--- a/reports/GO-2021-0235.yaml
+++ b/reports/GO-2021-0235.yaml
@@ -5,8 +5,8 @@
       - p224Contract
     versions:
       - fixed: go1.14.14
-      - fixed: go1.15.7
-      - fixed: go1.16.0
+      - introduced: go1.15.0
+        fixed: go1.15.7
 description: |
     The P224() Curve implementation can in rare circumstances generate
     incorrect outputs, including returning invalid points from
diff --git a/reports/GO-2021-0239.yaml b/reports/GO-2021-0239.yaml
index 74c98dc..320a42c 100644
--- a/reports/GO-2021-0239.yaml
+++ b/reports/GO-2021-0239.yaml
@@ -9,8 +9,8 @@
       - Resolver.LookupSRV
     versions:
       - fixed: go1.15.13
-      - fixed: go1.16.5
-      - fixed: go1.17
+      - introduced: go1.16.0
+        fixed: go1.16.5
 description: |
     The LookupCNAME, LookupSRV, LookupMX, LookupNS, and LookupAddr
     functions and their respective methods on the Resolver type may
diff --git a/reports/GO-2021-0240.yaml b/reports/GO-2021-0240.yaml
index 2b066b3..064868b 100644
--- a/reports/GO-2021-0240.yaml
+++ b/reports/GO-2021-0240.yaml
@@ -5,8 +5,8 @@
       - Reader.init
     versions:
       - fixed: go1.15.13
-      - fixed: go1.16.5
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.5
 description: |
     NewReader and OpenReader can cause a panic or an unrecoverable
     fatal error when reading an archive that claims to contain a large
diff --git a/reports/GO-2021-0241.yaml b/reports/GO-2021-0241.yaml
index 79353a6..77cd891 100644
--- a/reports/GO-2021-0241.yaml
+++ b/reports/GO-2021-0241.yaml
@@ -5,8 +5,8 @@
       - ReverseProxy.ServeHTTP
     versions:
       - fixed: go1.15.13
-      - fixed: go1.16.5
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.5
 description: |
     ReverseProxy can be made to forward certain hop-by-hop headers,
     including Connection. If the target of the ReverseProxy is
diff --git a/reports/GO-2021-0242.yaml b/reports/GO-2021-0242.yaml
index 0146e35..87209c7 100644
--- a/reports/GO-2021-0242.yaml
+++ b/reports/GO-2021-0242.yaml
@@ -5,8 +5,8 @@
       - Rat.SetString
     versions:
       - fixed: go1.15.13
-      - fixed: go1.16.5
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.5
 description: |
     Rat.SetString and Rat.UnmarshalText may cause a panic or an
     unrecoverable fatal error if passed inputs with very large
diff --git a/reports/GO-2021-0243.yaml b/reports/GO-2021-0243.yaml
index 76f30db..066187a 100644
--- a/reports/GO-2021-0243.yaml
+++ b/reports/GO-2021-0243.yaml
@@ -5,8 +5,8 @@
       - rsaKeyAgreement.generateClientKeyExchange
     versions:
       - fixed: go1.15.14
-      - fixed: go1.16.6
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.6
 description: |
     crypto/tls clients can panic when provided a certificate of the
     wrong type for the negotiated parameters. net/http clients
diff --git a/reports/GO-2021-0245.yaml b/reports/GO-2021-0245.yaml
index bc0076b..16af7fa 100644
--- a/reports/GO-2021-0245.yaml
+++ b/reports/GO-2021-0245.yaml
@@ -5,8 +5,8 @@
       - ReverseProxy.ServeHTTP
     versions:
       - fixed: go1.15.15
-      - fixed: go1.16.7
-      - fixed: go1.17.0
+      - introduced: go1.16.0
+        fixed: go1.16.7
 description: |
     ReverseProxy can panic after encountering a problem copying
     a proxied response body.
diff --git a/reports/GO-2021-0263.yaml b/reports/GO-2021-0263.yaml
index 81fd9a3..68458db 100644
--- a/reports/GO-2021-0263.yaml
+++ b/reports/GO-2021-0263.yaml
@@ -4,8 +4,9 @@
     symbols:
       - NewFile
     versions:
-      - fixed: go1.17.3
       - fixed: go1.16.10
+      - introduced: go1.17.0
+        fixed: go1.17.3
 description: |
     Calling File.ImportedSymbols on a loaded file which contains an invalid
     dynamic symbol table command can cause a panic, in particular if the encoded
diff --git a/reports/GO-2021-0264.yaml b/reports/GO-2021-0264.yaml
index 73fd19b..f24a159 100644
--- a/reports/GO-2021-0264.yaml
+++ b/reports/GO-2021-0264.yaml
@@ -6,7 +6,8 @@
       - Reader.Open
     versions:
       - fixed: go1.16.10
-      - fixed: go1.17.3
+      - introduced: go1.17.0
+        fixed: go1.17.3
 description: |
     Previously, opening a zip with (*Reader).Open could result in a panic if the
     zip contained a file whose name was exclusively made up of slash characters or