Skip to content

feat: recommending safe packages #1284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Aug 11, 2021
Merged

feat: recommending safe packages #1284

merged 43 commits into from
Aug 11, 2021

Conversation

peb-peb
Copy link
Contributor

@peb-peb peb-peb commented Jul 27, 2021

No description provided.

peb-peb and others added 28 commits July 18, 2021 00:10
* fixes: intel#1159
* test: change way pytest is run in CI

Switch to using a full pytest gather for the async tests. Several test files are singled out to be run synchronously because they touch NVD and can cause us to get rate limited if they run in parallel.

This will ensure that no one needs to add new test files explicitly to CI unless they need to be run synchronously.

Several test files are (temporarily) disabled because they are not passing; we'll enable those as they are fixed.

Signed-off-by: Terri Oda <[email protected]>
* refactor(scanner): Remove scanned string splitting

refactor(strings): Return a string instead of array of string

* fix(checker): with respect to changes from b240fa0

fix: xml2 checker
fix: sqlite checker
fix: glibc checker and test
fix: systemd checker
fix: libdb checker
fix: systemd checker v229 .deb
fix: universal python package checker
* feat(checker): Add sudo checker

* add condensed downloads for sudo
* Fix yaml and toml tests in test_config
* Related to intel#1159
* Add test_config back to updated CI

Two of the tests in test_config were failing, presumably because they'd
gotten out of date when we changed the default for extraction. These
tests were not being run in CI (thanks to @Molkree for noticing that) so
they didn't get fixed in a timely manner.

Signed-off-by: Terri Oda <[email protected]>
* Add recommdended dev tools list
* Moved isort to dev requirements file.
* fix: pip install from dev-recommended for isort CI
* fix: remove isort also from requirements.csv

Signed-off-by: Terri Oda <[email protected]>
* fix: Add gnome-shell checker
* fix: Improve regex(multiline)
* fix: Windows test for gnome-shell checker
* fix: rename development requirements file for Snyk

Snyk picks up files named `*req*.txt` so we're switching
dev-recommended.txt to dev-requirements.txt so gets scanned
automatically.

Signed-off-by: Terri Oda <[email protected]>
* fix: condensed downloads

The condensed downloads were made without any proper strings in it due to the changes in intel#1227.

* refactor: writelines -> write
…#1246)

* refactor: helper script for is_executable() and parse_string()
* helper script: Instantiate instance of VersionScanner

Previously we were only using VersionScanner.clean_file_path() which is
a static method. In this case we did not need to instantiate and
instance.

We started using the is_executable() and parse_strings() methods, which
are regular methods which use self. When a method takes self as the
first positional argument we must instantiate its class before we use
call the attached method.

As such, we've created a new instance of VersionScanner as the
version_scanner property within HelperScript.

Tested with

$ curl -sfLo /tmp/libmatroska-1.5.0-1.el8.aarch64.rpm \
  https://download-ib01.fedoraproject.org/pub/epel/8/Everything/x86_64/Packages/l/libmatroska-1.5.0-1.el8.x86_64.rpm
$ python cve_bin_tool/helper_script.py /tmp/libmatroska-1.5.0-1.el8.aarch64.rpm
[07:09:05] INFO     cve_bin_tool.VersionScanner - Updating version_scanner.py:42
                    egg_info
           WARNING  cve_bin_tool.HelperScript - False        helper_script.py:74
           WARNING  cve_bin_tool.HelperScript - False        helper_script.py:74
           WARNING  cve_bin_tool.HelperScript - False        helper_script.py:74
           WARNING  cve_bin_tool.HelperScript - False        helper_script.py:74
           WARNING  cve_bin_tool.HelperScript - (True, '/tmp helper_script.py:74
                    /cve-bin-tool-3shnb9ex/libmatroska-1.5.0
                    -1.el8.aarch64.rpm.extracted/usr/lib64/l
                    ibmatroska.so.6.0.0: ELF 64-bit LSB
                    shared object, x86-64, version 1 (SYSV),
                    dynamically linked, BuildID[sha1]=4e7b21
                    76f1dc3f381571a2c873532ab0ce254c24,
                    stripped, too many notes (256)\n')
           WARNING  cve_bin_tool.HelperScript - False        helper_script.py:74
────────────────────────────── LibmatroskaChecker ──────────────────────────────
CONTAIN_PATTERNS = [
        r"N11libmatroska24KaxChapterProcessCodecIDE",
        r"N11libmatroska24KaxChapterProcessCommandE",
        r"N11libmatroska24KaxChapterProcessPrivateE",
libmatroska::KaxVideoGamma::RenderData(libebml::IOCallback&, bool, bool)",
        ... <output shortened> ...
        r"virtual libmatroska::DataBuffer* libmatroska::DataBuffer::Clone()",
        r"void
libmatroska::KaxBlockBlob::SetBlockGroup(libmatroska::KaxBlockGroup&)",
        r"void libmatroska::KaxBlockGroup::SetBlockDuration(uint64)",
        r"void libmatroska::KaxReferenceBlock::SetReferencedBlock(const
libmatroska::KaxBlockBlob*)",
]
FILENAME_PATTERNS = [
        r"libmatroska.so.6.0.0",
]
VERSION_PATTERNS = [
        r"/builddir/build/BUILD/libmatroska-1.5.0/matroska/KaxBlock.h",
        r"/builddir/build/BUILD/libmatroska-1.5.0/matroska/KaxTracks.h",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxBlock.cpp",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxBlockData.cpp",
        r"/builddir/build/BUILD/libmatroska-1.5.0/matroska/KaxCluster.h",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxCluster.cpp",
        r"/builddir/build/BUILD/libmatroska-1.5.0/matroska/KaxCues.h",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxCues.cpp",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxCuesData.cpp",
        r"/builddir/build/BUILD/libmatroska-1.5.0/src/KaxSemantic.cpp",
]
VENDOR_PRODUCT = [('matroska', 'libmatroska')]
────────────────────────────────────────────────────────────────────────────────

Signed-off-by: John Andersen <[email protected]>

* refactor(helperscript): removed unused imports

fix for black and isort
commented the log messages

* fix: typo

* refactor(version_scanner): now is_executable would retrun (True, output) or (False, None)

* refactor(helper_script): removed try/except block

* fix: black

Co-authored-by: John Andersen <[email protected]>
This helps close the gap we were seeing between the new and old API numbers: the new NVD API didn't report the rejected entries, but our code using the old API was storing entries in the db for them.
Making a SECURITY.md file just to make github happy (the information is duplicated from Readme.md)
Disables test_api_cve_count until it can be fixed.
@peb-peb
Copy link
Contributor Author

peb-peb commented Aug 3, 2021

(cve_env) peb@ooo:/cve-bin-tool$ python3 -m cve_bin_tool.cli -u never -f console ../test/
[21:12:30] WARNING  cve_bin_tool - Not verifying CVE DB cache                                                                         cli.py:318
           INFO     cve_bin_tool.CVEDB - There are 167114 CVE entries in the database                                               cvedb.py:334
           INFO     cve_bin_tool.VersionScanner - Updating egg_info                                                        version_scanner.py:42
[21:12:32] INFO     cve_bin_tool.VersionScanner - Checkers: accountsservice, avahi, bash, bind, binutils, bolt,            version_scanner.py:91
                    bubblewrap, busybox, bzip2, cronie, cryptsetup, cups, curl, dbus, dnsmasq, dovecot, dpkg, enscript,
                    expat, ffmpeg, freeradius, ftp, gcc, gimp, glibc, gnomeshell, gnupg, gnutls, gpgme, gstreamer, gupnp,
                    haproxy, hostapd, hunspell, icecast, icu, irssi, kbd, kerberos, kexectools, libarchive, libbpg,
                    libcurl, libdb, libgcrypt, libical, libjpeg_turbo, liblas, libnss, libsndfile, libsoup, libssh2,
                    libtiff, libvirt, libxslt, lighttpd, logrotate, lua, mariadb, mdadm, memcached, mtr, mysql, ncurses,
                    nessus, netpbm, nginx, node, ntp, openafs, openjpeg, openldap, openssh, openssl, openswan, openvpn,
                    p7zip, png, polarssl_fedora, postgresql, pspp, python, qt, radare2, rsyslog, samba, sqlite,
                    strongswan, subversion, sudo, syslogng, systemd, tcpdump, trousers, varnish, webkitgtk, wireshark,
                    wpa_supplicant, xerces, xml2, zlib, zsh

