From: Sachin Sant <sachinp@linux.ibm.com>
To: ltp@lists.linux.it
Subject: [LTP] [PATCH v2] doc: generate CVE reproducer statistics
Date: Thu, 23 Apr 2026 16:54:39 +0530 [thread overview]
Message-ID: <20260423112439.69230-1-sachinp@linux.ibm.com> (raw)
Add a Sphinx builder hook to parse runtest/cve, collect CVE
reproducer metadata, and generate a documentation page with
per-year counts and links to CVE entries and test sources.
Also include the generated CVE reproducer statistics page from
doc/users/stats.rst.
Closes: https://github.com/linux-test-project/ltp/issues/1254
Signed-off-by: Sachin Sant <sachinp@linux.ibm.com>
---
V2 changes:
- Replace Fixes tag by Closes
- V1 link https://lore.kernel.org/ltp/20260423105304.59788-1-sachinp@linux.ibm.com/T/#u
---
doc/conf.py | 169 ++++++++++++++++++++++++++++++++++++++++++++
doc/users/stats.rst | 2 +
2 files changed, 171 insertions(+)
diff --git a/doc/conf.py b/doc/conf.py
index 63d09352e..19e744d79 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -535,6 +535,174 @@ def generate_test_catalog(_):
with open(output, 'w+', encoding='utf-8') as new_tests:
new_tests.write('\n'.join(text))
+def generate_cve_stats(_):
+ """
+ Generate statistics for CVE reproducers. Parse runtest/cve file,
+ scan testcases directories, and generate documentation with links
+ to CVE databases and test sources.
+ """
+ output = '_static/cve_reproducers.rst'
+ runtest_cve = '../runtest/cve'
+
+ text = [
+ 'CVE Reproducers\n',
+ '---------------\n\n',
+ 'LTP includes reproducers for known CVEs. These tests help verify\n',
+ 'that systems are patched against known vulnerabilities.\n\n',
+ ]
+
+ # Parse runtest/cve file
+ cve_data = {}
+ cve_pattern = re.compile(r'^(cve-(\d{4})-\d+)\s+(\S+)(?:\s+(.*))?$')
+
+ try:
+ with open(runtest_cve, 'r', encoding='utf-8') as f:
+ for line in f:
+ line = line.strip()
+ # Skip comments and empty lines
+ if not line or line.startswith('#'):
+ continue
+
+ match = cve_pattern.match(line)
+ if match:
+ cve_id = match.group(1).upper()
+ year = match.group(2)
+ test_name = match.group(3)
+ options = match.group(4) if match.group(4) else ''
+
+ cve_data[cve_id] = {
+ 'cve_id': cve_id,
+ 'year': year,
+ 'test_name': test_name,
+ 'options': options,
+ 'source_path': None,
+ 'description': ''
+ }
+ except FileNotFoundError:
+ logger = sphinx.util.logging.getLogger(__name__)
+ msg = f"Can't find runtest/cve file ({runtest_cve})"
+ logger.warning(msg)
+
+ with open(output, 'w+', encoding='utf-8') as stats:
+ stats.write(f".. warning::\n\n {msg}")
+ return
+
+ # Scan for CVE test source files
+ testcases_dirs = [
+ '../testcases/cve',
+ '../testcases/kernel/syscalls',
+ '../testcases/kernel/mem',
+ '../testcases/kernel/pty',
+ ]
+
+ # Build a mapping of all potential source files
+ # Key: base filename without extension, Value: full path
+ source_files = {}
+ for base_dir in testcases_dirs:
+ if not os.path.exists(base_dir):
+ continue
+
+ for dirpath, _, files in os.walk(base_dir):
+ for filename in files:
+ if filename.endswith('.c') or filename.endswith('.sh'):
+ # Store base name without extension
+ base_name = filename.rsplit('.', 1)[0]
+ rel_path = os.path.join(
+ dirpath.replace('../', ''),
+ filename
+ )
+ source_files[base_name] = rel_path
+
+ # Match CVE entries with source files
+ for cve_id, cve_info in cve_data.items():
+ test_name = cve_info['test_name']
+ source_path = None
+
+ if test_name in source_files:
+ source_path = source_files[test_name]
+ elif cve_id.lower() in source_files:
+ source_path = source_files[cve_id.lower()]
+
+ if source_path:
+ cve_info['source_path'] = source_path
+
+ try:
+ src_file = os.path.join('..', source_path)
+ with open(src_file, 'r', encoding='utf-8') as src:
+ in_comment = False
+ desc_lines = []
+ for line in src:
+ line = line.strip()
+ if line.startswith('/*\\'):
+ in_comment = True
+ continue
+ if in_comment:
+ if line.endswith('*/'):
+ break
+ if line.startswith('*'):
+ desc_line = line[1:].strip()
+ if desc_line:
+ desc_lines.append(desc_line)
+
+ if desc_lines:
+ # Use first non-empty line as description
+ cve_info['description'] = desc_lines[0]
+ except (IOError, UnicodeDecodeError):
+ pass
+
+ # Generate statistics
+ total_cves = len(cve_data)
+ cves_by_year = {}
+ for cve_info in cve_data.values():
+ year = cve_info['year']
+ cves_by_year[year] = cves_by_year.get(year, 0) + 1
+
+ text.append(f'* **Total CVEs tested:** {total_cves}\n')
+ text.append('* **CVEs by year:**\n\n')
+
+ for year in sorted(cves_by_year.keys()):
+ text.append(f' * {year}: {cves_by_year[year]} CVEs\n')
+
+ text.append('\n')
+
+ # Generate CVE table
+ text.extend([
+ 'CVE List\n',
+ '~~~~~~~~\n\n',
+ '.. list-table::\n',
+ ' :header-rows: 1\n',
+ ' :widths: 20 25 10 45\n\n',
+ ' * - CVE ID\n',
+ ' - Test Name\n',
+ ' - Year\n',
+ ' - Description\n',
+ ])
+
+ # Sort CVEs by ID (chronologically)
+ for cve_id in sorted(cve_data.keys()):
+ cve_info = cve_data[cve_id]
+
+ cve_url = f"https://cve.mitre.org/cgi-bin/cvename.cgi?name={cve_id}"
+ cve_link = f"`{cve_id} <{cve_url}>`_"
+
+ if cve_info['source_path']:
+ test_url = f"{ltp_repo_base_url}/{cve_info['source_path']}"
+ test_link = f"`{cve_info['test_name']} <{test_url}>`__"
+ else:
+ test_link = cve_info['test_name']
+
+ description = cve_info['description'] if cve_info['description'] else 'CVE reproducer test'
+
+ text.extend([
+ f" * - {cve_link}\n",
+ f" - {test_link}\n",
+ f" - {cve_info['year']}\n",
+ f" - {description}\n",
+ ])
+
+ with open(output, 'w+', encoding='utf-8') as stats:
+ stats.writelines(text)
+
def setup(app):
"""
@@ -543,4 +711,5 @@ def setup(app):
"""
app.add_css_file('custom.css')
app.connect('builder-inited', generate_syscalls_stats)
+ app.connect('builder-inited', generate_cve_stats)
app.connect('builder-inited', generate_test_catalog)
diff --git a/doc/users/stats.rst b/doc/users/stats.rst
index 7073442aa..9ecf806bb 100644
--- a/doc/users/stats.rst
+++ b/doc/users/stats.rst
@@ -7,3 +7,5 @@ In this section we collect some statistics related to the current state of
LTP tests.
.. include:: ../_static/syscalls.rst
+
+.. include:: ../_static/cve_reproducers.rst
--
2.39.1
--
Mailing list info: https://lists.linux.it/listinfo/ltp
next reply other threads:[~2026-04-23 11:25 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-23 11:24 Sachin Sant [this message]
2026-04-23 12:31 ` [LTP] [PATCH v2] doc: generate CVE reproducer statistics Petr Vorel
2026-04-23 13:17 ` Sachin Sant
2026-04-23 13:32 ` [LTP] " linuxtestproject.agent
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260423112439.69230-1-sachinp@linux.ibm.com \
--to=sachinp@linux.ibm.com \
--cc=ltp@lists.linux.it \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox