Matches upstream advisory data against the OTP and OpenSSL versions
fingerprinted from a cached runtime, and the SQLite version compiled
into the project's exqlite, producing Findings.
Pure: callers pass the decoded feed bodies (fetched or cached by
MobDev.SecurityScan.BundledRuntime.AdvisoryFeed) and the installed
versions. Four sources:
- Erlang/OTP — the GitHub repository security advisories of
erlang/otp. OSV.dev is no help here: the EEF records it carries useGITranges only, so apkg:otp/ssl@11.7query returns nothing. - OpenSSL —
openssl-library.org/news/vulnerabilities-<branch>/, one page per release branch. OpenSSL retired its JSON feed; the HTML page is the published record. - OpenSSL branch EOL —
endoflife.date/api/openssl.json. - SQLite — the "Status Of Recent SQLite CVEs" table on
www.sqlite.org/cves.html: CVE ids, the release that fixed them (or "Not a bug in SQLite", which is skipped) and a comment.
How an OTP advisory is matched
Each advisory lists vulnerabilities[], one entry per package: the
umbrella OTP release plus the individual applications (ssl,
public_key, erts, ...). vulnerable_version_range is the
introduction point (">= 9.5") and patched_versions names one fix
per maintained branch, e.g. "11.7.8, 11.6.0.7, 11.2.12.14".
Application entries are matched against the application versions in
the tarball's lib/ (what actually gets packaged); the OTP entry is
only used when an advisory names no application, because the app
entries are the precise statement and an app the runtime doesn't ship
can't be vulnerable in it. A package may appear more than once with
different introduction points; its entries are merged.
OTP and application versions form a tree, not a line: 11.6.0.7 is a
maintenance branch off 11.6, and 11.7 neither contains nor precedes
it. otp_compare/2 implements the documented comparison (OTP's
versions:compare/2, "Version Scheme" in the OTP system principles),
which answers :same, :ancestor, :descendant or :undefined;
the advisories use >= in that sense. An installed version is:
- affected when it is the same as, or a descendant of, some entry's introduction point;
- fixed when it is the same as, or a descendant of, some patched version;
- vulnerable when affected and not fixed. The fix reported is the
patched version descending from it that shares the longest prefix
with it (
11.7→11.7.8,11.2→11.2.12.14), or none when no patched version descends from it (an unmaintained branch).
A release candidate (29.0-rc3) is treated as an ancestor of its
release.
Summary
Types
Outcome of matching a branch against endoflife.date data.
Versions fingerprinted from one runtime tarball. :otp is the full
OTP release version (releases/*/OTP_VERSION); :apps lists
{name, version} for each application directory, including erts.
Functions
The OpenSSL release branch a version belongs to, which names its
advisory page: 3.4.0 → 3.4, 1.1.1w → 1.1.1 (pre-3.0 branches
were three components wide).
Compare two OpenSSL versions, which are linearly ordered: dotted numbers
with an optional letter suffix (1.1.1zj). Missing components count as
zero; letter suffixes order by length, then alphabetically (z < za).
A pre-release tail (3.4.0-beta1) compares as its release, so a beta
is reported against the release's CVEs rather than none.
Whether the OpenSSL branch of version is past end of life on today
according to endoflife.date data. A branch is treated as EOL from its
EOL date onward: on that day it stops receiving fixes. eol: true
(end of life, date not given) is EOL with a nil date.
Findings for every CVE on a branch page whose affected range on the installed version's branch contains it.
URL of the advisory page for an OpenSSL branch.
Human-facing index of the Erlang/OTP advisories.
Compare two OTP or OTP-application versions per the OTP version scheme:
how v1 relates to v2 in the version tree. :error when either is
not a version.
Findings for every published, non-withdrawn OTP advisory that affects the installed versions.
Verdict for one installed version against the merged advisory
entries for its package. See the moduledoc for the rules.
Parse an openssl-library.org/news/vulnerabilities-<branch>/ page into
%{cve, severity, title, affected: [{from, before | nil}]} entries.
Parse the "Status Of Recent SQLite CVEs" table of sqlite.org/cves.html
into one %{cves, fixed_in, severity, comment} entry per row.
Map an upstream severity label (GitHub critical/high/medium/low,
OpenSSL Critical/High/Moderate/Low) to a Finding severity.
URL of SQLite's CVE status page.
Findings for every CVE on the SQLite page whose fixing release is
newer than version. Disputed rows are skipped. Severity is the
page's, or :medium when the page gives none.
Whether version is a dotted version the SQLite matcher can compare.
Types
@type eol_status() :: {:eol, Date.t() | nil, MobDev.SecurityScan.Finding.t()} | {:supported, Date.t() | nil} | :unknown_branch
Outcome of matching a branch against endoflife.date data.
Versions fingerprinted from one runtime tarball. :otp is the full
OTP release version (releases/*/OTP_VERSION); :apps lists
{name, version} for each application directory, including erts.
Functions
The OpenSSL release branch a version belongs to, which names its
advisory page: 3.4.0 → 3.4, 1.1.1w → 1.1.1 (pre-3.0 branches
were three components wide).
Compare two OpenSSL versions, which are linearly ordered: dotted numbers
with an optional letter suffix (1.1.1zj). Missing components count as
zero; letter suffixes order by length, then alphabetically (z < za).
A pre-release tail (3.4.0-beta1) compares as its release, so a beta
is reported against the release's CVEs rather than none.
@spec openssl_eol([map()], String.t(), Date.t()) :: eol_status()
Whether the OpenSSL branch of version is past end of life on today
according to endoflife.date data. A branch is treated as EOL from its
EOL date onward: on that day it stops receiving fixes. eol: true
(end of life, date not given) is EOL with a nil date.
@spec openssl_findings([map()], String.t(), String.t()) :: [ MobDev.SecurityScan.Finding.t() ]
Findings for every CVE on a branch page whose affected range on the installed version's branch contains it.
URL of the advisory page for an OpenSSL branch.
@spec otp_advisories_url() :: String.t()
Human-facing index of the Erlang/OTP advisories.
Compare two OTP or OTP-application versions per the OTP version scheme:
how v1 relates to v2 in the version tree. :error when either is
not a version.
@spec otp_findings([map()], installed()) :: [MobDev.SecurityScan.Finding.t()]
Findings for every published, non-withdrawn OTP advisory that affects the installed versions.
Verdict for one installed version against the merged advisory
entries for its package. See the moduledoc for the rules.
Parse an openssl-library.org/news/vulnerabilities-<branch>/ page into
%{cve, severity, title, affected: [{from, before | nil}]} entries.
Returns {:error, :unrecognised_page} when the page doesn't parse the
way the published layout does, so a redesigned or wrong page fails
loudly instead of reading as "no CVEs": it must carry the
Vulnerabilities <branch> heading, every CVE block must yield a known
severity and at least one affected range, and a page that mentions a
CVE must yield at least one block.
Parse the "Status Of Recent SQLite CVEs" table of sqlite.org/cves.html
into one %{cves, fixed_in, severity, comment} entry per row.
fixed_in is the release named in the Fix column, or nil for rows
SQLite disputes ("Not a bug in SQLite", "unreproducible"); those are
kept so callers can count them, never matched. severity is the
page's Severity column when it has one (it has none today), else nil.
Returns {:error, :unrecognised_page} unless the page carries the
table heading and its CVE Number / Fix / Comments columns, every row
names a CVE and has a Fix cell that is a version or a recognised
dispute, and at least one row names a fix. A redesigned page then
fails loudly instead of reading as "no CVEs".
@spec severity(String.t() | nil) :: MobDev.SecurityScan.Finding.severity()
Map an upstream severity label (GitHub critical/high/medium/low,
OpenSSL Critical/High/Moderate/Low) to a Finding severity.
@spec sqlite_cves_url() :: String.t()
URL of SQLite's CVE status page.
@spec sqlite_findings([map()], String.t()) :: [MobDev.SecurityScan.Finding.t()]
Findings for every CVE on the SQLite page whose fixing release is
newer than version. Disputed rows are skipped. Severity is the
page's, or :medium when the page gives none.
Whether version is a dotted version the SQLite matcher can compare.