@@ -2,46 +2,72 @@ from __future__ import annotations
Show 9 hidden lines
+import re from pathlib import Path from graphify.extractors.base import _file_stem, _make_id, _read_text _FORTRAN_CPP_EXTS = {".F", ".F90", ".F95", ".F03", ".F08"} +# A cpp `#include` directive at the start of a (optionally indented) line, in+# either the `"..."` or `<...>` form. Matched on raw bytes before preprocessing.
+_INCLUDE_DIRECTIVE_RE = re.compile(rb"^[ \t]*#[ \t]*include\b")+++def _strip_include_directives(source: bytes) -> bytes:+ """Blank every cpp `#include` directive, preserving line numbers.++ cpp always searches the directory of the file being preprocessed for quoted
Show 15 hidden lines
+ includes and always honours absolute include paths, regardless of+ `-nostdinc`/`-I` — so a malicious capital-F Fortran source containing+ `#include "/etc/passwd"` or `#include "../../../secret"` would inline+ arbitrary host files into the graph we ship to the LLM and persist to disk+ (GHSA-pcc4-rvhr-2pr8, CWE-22/73/200). The `-nostdinc -I /dev/null` mitigation+ did not cover those two cases. Removing the directives entirely keeps macro+ expansion (the reason capital-F files need cpp — `#ifdef MPI`, `#define+ REAL8`) while making host-file reads impossible. Cross-file include fidelity+ is lost, which is an acceptable trade for closing an arbitrary-read vector on+ the default offline path. Each stripped line is blanked (not deleted) so+ reported line numbers still match the original source.+ """+ return b"\n".join(+ b"" if _INCLUDE_DIRECTIVE_RE.match(line) else line+ for line in source.split(b"\n")
+ )++ def _cpp_preprocess(path: Path) -> bytes: """Run cpp -w -P on a capital-F Fortran file and return preprocessed bytes. - Falls back to raw file bytes if cpp is not available. Capital-F extensions
Show 11 hidden lines
- conventionally require C preprocessor expansion (#ifdef MPI, #define REAL8, etc.)- before parsing.-- Security (F-007): we pass `-nostdinc` and `-I /dev/null` so a malicious- source file containing `#include "/home/victim/.ssh/id_rsa"` (or any other- include directive) cannot inline arbitrary host files into the output that- we then ship to an LLM. Without these flags `cpp` happily resolves any- relative or absolute include path it can read, which is a corpus-side- file-exfiltration vector.+ Falls back to (include-stripped) raw file bytes if cpp is not available.+ Capital-F extensions conventionally require C preprocessor expansion
+ (#ifdef MPI, #define REAL8, etc.) before parsing.++ Security (GHSA-pcc4-rvhr-2pr8): every `#include` directive is stripped before+ preprocessing (see `_strip_include_directives`) and the sanitized source is+ fed to cpp on stdin, so cpp never opens the source file's directory or any+ absolute/traversing include path — closing the corpus-side arbitrary+ file-read/exfiltration vector that `-nostdinc -I /dev/null` alone left open.
Show 2 hidden lines
+ Feeding stdin (rather than a file path) also removes the `-I/etc/x.F90`+ argument-injection edge case entirely.
""" import shutil import subprocess+ safe = _strip_include_directives(path.read_bytes()) if not shutil.which("cpp"):- return path.read_bytes()+ return safe
Show 21 hidden lines
try:- # Pass an absolute path so a corpus file named like "-I/etc/x.F90" cannot- # be parsed by cpp as an option (cpp does not accept a "--" end-of-options- # terminator). What matters is that an absolute path cannot begin with- # "-" — not that it begins with "/", which only holds on POSIX (a Windows- # absolute path starts with a drive letter, and is equally safe). result = subprocess.run(- ["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null", str(path.resolve())],+ ["cpp", "-w", "-P", "-nostdinc", "-I", "/dev/null"],+ input=safe, capture_output=True, timeout=30, ) if result.returncode == 0 and result.stdout: return result.stdout except Exception: pass- return path.read_bytes()+ return safe def extract_fortran(path: Path) -> dict:
The fix adds _strip_include_directives, which blanks every plain #include line, and _cpp_preprocess now feeds the sanitized source to cpp on stdin, so absolute and parent-directory includes can no longer pull host files into graph data.