diff --git a/crates/pecos-foreign/src/discovery.rs b/crates/pecos-foreign/src/discovery.rs index f91758ceb..a8118c3ce 100644 --- a/crates/pecos-foreign/src/discovery.rs +++ b/crates/pecos-foreign/src/discovery.rs @@ -157,6 +157,9 @@ pub fn load_plugin(path: &Path) -> Result { // Wrap decoder if provided. let decoder = if !desc.decoder_handle.is_null() && !desc.decoder_vtable.is_null() { + // SAFETY: `decoder_vtable` was just null-checked; the plugin protocol + // requires `init_fn` to populate it with a `*const DecoderVTable` that + // points to a vtable owned by the loaded plugin for the plugin's lifetime. let vtable_copy = unsafe { *desc.decoder_vtable }; unsafe { ForeignDecoder::new(desc.decoder_handle, vtable_copy) } } else { @@ -165,6 +168,9 @@ pub fn load_plugin(path: &Path) -> Result { // Wrap simulator if provided. let simulator = if !desc.simulator_handle.is_null() && !desc.simulator_vtable.is_null() { + // SAFETY: `simulator_vtable` was just null-checked; per plugin protocol, + // `init_fn` populates it with a `*const SimulatorVTable` owned by the + // loaded plugin for the plugin's lifetime. let vtable_copy = unsafe { *desc.simulator_vtable }; unsafe { ForeignSimulator::new( diff --git a/crates/pecos-foreign/src/engine.rs b/crates/pecos-foreign/src/engine.rs index 05645b8e4..03f951b4c 100644 --- a/crates/pecos-foreign/src/engine.rs +++ b/crates/pecos-foreign/src/engine.rs @@ -153,7 +153,13 @@ pub unsafe extern "C" fn pecos_engine_process( output_ptr: *mut *mut u8, output_len: *mut usize, ) -> i32 { + if engine.is_null() { + return -1; + } + // SAFETY: null-checked above; per `# Safety` doc, no other reference exists + // for this call (single-threaded FFI contract), so `&mut` aliasing is unique. let eng = unsafe { &mut *engine }; + // SAFETY: per `# Safety` doc, `input_ptr` is valid for `input_len` bytes. let input_bytes = unsafe { std::slice::from_raw_parts(input_ptr, input_len) }; let input = ByteMessage::new(input_bytes); @@ -185,6 +191,10 @@ pub unsafe extern "C" fn pecos_engine_process( /// `engine` must be a valid pointer from `pecos_engine_create`. #[unsafe(no_mangle)] pub unsafe extern "C" fn pecos_engine_reset(engine: *mut PecosEngine) -> i32 { + if engine.is_null() { + return -1; + } + // SAFETY: null-checked above; per `# Safety` doc, exclusively owned for this call. let eng = unsafe { &mut *engine }; match eng.inner.reset() { Ok(()) => 0, @@ -488,3 +498,30 @@ pub unsafe extern "C" fn pecos_free_outcomes(ptr: *mut u32, len: usize) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn process_returns_minus_one_on_null_engine() { + let mut out_ptr: *mut u8 = std::ptr::null_mut(); + let mut out_len: usize = 0; + let rc = unsafe { + pecos_engine_process( + std::ptr::null_mut(), + std::ptr::null(), + 0, + &raw mut out_ptr, + &raw mut out_len, + ) + }; + assert_eq!(rc, -1); + } + + #[test] + fn reset_returns_minus_one_on_null_engine() { + let rc = unsafe { pecos_engine_reset(std::ptr::null_mut()) }; + assert_eq!(rc, -1); + } +} diff --git a/crates/pecos-qis-ffi/src/ffi.rs b/crates/pecos-qis-ffi/src/ffi.rs index a0188cea2..b78cfb42c 100644 --- a/crates/pecos-qis-ffi/src/ffi.rs +++ b/crates/pecos-qis-ffi/src/ffi.rs @@ -1570,7 +1570,8 @@ mod tests { let ptr = unsafe { heap_alloc(100) }; assert!(!ptr.is_null()); - // Write to the memory to verify it's valid + // SAFETY: `ptr` was just allocated by `heap_alloc(100)` and asserted non-null + // above; the test scope owns it exclusively until `heap_free` below. unsafe { std::ptr::write(ptr, 42u8); assert_eq!(std::ptr::read(ptr), 42u8); diff --git a/crates/pecos-qis-ffi/src/lib.rs b/crates/pecos-qis-ffi/src/lib.rs index 5fecf0371..2f7c986c5 100644 --- a/crates/pecos-qis-ffi/src/lib.rs +++ b/crates/pecos-qis-ffi/src/lib.rs @@ -1048,7 +1048,8 @@ mod tests { let ptr = pecos_get_pending_operations(); assert!(!ptr.is_null()); - // Verify operations + // SAFETY: `pecos_get_pending_operations` returns a fresh leaked Box; + // non-null asserted above; the test scope owns it until `pecos_free_operations`. let collector = unsafe { &*ptr }; assert_eq!(collector.operations.len(), 2); @@ -1341,6 +1342,8 @@ mod tests { // Verify operations were exported let ops_ptr = pecos_get_pending_operations(); assert!(!ops_ptr.is_null()); + // SAFETY: `pecos_get_pending_operations` returns a fresh leaked Box; + // non-null asserted above; freed below via `pecos_free_operations`. let ops = unsafe { &*ops_ptr }; assert_eq!(ops.operations.len(), 2); unsafe { pecos_free_operations(ops_ptr) }; @@ -1407,6 +1410,8 @@ mod tests { assert_eq!(needed_id, 0); let ops_ptr = pecos_get_pending_operations(); assert!(!ops_ptr.is_null()); + // SAFETY: `pecos_get_pending_operations` returns a fresh leaked Box; + // non-null asserted above; freed below via `pecos_free_operations`. let ops = unsafe { &*ops_ptr }; assert_eq!(ops.operations, vec![Operation::AllocateQubit { id: 0 }]); unsafe { pecos_free_operations(ops_ptr) }; @@ -1416,6 +1421,8 @@ mod tests { assert_eq!(needed_id, 1); let ops_ptr = pecos_get_pending_operations(); assert!(!ops_ptr.is_null()); + // SAFETY: `pecos_get_pending_operations` returns a fresh leaked Box; + // non-null asserted above; freed below via `pecos_free_operations`. let ops = unsafe { &*ops_ptr }; assert_eq!(ops.operations, vec![Operation::Quantum(QuantumOp::H(0))]); unsafe { pecos_free_operations(ops_ptr) }; diff --git a/docs/requirements.txt b/docs/requirements.txt index dfec0690b..a85de4885 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,3 +4,4 @@ mkdocstrings>=0.24.0 mkdocstrings-python>=1.7.4 markdown-exec>=1.10.3 pymdown-extensions>=10.21.3 +idna>=3.15 # CVE-2026-45409 (re-fix of CVE-2024-3651): idna.encode() DoS on crafted long inputs diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 000000000..1787157a3 --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,67 @@ +# OSV-Scanner configuration for the PECOS workspace. +# +# Ignore list policy: +# - Only include entries we cannot fix from this repo (transitive, blocked on +# an upstream release). +# - Each entry must record (a) the offending crate@version, (b) the chain that +# pulls it in, and (c) the upstream that owns the fix. +# - Real CVEs on first-party deps are NEVER ignored here -- pin and fix them +# in the manifest instead (see docs/requirements.txt for the idna pin). +# - Periodically review this list when bumping hugr/tket, fusion-blossom, or +# mwpf and drop entries that the new upstream version eliminates. + +[[IgnoredVulns]] +id = "RUSTSEC-2025-0057" +# fxhash@0.2.1 -- unmaintained. +# Chain: hugr-passes -> hugr -> tket -> pecos-hugr. +# Upstream owner: https://github.com/CQCL/hugr (awaiting rustc-hash/ahash swap). +reason = "Transitive via hugr/tket; unmaintained-crate warning, no exploit path in PECOS." + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0436" +# paste@1.0.15 -- unmaintained proc-macro. +# Chain: hugr-passes/ascent and mwpf both pull it in. +# Upstream owners: https://github.com/CQCL/hugr and https://github.com/yuewuo/mwpf. +reason = "Transitive via hugr/tket and mwpf; unmaintained-crate warning, no runtime impact (proc-macro)." + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0388" +# derivative@2.2.0 -- unmaintained proc-macro. +# Chain: fusion-blossom and mwpf both pull it in. +# Upstream owners: https://github.com/yuewuo/fusion-blossom and https://github.com/yuewuo/mwpf. +reason = "Transitive via fusion-blossom and mwpf; unmaintained-crate warning, no runtime impact (proc-macro)." + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0384" +# instant@0.1.13 -- unmaintained; replaced by web-time. +# Chain: hugr-passes/ascent -> ... -> pecos-hugr. +# Upstream owner: https://github.com/CQCL/hugr (ascent is a transitive build-graph helper). +reason = "Transitive via hugr-passes/ascent; unmaintained-crate warning, no Wasm target." + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0375" +# atty@0.2.14 -- unmaintained (clap 4.x dropped it). +# Chain: mwpf -> slp -> structopt 0.3 -> clap 2.34. +# Upstream owner: https://github.com/yuewuo/mwpf (awaiting clap 4 / pico-args migration in slp). +reason = "Transitive via mwpf -> slp (clap2-era CLI helper); CLI tty-detect path not exercised by PECOS." + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0370" +# proc-macro-error@1.0.4 -- unmaintained; proc-macro-error2 is the maintained fork. +# Chain: mwpf -> slp -> structopt-derive (compile-time only). +# Upstream owner: https://github.com/yuewuo/mwpf. +reason = "Transitive via mwpf -> slp -> structopt-derive; compile-time proc-macro only, no runtime impact." + +[[IgnoredVulns]] +id = "RUSTSEC-2021-0145" +# atty@0.2.14 -- potential unaligned read on Windows custom allocators. +# Chain: mwpf -> slp -> structopt 0.3 -> clap 2.34. +# Upstream owner: https://github.com/yuewuo/mwpf. +reason = "Transitive via mwpf -> slp -> clap2; PECOS does not invoke slp's CLI tty-detection at runtime." + +[[IgnoredVulns]] +id = "RUSTSEC-2021-0139" +# ansi_term@0.12.1 -- unmaintained. +# Chain: mwpf -> slp -> structopt 0.3 -> clap 2.34. +# Upstream owner: https://github.com/yuewuo/mwpf. +reason = "Transitive via mwpf -> slp -> clap2; unmaintained-crate warning, terminal-color path unused." diff --git a/python/quantum-pecos/docs/requirements.txt b/python/quantum-pecos/docs/requirements.txt index 2bb925489..8b28a56f4 100644 --- a/python/quantum-pecos/docs/requirements.txt +++ b/python/quantum-pecos/docs/requirements.txt @@ -1,5 +1,6 @@ matplotlib==3.9.2 -pillow>=11.3.0 # security floor: Pillow is transitive via matplotlib; avoid known-vulnerable old releases +pillow>=11.3.0 # CVE-2023-50447 + libwebp; transitive via matplotlib +idna>=3.15 # CVE-2026-45409 (re-fix of CVE-2024-3651): idna.encode() DoS on crafted long inputs networkx==3.3 numpy==1.26.4 scipy==1.14.1 diff --git a/scripts/native_bench/bench_pecos/osv-scanner.toml b/scripts/native_bench/bench_pecos/osv-scanner.toml new file mode 100644 index 000000000..d19902915 --- /dev/null +++ b/scripts/native_bench/bench_pecos/osv-scanner.toml @@ -0,0 +1,8 @@ +# OSV-Scanner configuration for the native_bench/bench_pecos workspace. +# See root osv-scanner.toml for policy. + +[[IgnoredVulns]] +id = "RUSTSEC-2024-0436" +# paste@1.0.15 -- unmaintained proc-macro; same chain as workspace root. +# Upstream owner: https://github.com/CQCL/hugr and https://github.com/yuewuo/mwpf. +reason = "Transitive via bench dependencies; unmaintained-crate warning, no runtime impact (proc-macro)."