Skip to content

Commit

Permalink
fix: handle invalid version input in ecosystem sort_key (#2515)
Browse files Browse the repository at this point in the history
Some ecosystem `sort_key` functions do not validate versions before
converting them to their ecosystem-specific formats. This leads to
several errors occur when using `sort_key` to compare user-input
versions with affected ranges.

Add version validation before conversion to ensure no errors occur.
  • Loading branch information
hogo6002 authored Aug 23, 2024
1 parent fa75d48 commit af69902
Show file tree
Hide file tree
Showing 19 changed files with 94 additions and 46 deletions.
4 changes: 4 additions & 0 deletions osv/ecosystems/alpine.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def get_branch_name(self) -> str:
return self.alpine_release_ver.lstrip('v') + self._BRANCH_SUFFIX

def sort_key(self, version):
if not AlpineLinuxVersion.is_valid(version):
# If version is not valid, it is most likely an invalid input
# version then sort it to the last/largest element
return AlpineLinuxVersion('999999')
return AlpineLinuxVersion(version)

@staticmethod
Expand Down
4 changes: 4 additions & 0 deletions osv/ecosystems/alpine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ def test_alpine(self, ensure_updated_checkout_mock: mock.MagicMock):
self.assertGreater(
ecosystem.sort_key('1.13.2-r0'), ecosystem.sort_key('1.13.2_alpha'))

# Check invalid version handle
self.assertGreater(
ecosystem.sort_key('1-0-0'), ecosystem.sort_key('1.13.2-r0'))

ecosystems.config.set_cache(None)
4 changes: 4 additions & 0 deletions osv/ecosystems/bioconductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def get_bioc_versions(self):

def sort_key(self, version):
"""Sort key."""
if not semver_index.is_valid(version):
# If version is not valid, it is most likely an invalid input
# version then sort it to the last/largest element
return semver_index.parse('999999')
return semver_index.parse(version)

def _enumerate_versions(self,
Expand Down
1 change: 1 addition & 0 deletions osv/ecosystems/bioconductor_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ def test_next_version(self):
ecosystem = ecosystems.get('Bioconductor')
self.assertEqual('1.18.0', ecosystem.next_version('a4', '1.16.0'))
self.assertEqual('1.20.0', ecosystem.next_version('a4', '1.18.0'))
self.assertGreater(ecosystem.sort_key('1-0'), ecosystem.sort_key('1.2.0'))
with self.assertRaises(ecosystems.EnumerateError):
ecosystem.next_version('doesnotexist123456', '1')
1 change: 1 addition & 0 deletions osv/ecosystems/cran.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def sort_key(self, version):
# The packaging.version appears to work for the typical X.Y.Z and
# X.Y-Z cases
version = version.replace("-", ".")
# version.parse() handles invalid versions by returning LegacyVersion()
return packaging.version.parse(version)

def _enumerate_versions(self,
Expand Down
7 changes: 6 additions & 1 deletion osv/ecosystems/haskell.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ def sort_key(self, version):
https://hackage.haskell.org/package/Cabal-syntax/docs/Distribution-Types-Version.html
"""
return [int(x) for x in version.split('.')]
# If version is not valid, it is most likely an invalid input version
# then sort it to the last/largest element
try:
return [int(x) for x in version.split('.')]
except ValueError:
return [999999]

def enumerate_versions(self,
package,
Expand Down
6 changes: 6 additions & 0 deletions osv/ecosystems/haskell_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def test_next_version(self):
with self.assertRaises(ecosystems.EnumerateError):
ecosystem.next_version('doesnotexist123456', '1')

def test_sort_key(self):
"""Test sort_key."""
ecosystem = ecosystems.get('Hackage')
self.assertGreater(
ecosystem.sort_key('1-20-0'), ecosystem.sort_key('1.20.0'))


class GHCEcosystemTest(unittest.TestCase):
"""GHC ecosystem helper tests."""
Expand Down
37 changes: 8 additions & 29 deletions osv/ecosystems/maven_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,42 +84,21 @@ def test_sort(self):
"""Basic sort tests."""
# These tests are taken from the spec.
unsorted = [
'1',
'1.1',
'1-snapshot',
'1',
'1-sp',
'1-foo2',
'1-foo10',
'1.foo',
'1-foo',
'1-1',
'1.1',
'1.ga',
'1-ga',
'1-0',
'1.0',
'1',
'1-sp',
'1-ga',
'1-sp.1',
'1-ga.1',
'1-sp-1',
'1-ga-1',
'1-1',
'1-a1',
'1-alpha-1',
'2',
'1', '1.1', '1-snapshot', '1', '1-sp', '1-foo2', '1-foo10', '1.foo',
'1-foo', '1-1', '1.1', '1.ga', '1-ga', '1-0', '1.0', '1', '1-sp',
'1-ga', '1-sp.1', '1-ga.1', '1-sp-1', '1-ga-1', '1-1', '1-a1',
'1-alpha-1', '2', 'invalid', '0'
]

sorted_versions = [
str(v) for v in sorted(maven.Version.from_string(v) for v in unsorted)
]

self.assertListEqual([
'1-alpha-1', '1-alpha-1', '1-snapshot', '1', '1', '1', '1', '1', '1',
'1', '1', '1.foo', '1-.1', '1-sp', '1-sp', '1-sp-1', '1-sp.1', '1-foo',
'1-foo-2', '1-foo-10', '1-1', '1-1', '1-1', '1.1', '1.1', '2'
'invalid', '0', '1-alpha-1', '1-alpha-1', '1-snapshot', '1', '1', '1',
'1', '1', '1', '1', '1', '1.foo', '1-.1', '1-sp', '1-sp', '1-sp-1',
'1-sp.1', '1-foo', '1-foo-2', '1-foo-10', '1-1', '1-1', '1-1', '1.1',
'1.1', '2'
], sorted_versions)

def test_versions_qualifiers(self):
Expand Down
4 changes: 4 additions & 0 deletions osv/ecosystems/nuget.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def __lt__(self, other):
def from_string(cls, str_version):
str_version = semver_index.coerce(str_version)
str_version, revision = _extract_revision(str_version)
if not semver_index.is_valid(str_version):
# If version is not valid, it is most likely an invalid input
# version then sort it to the last/largest element
return Version(semver_index.parse('999999'), 999999)
return Version(semver_index.parse(str_version), revision)


Expand Down
6 changes: 6 additions & 0 deletions osv/ecosystems/nuget_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ def test_next_version(self):
with self.assertRaises(ecosystems.EnumerateError):
ecosystem.next_version('doesnotexist123456', '1')

def test_sort_key(self):
ecosystem = ecosystems.get('NuGet')
# Tests invalid input versions
self.assertGreater(
ecosystem.sort_key('1.4.0rc3'), ecosystem.sort_key('3.0.0.4001'))


if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions osv/ecosystems/packagist_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class PackagistEcosystemTest(unittest.TestCase):
def test_packagist(self):
"""Test Packagist."""
ecosystem = ecosystems.get('Packagist')
# Any invalid versions will be handled.
self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0'))
self.assertLess(
ecosystem.sort_key('4.3-2RC1'), ecosystem.sort_key('4.3-2RC2'))
self.assertGreater(
Expand Down
7 changes: 6 additions & 1 deletion osv/ecosystems/pub.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ def __eq__(self, other):

@classmethod
def from_string(cls, str_version):
return Version(semver_index.parse(str_version))
# If version is not valid, it is most likely an invalid input
# version then sort it to the last/largest element
try:
return Version(semver_index.parse(str_version))
except ValueError:
return Version(semver_index.parse('999999'))


class Pub(Ecosystem):
Expand Down
2 changes: 2 additions & 0 deletions osv/ecosystems/pub_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def test_parse(self):
pub.Version.from_string('1.2.3+build.1')
pub.Version.from_string('1.2.3+x.7.z-92')
pub.Version.from_string('1.0.0-rc-1+build-1')
# Tests invalid versions
pub.Version.from_string('3.4.0rc3-invalid')

def test_empty_identifier(self):
"""Test parsing versions with empty identifiers.
Expand Down
1 change: 1 addition & 0 deletions osv/ecosystems/pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class PyPI(Ecosystem):

def sort_key(self, version):
"""Sort key."""
# version.parse() handles invalid versions by returning LegacyVersion()
return packaging.version.parse(version)

def enumerate_versions(self,
Expand Down
6 changes: 6 additions & 0 deletions osv/ecosystems/pypi_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ def test_next_version(self):
self.assertEqual('0.3.0', ecosystem.next_version('grpcio', '0'))
with self.assertRaises(ecosystems.EnumerateError):
ecosystem.next_version('doesnotexist123456', '1')

def test_sort_key(self):
"""Test sort_key"""
ecosystem = ecosystems.get('PyPI')
self.assertGreater(ecosystem.sort_key('2.0.0'), ecosystem.sort_key('1.0.0'))
self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0'))
16 changes: 9 additions & 7 deletions osv/ecosystems/rocky_linux_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ class RockyLinuxEcosystemTest(unittest.TestCase):
"""Rocky Linux ecosystem helper tests."""

def test_rocky_linux(self):
"""Test sort_key"""
ecosystem = ecosystems.get('Rocky Linux')
self.assertEqual("Rocky Linux", ecosystem.name)
self.assertEqual('Rocky Linux', ecosystem.name)
self.assertGreater(
ecosystem.sort_key("0:0.2.6-20.module+el8.9.0+1420+91577025"),
ecosystem.sort_key("0:0.0.99.4-5.module+el8.9.0+1445+07728297"))
ecosystem.sort_key('0:0.2.6-20.module+el8.9.0+1420+91577025'),
ecosystem.sort_key('0:0.0.99.4-5.module+el8.9.0+1445+07728297'))
self.assertGreater(
ecosystem.sort_key("0:0.2.6-20.module+el8.9.0+1420+91577025"),
ecosystem.sort_key("0"))
ecosystem.sort_key('0:0.2.6-20.module+el8.9.0+1420+91577025'),
ecosystem.sort_key('0'))
self.assertGreater(
ecosystem.sort_key("2:1.14.3-2.module+el8.10.0+1815+5fe7415e"),
ecosystem.sort_key("2:1.10.3-1.module+el8.10.0+1815+5fe7415e"))
ecosystem.sort_key('2:1.14.3-2.module+el8.10.0+1815+5fe7415e'),
ecosystem.sort_key('2:1.10.3-1.module+el8.10.0+1815+5fe7415e'))
self.assertLess(ecosystem.sort_key('invalid'), ecosystem.sort_key('0'))
9 changes: 7 additions & 2 deletions osv/ecosystems/rubygems.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import requests

from ..third_party.univers.gem import GemVersion
from ..third_party.univers.gem import GemVersion, InvalidVersionError

from . import config
from .helper_base import Ecosystem, EnumerateError
Expand All @@ -28,7 +28,12 @@ class RubyGems(Ecosystem):

def sort_key(self, version):
"""Sort key."""
return GemVersion(version)
# If version is not valid, it is most likely an invalid input
# version then sort it to the last/largest element
try:
return GemVersion(version)
except InvalidVersionError:
return GemVersion('999999')

def enumerate_versions(self,
package,
Expand Down
8 changes: 8 additions & 0 deletions osv/ecosystems/rubygems_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ def test_next_version(self):
ecosystem.next_version('rails', '5.0.0.beta4'))
with self.assertRaises(ecosystems.EnumerateError):
ecosystem.next_version('doesnotexist123456', '1')

def test_sort_key(self):
"""Test sort_key with invalid versions"""
ecosystem = ecosystems.get('RubyGems')
self.assertGreater(
ecosystem.sort_key('invalid'), ecosystem.sort_key('4.0.0.rc1'))
self.assertGreater(
ecosystem.sort_key('v3.1.1'), ecosystem.sort_key('4.0.0.rc1'))
15 changes: 9 additions & 6 deletions osv/ecosystems/ubuntu_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ class UbuntuEcosystemTest(unittest.TestCase):
"""Ubuntu ecosystem helper tests."""

def test_ubuntu(self):
"""Test sort_key"""
ecosystem = ecosystems.get('Ubuntu')
self.assertGreater(
ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.3"),
ecosystem.sort_key("2.40.0+dfsg-3ubuntu0.5"))
ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.3'),
ecosystem.sort_key('2.40.0+dfsg-3ubuntu0.5'))
self.assertGreater(
ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.3"),
ecosystem.sort_key("2.42.8+dfsg-1ubuntu0.2"))
self.assertGreater(ecosystem.sort_key("5.4.13-1"), ecosystem.sort_key("0"))
ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.3'),
ecosystem.sort_key('2.42.8+dfsg-1ubuntu0.2'))
self.assertGreater(ecosystem.sort_key('5.4.13-1'), ecosystem.sort_key('0'))
self.assertGreater(
ecosystem.sort_key("5.4.13-1"), ecosystem.sort_key("3.2.30-1"))
ecosystem.sort_key('5.4.13-1'), ecosystem.sort_key('3.2.30-1'))
self.assertGreater(
ecosystem.sort_key('invalid'), ecosystem.sort_key('3.2.30-1'))

0 comments on commit af69902

Please sign in to comment.