diff --git a/crates/cgmath/RUSTSEC-0000-0000.md b/crates/cgmath/RUSTSEC-0000-0000.md new file mode 100644 index 000000000..f5ef27340 --- /dev/null +++ b/crates/cgmath/RUSTSEC-0000-0000.md @@ -0,0 +1,96 @@ +```toml +[advisory] +id = "RUSTSEC-0000-0000" +package = "cgmath" +date = "2026-03-11" +url = "https://github.com/rustgd/cgmath/issues/565" +informational = "unsound" +categories = ["memory-corruption"] +keywords = ["soundness", "undefined-behavior", "aliasing", "stacked-borrows"] + +[affected.functions] +"cgmath::Matrix2::swap_columns" = ["= 0.18.0"] +"cgmath::Matrix3::swap_columns" = ["= 0.18.0"] +"cgmath::Matrix4::swap_columns" = ["= 0.18.0"] + +[versions] +patched = [] +``` + +# `Matrix{2,3,4}::swap_columns` can trigger undefined behavior for identical indices + +The `Matrix2::swap_columns`, `Matrix3::swap_columns`, and `Matrix4::swap_columns` +implementations call `ptr::swap(&mut self[a], &mut self[b])`. + +When `a == b`, these safe APIs create two mutable references to the same matrix +column and pass them to `ptr::swap`. This violates Rust's aliasing rules and can +trigger undefined behavior. The issue can be reproduced from safe Rust by calling +`swap_columns` with identical column indices, for example `m.swap_columns(0, 0)`. + +## Example + +The issue can be triggered without unsafe code in the caller: + +```rust +use cgmath::{Matrix, Matrix2}; + +fn main() { + let mut m = Matrix2::new(1.0, 2.0, 3.0, 4.0); + m.swap_columns(0, 0); +} +``` + +```rust +use cgmath::{Matrix, Matrix3}; + +fn main() { + let mut m = Matrix3::new( + 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, + 7.0, 8.0, 9.0, + ); + m.swap_columns(1, 1); +} +``` + +```rust +use cgmath::{Matrix, Matrix4}; + +fn main() { + let mut m = Matrix4::new( + 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0, + ); + m.swap_columns(2, 2); +} +``` + +## Miri output + +The issue was validated with Miri on `cgmath` 0.18.0. For the `Matrix2` +reproducer, Miri reports: + +```text +error: Undefined Behavior: attempting a read access using <281> at alloc120[0x0], +but that tag does not exist in the borrow stack for this location + --> core/src/ptr/mod.rs:1308:9 + | +1308 | copy_nonoverlapping(x, tmp.as_mut_ptr(), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: inside `std::ptr::swap::>` + = note: inside ` as cgmath::Matrix>::swap_columns` +note: inside `main` + --> src/main.rs:9:5 + | +9 | m.swap_columns(0, 0); + | ^^^^^^^^^^^^^^^^^^^^ +``` + +The `Matrix3` and `Matrix4` reproducers produce the same class of Stacked +Borrows violation, with the backtrace pointing to their corresponding +`swap_columns` implementations. + +A minimal fix is to return early when the two column indices are equal before +calling `ptr::swap`.