diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 8488eab94d..b9cf4c71e7 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -90,6 +90,7 @@ connman conventionalcommits copyleft coreinfrastructure +cpanfile cpio cpp CQA diff --git a/README.md b/README.md index 6200b6685b..78bfb04682 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ For more details, see our [documentation](https://cve-bin-tool.readthedocs.io/en - [Go](#go) - [Swift](#swift) - [Python](#python) + - [Perl](#perl) - [SBOM Generation](#sbom-generation) - [Limitations](#limitations) - [Additional Requirements](#additional-requirements) @@ -520,6 +521,14 @@ The tool supports the scanning of the contents of any Wheel package files (indic The `--package-list` option can be used with a Python dependencies file `requirements.txt` to find the vulnerabilities in the list of components. +### Perl + +The scanner examines the `cpanfile` file within a perl application to identify components. The package names and versions are used to search the database for vulnerabilities. + +The `cpanfile` must specify the version data for the vulnerability scanner to work. At this time packages without versions will be ignored. (Patches welcome to improve this behaviour in future!) + +Here's an example of what a [`cpanfile`](https://github.com/intel/cve-bin-tool/blob/main/test/language_data/cpanfile) might look like. + ## SBOM Generation To generate a software bill of materials file (SBOM) ensure these options are included: diff --git a/cve_bin_tool/parsers/__init__.py b/cve_bin_tool/parsers/__init__.py index 478ec26b88..21b223a486 100644 --- a/cve_bin_tool/parsers/__init__.py +++ b/cve_bin_tool/parsers/__init__.py @@ -14,6 +14,7 @@ "go", "swift", "php", + "perl", ] diff --git a/cve_bin_tool/parsers/parse.py b/cve_bin_tool/parsers/parse.py index b51337bda0..032fdcf30a 100644 --- a/cve_bin_tool/parsers/parse.py +++ b/cve_bin_tool/parsers/parse.py @@ -4,6 +4,7 @@ from cve_bin_tool.parsers.go import GoParser from cve_bin_tool.parsers.java import JavaParser from cve_bin_tool.parsers.javascript import JavascriptParser +from cve_bin_tool.parsers.perl import PerlParser from cve_bin_tool.parsers.php import PhpParser from cve_bin_tool.parsers.python import PythonParser, PythonRequirementsParser from cve_bin_tool.parsers.r import RParser @@ -23,6 +24,7 @@ "Gemfile.lock": RubyParser, "Package.resolved": SwiftParser, "composer.lock": PhpParser, + "cpanfile": PerlParser, } diff --git a/cve_bin_tool/parsers/perl.py b/cve_bin_tool/parsers/perl.py new file mode 100644 index 0000000000..a3b08566d9 --- /dev/null +++ b/cve_bin_tool/parsers/perl.py @@ -0,0 +1,35 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: GPL-3.0-or-later + +from cve_bin_tool.parsers import Parser + + +class PerlParser(Parser): + def __init__(self, cve_db, logger): + super().__init__(cve_db, logger) + + def run_checker(self, filename): + self.filename = filename + with open(self.filename) as fh: + data = fh.readlines() + # Split each line into tokens and find dependencies + dependencies = [] + for line in data: + tokens = line.split() + if len(tokens) >= 4 and tokens[0] == "requires" and tokens[2] == "=>": + dependencies.append( + ( + tokens[1].strip('"').split("::")[-1], + tokens[3].strip("'").strip(";").strip('"'), + ) + ) + elif len(tokens) >= 1 and tokens[0] == "requires": + name = (tokens[1].strip('"').split("::")[-1],) + self.logger.debug(f"Dependency with no version information: {name}") + + # Print the extracted dependencies + for dependency in dependencies: + vendor = self.find_vendor(dependency[0], dependency[1]) + if vendor is not None: + yield from vendor + self.logger.debug(f"Done scanning file: {self.filename}") diff --git a/test/language_data/cpanfile b/test/language_data/cpanfile new file mode 100644 index 0000000000..caf9240ceb --- /dev/null +++ b/test/language_data/cpanfile @@ -0,0 +1,35 @@ +use strict; +use warnings; + +requires "Carp" => "0"; +requires "HTTP::Tiny" => "0.056"; +requires "IO::Socket::SSL" => "1.42"; +requires "JSON::MaybeXS" => "0"; +requires "JSON::PP" => "0"; +requires "Moo" => "0"; +requires "Moo::Role" => "0"; +requires "Net::SSLeay" => "1.49"; +requires "Ref::Util" => "0"; +requires "Safe::Isa" => "0"; +requires "Type::Tiny" => "0"; +requires "URI::Escape"; +requires "perl" => "5.010"; +requires "strict" => "0"; +requires "warnings" => "0"; + +on 'test' => sub { + requires "Test::Fatal" => "0"; + requires "Test::More" => "0"; + requires "Test::Needs" => "0.002005"; + requires "base" => "0"; + requires "blib" => "1.01"; + requires "LWP::Protocol::https" => "0"; + recommends "HTTP::Tiny::Mech" => "1.001002"; + recommends "WWW::Mechanize::Cached" => "1.54"; +}; + +on 'develop' => sub { + requires "HTTP::Tiny::Mech" => "1.001002"; + requires "LWP::Protocol::https" => "0"; + requires "WWW::Mechanize::Cached" => "1.54"; +}; \ No newline at end of file diff --git a/test/test_language_scanner.py b/test/test_language_scanner.py index f5889aff25..62fd1917cb 100644 --- a/test/test_language_scanner.py +++ b/test/test_language_scanner.py @@ -150,6 +150,7 @@ class TestLanguageScanner: "database", "phpunit", ] + PERL_PRODUCTS = ["perl", "warnings", "base"] SWIFT_PRODUCTS = ["alliance_web_platform"] @@ -213,6 +214,7 @@ def test_language_package_none_found(self, filename: str) -> None: (str(TEST_FILE_PATH / "go.mod"), GO_PRODUCTS), (str(TEST_FILE_PATH / "Package.resolved"), SWIFT_PRODUCTS), (str(TEST_FILE_PATH / "composer.lock"), PHP_PRODUCTS), + (str(TEST_FILE_PATH / "cpanfile"), PERL_PRODUCTS), ], ) def test_language_package(self, filename: str, products) -> None: