Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions crates/cgmath/RUSTSEC-0000-0000.md
Original file line number Diff line number Diff line change
@@ -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::<cgmath::Vector2<f64>>`
= note: inside `<cgmath::Matrix2<f64> 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`.