-----
output reduced
-----
           INFO     cve_bin_tool - There are 5 files with known CVEs detected                                                         cli.py:421
           INFO     cve_bin_tool - Known CVEs in ('gcc', '8.2.1'), ('gcc', '8.2.1'), ('wireshark', '1.10.14'), ('wireshark',          cli.py:431
                    '2.6.2'):
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /usr/lib/python3.8/runpy.py:194 in _run_module_as_main                                           │
│                                                                                                  │
│   191 │   main_globals = sys.modules["__main__"].__dict__                                        │
│   192 │   if alter_argv:                                                                         │
│   193 │   │   sys.argv[0] = mod_spec.origin                                                      │
│ ❱ 194 │   return _run_code(code, main_globals, None,                                             │
│   195 │   │   │   │   │    "__main__", mod_spec)                                                 │
│   196                                                                                            │
│   197 def run_module(mod_name, init_globals=None,                                                │
│                                                                                                  │
│ /usr/lib/python3.8/runpy.py:87 in _run_code                                                      │
│                                                                                                  │
│    84 │   │   │   │   │      __loader__ = loader,                                                │
│    85 │   │   │   │   │      __package__ = pkg_name,                                             │
│    86 │   │   │   │   │      __spec__ = mod_spec)                                                │
│ ❱  87 │   exec(code, run_globals)                                                                │
│    88 │   return run_globals                                                                     │
│    89                                                                                            │
│    90 def _run_module_code(code, init_globals=None,                                              │
│                                                                                                  │
│ /mnt/d/git_stuff/cve-bin-tool/cve_bin_tool/cli.py:464 in <module>                                │
│                                                                                                  │
│   461 │   if os.getenv("NO_EXIT_CVE_NUM"):                                                       │
│   462 │   │   main()                                                                             │
│   463 │   else:                                                                                  │
│ ❱ 464 │   │   sys.exit(main())                                                                   │
│   465                                                                                            │
│                                                                                                  │
│ /mnt/d/git_stuff/cve-bin-tool/cve_bin_tool/cli.py:448 in main                                    │
│                                                                                                  │
│   445 │   │   │   │   is_report=args["report"],                                                  │
│   446 │   │   │   │   append=args["append"],                                                     │
│   447 │   │   │   │   merge_report=merged_reports,                                               │
│ ❱ 448 │   │   │   │   verbosity_level=args["verbose"],                                           │
│   449 │   │   │   )                                                                              │
│   450 │   │   │                                                                                  │
│   451 │   │   │   if not args["quiet"]:                                                          │
│                                                                                                  │
│ /usr/lib/python3.8/collections/__init__.py:898 in __getitem__                                    │
│                                                                                                  │
│    895 │   │   │   │   return mapping[key]             # can't use 'key in mapping' with defaul  │
│    896 │   │   │   except KeyError:                                                              │
│    897 │   │   │   │   pass                                                                      │
│ ❱  898 │   │   return self.__missing__(key)            # support subclasses that define __missi  │
│    899 │                                                                                         │
│    900 │   def get(self, key, default=None):                                                     │
│    901 │   │   return self[key] if key in self else default                                      │
│                                                                                                  │
│ /usr/lib/python3.8/collections/__init__.py:890 in __missing__                                    │
│                                                                                                  │
│    887 │   │   self.maps = list(maps) or [{}]          # always at least one map                 │
│    888 │                                                                                         │
│    889 │   def __missing__(self, key):                                                           │
│ ❱  890 │   │   raise KeyError(key)                                                               │
│    891 │                                                                                         │
│    892 │   def __getitem__(self, key):                                                           │
│    893 │   │   for mapping in self.maps:                                                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
KeyError: 'verbose'

I'm getting this error when passing without the -v or --verbose option. Why is this happening?

@peb-peb
Copy link
Contributor Author

peb-peb commented Aug 3, 2021

this is the result that I'm currently get with the -v option:

╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                                   CVE BINARY TOOL                                                    ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

 • cve-bin-tool Report Generated: 2021-08-03  21:34:13
 • Time of last update of CVE Data: 2021-07-26  23:10:14
╭─────────────────╮
│  NewFound CVEs  │
╰─────────────────╯
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ Vendor    ┃ Product   ┃ Version ┃ CVE Number     ┃ Severity ┃ Score (CVSS Version) ┃ Affected Versions ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3182  │ MEDIUM   │ 5.5 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3814  │ MEDIUM   │ 5 (v2)               │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17935 │ HIGH     │ 7.5 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17997 │ HIGH     │ 7.5 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-6014  │ HIGH     │ 7.5 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-14438 │ HIGH     │ 7.5 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-6836  │ CRITICAL │ 9.8 (v3)             │                   │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2020-26575 │ HIGH     │ 7.5 (v3)             │                   │
└───────────┴───────────┴─────────┴────────────────┴──────────┴──────────────────────┴───────────────────┘

the all_cve_version_info returns me defaultdict(<class 'list'>, {'CVE-2017-17935': [['', '', '2.2.11', '']], 'CVE-2017-17997': [['', '', '2.2.11', '']], 'CVE-2017-6014': [['', '', '2.2.4', '']], 'CVE-2018-14438': [['', '', '2.6.2', '']], 'CVE-2018-6836': [['', '', '2.4.4', '']], 'CVE-2020-26575': [['', '', '3.2.7', '']], 'CVE-2015-3182': [], 'CVE-2015-3814': []}). How do I print this in the affected_version row?

@terriko
Copy link
Contributor

terriko commented Aug 3, 2021

Oh, also, you probably want to rebase this against origin/main so that it's triggering the new CI rules.

git fetch origin main
git rebase origin/main

now affected-versions get output with their respective color schema
based on severity
@peb-peb
Copy link
Contributor Author

peb-peb commented Aug 4, 2021

For console, the output looks like:

╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                                   CVE BINARY TOOL                                                    ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝

 • cve-bin-tool Report Generated: 2021-08-04  20:48:36
 • Time of last update of CVE Data: 2021-07-26  23:10:14
╭─────────────────╮
│  NewFound CVEs  │
╰─────────────────╯
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ Vendor    ┃ Product   ┃ Version ┃ CVE Number     ┃ Severity ┃ Score (CVSS Version) ┃ Affected Versions ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3182  │ MEDIUM   │ 5.5 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3814  │ MEDIUM   │ 5 (v2)               │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17935 │ HIGH     │ 7.5 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17997 │ HIGH     │ 7.5 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-6014  │ HIGH     │ 7.5 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-14438 │ HIGH     │ 7.5 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-6836  │ CRITICAL │ 9.8 (v3)             │ [ - 3.2.7]        │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2020-26575 │ HIGH     │ 7.5 (v3)             │ [ - 3.2.7]        │
└───────────┴───────────┴─────────┴────────────────┴──────────┴──────────────────────┴───────────────────┘

I was not able to output these formatted forms of affected-version patterns and was switching between things. But, now just before the meeting it just clicked me how it could be done 😅

Beyond that, this PR is missing:

documentation with examples (preferably in the various output formats?) as part of MANUAL.md
tests

I'll add these :) (since now the output problem is solved 😅)

Copy link
Contributor

@Molkree Molkree left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the docstring, now I understand the purpose of this function a bit more, which raises some questions:

  1. You are not using VersionInfo defined in util.py
  2. According to your logic in format_version_info there are only 3 cases: no version info, both including endpoints or both excluding endpoints. Is it true?
    • If it is true then there's no need to always pass 4 versions (when 2 of them will always be empty), just use 2 versions and a flag in your VersionInfo tuple (and don't forget to use it! it's easier to read named fields than just an index into the list)
    • If it's not true then you will need to cover mixed cases (v1,v2], [v1,v2) (again, don't forget to use your VersionInfo)

