Skip to content

fix: validate genesis file type and size before reading (PM-19964)#832

Merged
m2ux merged 13 commits into
mainfrom
fix/PM-19964-unbounded-genesis-file-reads
Mar 6, 2026
Merged

fix: validate genesis file type and size before reading (PM-19964)#832
m2ux merged 13 commits into
mainfrom
fix/PM-19964-unbounded-genesis-file-reads

Conversation

@m2ux

@m2ux m2ux commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Validate genesis file type and size before reading to address Least Authority audit Issue AI (unbounded reads in Cfg::load_spec). Introduces a validated_file module that rejects symlinks, non-regular files, and files exceeding 10 MB across all 12 genesis/config file read sites in the cfg subsystem.

🎫 Ticket 📐 Engineering 🧪 Test Plan


Motivation

The Least Authority security audit (Issue AI) identified that Cfg::load_spec reads genesis and configuration files using std::fs::read and std::fs::read_to_string without validating file type or size. This allows potential denial-of-service through oversized files, symlink traversal to unintended paths, or non-file inputs (e.g., directories, device files).

All file reads in the cfg module occur during node startup and process operator-supplied configuration files, making pre-read validation a straightforward mitigation with no performance impact on normal operation.


Changes

  • validated_file module — New safe_read() and safe_read_to_string() functions that validate file metadata before reading: reject symlinks (via symlink_metadata), reject non-regular files, enforce 10 MB size limit (MAX_GENESIS_FILE_SIZE)
  • cfg/mod.rs — 10 std::fs::read/read_to_string calls in load_spec replaced with validated variants
  • meta_cfg/mod.rs — 1 std::fs::read_to_string call in CfgPreset::load_config replaced
  • substrate_cfg/mod.rs — 1 std::fs::read_to_string call in SubstrateCfg::try_from replaced
  • Tests — 16 unit tests covering happy paths, boundary conditions (0 bytes, exact limit, limit+1), error paths (symlink, directory, oversized, nonexistent), and error message quality
  • Test results — 29 passed, 0 failed (cargo test -p midnight-node); cargo clippy -D warnings clean

📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason: included (changes/changed/validate-genesis-file-reads.md)
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • No new todos introduced

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other
  • N/A

🗹 TODO before merging

  • Ready for review

@github-actions

github-actions Bot commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

kics-logo

KICS version: v2.1.19

Category Results
CRITICAL CRITICAL 0
HIGH HIGH 0
MEDIUM MEDIUM 47
LOW LOW 3
INFO INFO 59
TRACE TRACE 0
TOTAL TOTAL 109
Metric Values
Files scanned placeholder 26
Files parsed placeholder 26
Files failed to scan placeholder 0
Total executed queries placeholder 73
Queries failed to execute placeholder 0
Execution time placeholder 11

@m2ux m2ux force-pushed the fix/PM-19964-unbounded-genesis-file-reads branch from e24ebb4 to 29eb70e Compare March 2, 2026 17:30
@m2ux m2ux marked this pull request as ready for review March 2, 2026 18:03
@m2ux m2ux requested a review from a team as a code owner March 2, 2026 18:03
m2ux added 10 commits March 5, 2026 15:16
Unbounded genesis file reads in Cfg::load_spec — security audit finding.
See: https://shielded.atlassian.net/browse/PM-19964

Made-with: Cursor
Introduce validated_file module with safe_read() and
safe_read_to_string() that validate file metadata before reading:
- Reject symlinks via symlink_metadata()
- Reject non-regular files (directories, devices, FIFOs)
- Enforce configurable max file size (default 10 MB)

Includes 14 unit tests covering happy paths, rejection paths,
boundary conditions, and error message quality.

Ref: PM-19964
Made-with: Cursor
Replace all 10 std::fs::read / std::fs::read_to_string calls in
Cfg::load_spec() with validated_file::safe_read and
safe_read_to_string. Files are now validated for type and size
before reading.

Ref: PM-19964
Made-with: Cursor
Replace std::fs::read_to_string with validated_file::safe_read_to_string
in the filesystem fallback path of CfgPreset::load_config(). Built-in
presets via get_config() are unaffected.

Ref: PM-19964
Made-with: Cursor
Replace std::fs::read_to_string with validated_file::safe_read_to_string
for node key file reading in SubstrateCfg::try_from(). Error mapped to
sc_cli::Error::Input.

Ref: PM-19964
Made-with: Cursor
… size limit

Addresses L1 finding from test suite review — safe_read_to_string
variants were implicitly covered via shared validate_file_metadata
but lacked explicit boundary tests.

Made-with: Cursor
Replace `|e| ConfigError::Message(e)` with `ConfigError::Message` and
`|e| sc_cli::Error::Input(e)` with `sc_cli::Error::Input` to satisfy
clippy::redundant_closure.

Made-with: Cursor
@m2ux m2ux force-pushed the fix/PM-19964-unbounded-genesis-file-reads branch from 48670b6 to eb06e39 Compare March 5, 2026 15:17
m2ux and others added 2 commits March 5, 2026 15:18
Fixes clippy::useless_vec on Rust 1.93 — use &[..] instead of
&vec![..] for fixed-size test data in write_all calls.

Ref: PM-19964
Made-with: Cursor
@m2ux m2ux self-assigned this Mar 5, 2026
@m2ux m2ux enabled auto-merge March 5, 2026 16:45
@m2ux m2ux added this pull request to the merge queue Mar 6, 2026
Merged via the queue into main with commit ebc469b Mar 6, 2026
34 checks passed
@m2ux m2ux deleted the fix/PM-19964-unbounded-genesis-file-reads branch March 6, 2026 12:09
@gilescope gilescope added this to the node-1.0.0 milestone Apr 10, 2026
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.

3 participants