Skip to content

Commit 0b4907f

Browse files
committed
Validate debian package version
This patch was originally by Aaron Foster <afoster@cloudflare.com>, as a local fix at Cloudflare. While we've pushed most of our internal patches upstream it looks like this one may have been missed. Aaron is no longer at Cloudflare, so I'm sending this up on his behalf. This patch simply validates Debian versions so that invalid versions do not make it into the repo. I've dug through open and closed PRs, and don't see anything along these lines, but apologies if I missed it. I updated the patch for the current `master` branch. Signed off with internal and external emails for clarity, but commit is under personal email where we typically do open source contributions from. Signed-off-by: Phil Dibowitz <pdibowitz@cloudflare.com> Signed-off-by: Phil Dibowitz <pdibowitz@ipom.com>
1 parent f59b0d2 commit 0b4907f

5 files changed

Lines changed: 115 additions & 11 deletions

File tree

deb/import.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ func ImportPackageFiles(list *PackageList, packageFiles []string, forceReplace b
124124
continue
125125
}
126126

127+
if !isValidVersion(p.Version) {
128+
reporter.Warning("Version number ('%s') for the '%s' package is invalid", p.Version, p.Name)
129+
failedFiles = append(failedFiles, file)
130+
continue
131+
}
132+
127133
if p.Architecture == "" {
128134
reporter.Warning("Empty architecture on %s", file)
129135
failedFiles = append(failedFiles, file)

deb/package.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,9 @@ func versionSatisfiesDependency(version string, dep Dependency) bool {
412412
return r == 0
413413
case VersionLess:
414414
return r < 0
415+
// 2 is returned when a package with an invalid version is detected; setting boundary for VersionGreater and GreaterOrEqual cases
415416
case VersionGreater:
416-
return r > 0
417+
return r > 0 && r < 2
417418
case VersionLessOrEqual:
418419
return r <= 0
419420
case VersionGreaterOrEqual:

deb/query.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,13 @@ func (q *FieldQuery) Matches(pkg PackageLike) bool {
169169
return field != ""
170170
case VersionEqual:
171171
return CompareVersions(field, q.Value) == 0
172+
// 2 is returned when a package with an invalid version is detected; setting boundary for VersionGreater and VersionGreaterOrEqual cases
172173
case VersionGreater:
173-
return CompareVersions(field, q.Value) > 0
174+
result := CompareVersions(field, q.Value)
175+
return result > 0 && result < 2
174176
case VersionGreaterOrEqual:
175-
return CompareVersions(field, q.Value) >= 0
177+
result := CompareVersions(field, q.Value)
178+
return result >= 0 && result < 2
176179
case VersionLess:
177180
return CompareVersions(field, q.Value) < 0
178181
case VersionLessOrEqual:

deb/version.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,26 @@ import (
88
"unicode"
99
)
1010

11+
var (
12+
upstreamVersionRegex = regexp.MustCompile(`^[0-9][A-Za-z0-9.+~\-]*$`)
13+
debianRevisionRegex = regexp.MustCompile(`^[A-Za-z0-9.+~]*$`)
14+
)
15+
1116
// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
1217

1318
// CompareVersions compares two package versions
1419
func CompareVersions(ver1, ver2 string) int {
15-
e1, u1, d1 := parseVersion(ver1)
16-
e2, u2, d2 := parseVersion(ver2)
20+
e1, u1, d1, err := parseVersion(ver1)
21+
// if an error is caught during parse, return 2 to signal
22+
// an invalid version and handle as needed
23+
if err != nil {
24+
return 2
25+
}
26+
27+
e2, u2, d2, err := parseVersion(ver2)
28+
if err != nil {
29+
return 2
30+
}
1731

1832
r := compareVersionPart(e1, e2)
1933
if r != 0 {
@@ -29,7 +43,7 @@ func CompareVersions(ver1, ver2 string) int {
2943
}
3044

3145
// parseVersions breaks down full version to components (possibly empty)
32-
func parseVersion(ver string) (epoch, upstream, debian string) {
46+
func parseVersion(ver string) (epoch, upstream, debian string, err error) {
3347
i := strings.Index(ver, ":")
3448
if i != -1 {
3549
epoch, ver = ver[:i], ver[i+1:]
@@ -38,13 +52,55 @@ func parseVersion(ver string) (epoch, upstream, debian string) {
3852
i = strings.Index(ver, "-")
3953
if i != -1 {
4054
debian, ver = ver[i+1:], ver[:i]
55+
if debian == "" {
56+
// if a hyphen is detected in the upstream version
57+
// string without a debian revision following it, the
58+
// version is invalid
59+
return "", "", "", fmt.Errorf("could not parse version: version string ('%s-') includes hyphen without Debian revision", ver)
60+
}
4161
}
4262

4363
upstream = ver
4464

4565
return
4666
}
4767

68+
// isValidVersion checks whether package version is compliant with control field spec
69+
// source: https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version
70+
func isValidVersion(ver string) bool {
71+
epoch, upstream, deb, err := parseVersion(ver)
72+
if err != nil {
73+
return false
74+
}
75+
76+
// validate epoch component
77+
if epoch != "" {
78+
// uint64 for unexpectedly high epoch values
79+
_, err := strconv.ParseUint(epoch, 10, 64)
80+
if err != nil {
81+
return false
82+
}
83+
}
84+
85+
if upstream == "" || !isValidUpstreamVersion(upstream) {
86+
return false
87+
}
88+
89+
if deb != "" && !isValidDebianRevision(deb) {
90+
return false
91+
}
92+
93+
return true
94+
}
95+
96+
func isValidUpstreamVersion(upstream string) bool {
97+
return upstreamVersionRegex.MatchString(upstream)
98+
}
99+
100+
func isValidDebianRevision(debian string) bool {
101+
return debianRevisionRegex.MatchString(debian)
102+
}
103+
48104
// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
49105
func compareLexicographic(s1, s2 string) int {
50106
i := 0

deb/version_test.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,54 @@ type VersionSuite struct {
1010
var _ = Suite(&VersionSuite{})
1111

1212
func (s *VersionSuite) TestParseVersion(c *C) {
13-
e, u, d := parseVersion("1.3.4")
13+
e, u, d, err := parseVersion("1.3.4")
1414
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", ""})
15+
c.Check(err, Equals, nil)
1516

16-
e, u, d = parseVersion("4:1.3:4")
17+
e, u, d, err = parseVersion("4:1.3:4")
1718
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3:4", ""})
19+
c.Check(err, Equals, nil)
1820

19-
e, u, d = parseVersion("1.3.4-1")
21+
e, u, d, err = parseVersion("1.3.4-1")
2022
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3.4", "1"})
23+
c.Check(err, Equals, nil)
2124

22-
e, u, d = parseVersion("1.3-pre4-1")
25+
e, u, d, err = parseVersion("1.3-pre4-1")
2326
c.Check([]string{e, u, d}, DeepEquals, []string{"", "1.3", "pre4-1"})
27+
c.Check(err, Equals, nil)
2428

25-
e, u, d = parseVersion("4:1.3-pre4-1")
29+
e, u, d, err = parseVersion("4:1.3-pre4-1")
2630
c.Check([]string{e, u, d}, DeepEquals, []string{"4", "1.3", "pre4-1"})
31+
c.Check(err, Equals, nil)
32+
33+
e, u, d, err = parseVersion("1:1.2024-")
34+
c.Check([]string{e, u, d}, DeepEquals, []string{"", "", ""})
35+
c.Check(err.Error(), Equals, "could not parse version: version string ('1.2024-') includes hyphen without Debian revision")
36+
}
37+
38+
func (s *VersionSuite) TestIsValidVersion(c *C) {
39+
// valid cases
40+
valid := isValidVersion("1.2.3-abc")
41+
c.Check(valid, Equals, true)
42+
43+
valid = isValidVersion("1.0.1337~rc2-3")
44+
c.Check(valid, Equals, true)
45+
46+
valid = isValidVersion("1.2.3+fdsfgs")
47+
c.Check(valid, Equals, true)
48+
49+
valid = isValidVersion("1:2.3~4-5six")
50+
c.Check(valid, Equals, true)
51+
52+
// invalid cases
53+
valid = isValidVersion("1:1.2.3-")
54+
c.Check(valid, Equals, false)
55+
56+
valid = isValidVersion("42:")
57+
c.Check(valid, Equals, false)
58+
59+
valid = isValidVersion("1:a.1.2.3~-4")
60+
c.Check(valid, Equals, false)
2761
}
2862

2963
func (s *VersionSuite) TestCompareLexicographic(c *C) {
@@ -101,6 +135,10 @@ func (s *VersionSuite) TestCompareVersions(c *C) {
101135

102136
c.Check(CompareVersions("5.2.0.3", "5.2.0.283"), Equals, -1)
103137
c.Check(CompareVersions("4.3.5a", "4.3.5-rc3-1"), Equals, 1)
138+
139+
// version validation happens independent of CompareVersions, so only testing the
140+
// edge case where a package is missing a Debian version during parseVersion
141+
c.Check(CompareVersions("1:abc~1.2.3-", "1:1.2.3~abc-good"), Equals, 2)
104142
}
105143

106144
func (s *VersionSuite) TestParseDependency(c *C) {

0 commit comments

Comments
 (0)