@terriko
Copy link
Contributor

terriko commented Aug 6, 2021

Currently failing (for real, not because of the nvd issue):

            console=console,
>           time_of_last_update=datetime.today(),
        )
E       TypeError: output_console() missing 2 required positional arguments: 'all_cve_version_info' and 'affected_versions'

test/test_output_engine.py:167: TypeError
=============================== warnings summary ===============================
../../../../../opt/hostedtoolcache/Python/3.6.14/x64/lib/python3.6/site-packages/setuptools/depends.py:2
../../../../../opt/hostedtoolcache/Python/3.6.14/x64/lib/python3.6/site-packages/setuptools/depends.py:2
../../../../../opt/hostedtoolcache/Python/3.6.14/x64/lib/python3.6/site-packages/setuptools/depends.py:2
../../../../../opt/hostedtoolcache/Python/3.6.14/x64/lib/python3.6/site-packages/setuptools/depends.py:2
  /opt/hostedtoolcache/Python/3.6.14/x64/lib/python3.6/site-packages/setuptools/depends.py:2: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
    import imp

-- Docs: https://docs.pytest.org/en/stable/warnings.html
=========================== short test summary info ============================
FAILED test/test_output_engine.py::TestOutputEngine::test_output_console - Ty...
=========== 1 failed, 270 passed, 258 skipped, 4 warnings in 21.60s ============
Error: Process completed with exit code 1.

So looks like this needs a test update to pass CI.

peb-peb added 3 commits August 8, 2021 17:55
NOTE: can we short the `Text.style` repeating code?
now using VersionInfo NamedTuple
shortened `for ...` loop for adding the column
now affected versions do not repeat the last version range of the table
@codecov-commenter
Copy link

codecov-commenter commented Aug 8, 2021

Codecov Report

Merging #1284 (0e1ca78) into main (0333caa) will increase coverage by 0.25%.
The diff coverage is 92.30%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1284      +/-   ##
==========================================
+ Coverage   79.12%   79.37%   +0.25%     
==========================================
  Files         271      271              
  Lines        4871     4932      +61     
  Branches      586      596      +10     
==========================================
+ Hits         3854     3915      +61     
- Misses        866      868       +2     
+ Partials      151      149       -2     
Flag Coverage Δ
longtests 79.37% <92.30%> (+0.25%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
cve_bin_tool/cve_scanner.py 76.80% <58.33%> (-0.55%) ⬇️
cve_bin_tool/cli.py 75.44% <100.00%> (+0.14%) ⬆️
cve_bin_tool/output_engine/__init__.py 49.03% <100.00%> (+0.66%) ⬆️
cve_bin_tool/output_engine/console.py 95.83% <100.00%> (+2.97%) ⬆️
cve_bin_tool/util.py 74.52% <100.00%> (+1.26%) ⬆️
test/test_output_engine.py 98.41% <100.00%> (+0.13%) ⬆️
test/test_nvd_api.py 79.24% <0.00%> (-20.76%) ⬇️
cve_bin_tool/cvedb.py 82.42% <0.00%> (ø)
test/test_extractor.py 100.00% <0.00%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0333caa...0e1ca78. Read the comment docs.

Comment on lines 42 to 60
if versions:
# should refactor to use python 3.10's "Structural Pattern Matching"
if start_including and end_including:
return f"[{start_including} - {end_including}]"
elif start_including and end_excluding:
return f"[{start_including} - {end_excluding})"
elif start_excluding and end_including:
return f"({start_excluding} - {end_including}]"
elif start_excluding and end_excluding:
return f"({start_excluding} - {end_excluding})"
elif start_including:
return f">= {start_including}"
elif start_excluding:
return f"> {start_excluding}"
elif end_including:
return f"<= {end_including}"
elif end_excluding:
return f"< {end_excluding}"
return "-"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can anyone think of a better approach for this? OR we could just wait for python 3.10 and add the "Structural Pattern Matching" (Maybe we should open a "good first issue" issue for this as a reminder for future)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for Python 3.10 is not an option as cve-bin-tool supports all currently supported Python versions. So 3.9 will go out of support in 2025, only then 3.10 will become the minimum supported version (of course if cve-bin-tool continues to support all versions).

I would still rewrite it a bit because if versions check is useless (tuple of empty strings evaluates to True):

def format_version_range(version_info: VersionInfo) -> str:
    (start_including, start_excluding, end_including, end_excluding) = version_info
    if start_including and end_including:
        return f"[{start_including} - {end_including}]"
    if start_including and end_excluding:
        return f"[{start_including} - {end_excluding})"
    if start_excluding and end_including:
        return f"({start_excluding} - {end_including}]"
    if start_excluding and end_excluding:
        return f"({start_excluding} - {end_excluding})"
    if start_including:
        return f">= {start_including}"
    if start_excluding:
        return f"> {start_excluding}"
    if end_including:
        return f"<= {end_including}"
    if end_excluding:
        return f"< {end_excluding}"
    return "-"

Comment on lines 160 to 190
if affected_versions != 0:
table.add_row(
Text.styled(cve_data["vendor"], color),
Text.styled(cve_data["product"], color),
Text.styled(cve_data["version"], color),
linkify_cve(Text.styled(cve_data["cve_number"], color)),
Text.styled(cve_data["severity"], color),
Text.styled(
str(cve_data["score"])
+ " (v"
+ str(cve_data["cvss_version"])
+ ")",
color,
),
Text.styled(cve_data["affected_versions"], color),
)
else:
table.add_row(
Text.styled(cve_data["vendor"], color),
Text.styled(cve_data["product"], color),
Text.styled(cve_data["version"], color),
linkify_cve(Text.styled(cve_data["cve_number"], color)),
Text.styled(cve_data["severity"], color),
Text.styled(
str(cve_data["score"])
+ " (v"
+ str(cve_data["cvss_version"])
+ ")",
color,
),
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to deduplicate this? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah

            cells = [
                Text.styled(cve_data["vendor"], color),
                Text.styled(cve_data["product"], color),
                Text.styled(cve_data["version"], color),
                linkify_cve(Text.styled(cve_data["cve_number"], color)),
                Text.styled(cve_data["severity"], color),
                Text.styled(
                    f"{cve_data['score']} (v{cve_data['cvss_version']})", color
                ),
            ]
            if affected_versions != 0:
                cells.append(Text.styled(cve_data["affected_versions"], color))
            table.add_row(*cells)

@peb-peb
Copy link
Contributor Author

peb-peb commented Aug 8, 2021

Sorry for replying late :(

@Molkree reviews were really helpful and now I've implemented those changes. :)
@terriko I've added tests for these changes. I did not change the previously available data and have created new mock_data, Is this okay? Or should I implement these in those only? (personally I think that that would be difficult and hard to comprehend)

Comment on lines 114 to 142
if affected_versions != 0:
try:
start_including = dict(all_cve_version_info)[
cve.cve_number
].start_including
start_excluding = dict(all_cve_version_info)[
cve.cve_number
].start_excluding
end_including = dict(all_cve_version_info)[
cve.cve_number
].end_including
end_excluding = dict(all_cve_version_info)[
cve.cve_number
].end_excluding
except KeyError: # TODO: handle 'UNKNOWN' and some cves more cleanly
start_including = ""
start_excluding = ""
end_including = ""
end_excluding = ""
cve_by_remarks[cve.remarks][-1].update(
{
"affected_versions": format_version_range(
start_including,
start_excluding,
end_including,
end_excluding,
)
}
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote a comment for this, but am not able to find it now. So, I'm writing it again :(

Can we do something else (instead of try... except block) to handle the UNKNOWN and some cves which are not found in the all_cve_version_info dictionary?

For UNKNOWN, maybe we could could add a key: value pair of "UNKNOWN": VersionInfo('', '', '', '') at the start/end of this dictionary.

But, when some cves are not found in the all_cve_version_info, what could be a more clean/absolute way of handling both these error?

I'm facing this issue when running it over wireshark-1.10.14-25.el7.i686.rpm and currently I get this:

(cve_env) peb@ooo:/cve-bin-tool$ python3 -m cve_bin_tool.cli -u never --affected-versions ../test/wireshark-1.10.14-25.el7.i686.rpm
┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
┃ Vendor    ┃ Product   ┃ Version ┃ CVE Number     ┃ Severity ┃ Score (CVSS Version) ┃ Affected Versions ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3182  │ MEDIUM   │ 5.5 (v3)             │ -                 │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2015-3814  │ MEDIUM   │ 5 (v2)               │ -                 │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17935 │ HIGH     │ 7.5 (v3)             │ <= 2.2.11         │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-17997 │ HIGH     │ 7.5 (v3)             │ <= 2.2.11         │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2017-6014  │ HIGH     │ 7.5 (v3)             │ <= 2.2.4          │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-14438 │ HIGH     │ 7.5 (v3)             │ <= 2.6.2          │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2018-6836  │ CRITICAL │ 9.8 (v3)             │ <= 2.4.4          │
│ wireshark │ wireshark │ 1.10.14 │ CVE-2020-26575 │ HIGH     │ 7.5 (v3)             │ <= 3.2.7          │
└───────────┴───────────┴─────────┴────────────────┴──────────┴──────────────────────┴───────────────────┘

here, all_cve_version_info returns

defaultdict(<class 'cve_bin_tool.util.VersionInfo'>, 
  {
    'CVE-2017-17935': VersionInfo(start_including='', start_excluding='', end_including='2.2.11', end_excluding=''), 
    'CVE-2017-17997': VersionInfo(start_including='', start_excluding='', end_including='2.2.11', end_excluding=''), 
    'CVE-2017-6014': VersionInfo(start_including='', start_excluding='', end_including='2.2.4', end_excluding=''), 
    'CVE-2018-14438': VersionInfo(start_including='', start_excluding='', end_including='2.6.2', end_excluding=''), 
    'CVE-2018-6836': VersionInfo(start_including='', start_excluding='', end_including='2.4.4', end_excluding=''), 
    'CVE-2020-26575': VersionInfo(start_including='', start_excluding='', end_including='3.2.7', end_excluding='')
  }
)

Here, all_cve_version_info doesn't contain CVE-2015-3182 and CVE-2015-3814.

Comment on lines 116 to 127
start_including = dict(all_cve_version_info)[
cve.cve_number
].start_including
start_excluding = dict(all_cve_version_info)[
cve.cve_number
].start_excluding
end_including = dict(all_cve_version_info)[
cve.cve_number
].end_including
end_excluding = dict(all_cve_version_info)[
cve.cve_number
].end_excluding
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, all_cve_version_info is already a dictionary so you don't need to convert it to dict again.
Second, just use unpacking:

Suggested change
start_including = dict(all_cve_version_info)[
cve.cve_number
].start_including
start_excluding = dict(all_cve_version_info)[
cve.cve_number
].start_excluding
end_including = dict(all_cve_version_info)[
cve.cve_number
].end_including
end_excluding = dict(all_cve_version_info)[
cve.cve_number
].end_excluding
(
start_including,
start_excluding,
end_including,
end_excluding,
) = all_cve_version_info[cve.cve_number]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or go further and:

  1. Add default values "" to your VersionInfo class.
  2. Change format_version_range to accept VersionInfo class.
  3. Rewrite this whole if like this:
            if affected_versions != 0:
                try:
                    version_info = all_cve_version_info[cve.cve_number]
                except KeyError:  # TODO: handle 'UNKNOWN' and some cves more cleanly
                    version_info = VersionInfo()
                cve_by_remarks[cve.remarks][-1].update(
                    {"affected_versions": format_version_range(version_info)}
                )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using all_cve_version_info with defaultdict and was facing issues with it and this was the solution I decided to go with (it never occurred to me that I could just use dict here) :(

        if affected_versions != 0:
            try:
                version_info = all_cve_version_info[cve.cve_number]
            except KeyError:  # TODO: handle 'UNKNOWN' and some cves more cleanly
                version_info = VersionInfo()
            cve_by_remarks[cve.remarks][-1].update(
                {"affected_versions": format_version_range(version_info)}
            )

This was a very clean change :)
But, can we do something better? (OR can we remove # TODO: handle 'UNKNOWN' and some cves more cleanly)

start_including, start_excluding, end_including, end_excluding
):
"""
formats version info to desirable output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
formats version info to desirable output
Format version info to desirable output

Just to be consistent with another function in this module

Comment on lines 42 to 60
if versions:
# should refactor to use python 3.10's "Structural Pattern Matching"
if start_including and end_including:
return f"[{start_including} - {end_including}]"
elif start_including and end_excluding:
return f"[{start_including} - {end_excluding})"
elif start_excluding and end_including:
return f"({start_excluding} - {end_including}]"
elif start_excluding and end_excluding:
return f"({start_excluding} - {end_excluding})"
elif start_including:
return f">= {start_including}"
elif start_excluding:
return f"> {start_excluding}"
elif end_including:
return f"<= {end_including}"
elif end_excluding:
return f"< {end_excluding}"
return "-"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for Python 3.10 is not an option as cve-bin-tool supports all currently supported Python versions. So 3.9 will go out of support in 2025, only then 3.10 will become the minimum supported version (of course if cve-bin-tool continues to support all versions).

I would still rewrite it a bit because if versions check is useless (tuple of empty strings evaluates to True):

def format_version_range(version_info: VersionInfo) -> str:
    (start_including, start_excluding, end_including, end_excluding) = version_info
    if start_including and end_including:
        return f"[{start_including} - {end_including}]"
    if start_including and end_excluding:
        return f"[{start_including} - {end_excluding})"
    if start_excluding and end_including:
        return f"({start_excluding} - {end_including}]"
    if start_excluding and end_excluding:
        return f"({start_excluding} - {end_excluding})"
    if start_including:
        return f">= {start_including}"
    if start_excluding:
        return f"> {start_excluding}"
    if end_including:
        return f"<= {end_including}"
    if end_excluding:
        return f"< {end_excluding}"
    return "-"

Comment on lines 160 to 190
if affected_versions != 0:
table.add_row(
Text.styled(cve_data["vendor"], color),
Text.styled(cve_data["product"], color),
Text.styled(cve_data["version"], color),
linkify_cve(Text.styled(cve_data["cve_number"], color)),
Text.styled(cve_data["severity"], color),
Text.styled(
str(cve_data["score"])
+ " (v"
+ str(cve_data["cvss_version"])
+ ")",
color,
),
Text.styled(cve_data["affected_versions"], color),
)
else:
table.add_row(
Text.styled(cve_data["vendor"], color),
Text.styled(cve_data["product"], color),
Text.styled(cve_data["version"], color),
linkify_cve(Text.styled(cve_data["cve_number"], color)),
Text.styled(cve_data["severity"], color),
Text.styled(
str(cve_data["score"])
+ " (v"
+ str(cve_data["cvss_version"])
+ ")",
color,
),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah

            cells = [
                Text.styled(cve_data["vendor"], color),
                Text.styled(cve_data["product"], color),
                Text.styled(cve_data["version"], color),
                linkify_cve(Text.styled(cve_data["cve_number"], color)),
                Text.styled(cve_data["severity"], color),
                Text.styled(
                    f"{cve_data['score']} (v{cve_data['cvss_version']})", color
                ),
            ]
            if affected_versions != 0:
                cells.append(Text.styled(cve_data["affected_versions"], color))
            table.add_row(*cells)

peb-peb added 2 commits August 9, 2021 18:27
changed `all_cve_version_info` from defaultdict -> dict
handling `KeyError` more cleanly
shortened `table.add_row()` implementation
fixed `format_version_info` docstring
changed variable name to be more pythonic:
versionStartIncluding -> version_start_including
versionStartExcluding -> version_start_excluding
versionEndIncluding -> version_end_including
versionEndExcluding -> version_end_excluding
@terriko terriko merged commit 82de345 into intel:main Aug 11, 2021
@peb-peb peb-peb deleted the safe_packages branch August 12, 2021 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants