Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🦀 crates.io

GHSA-p75v-367r-2v23

`cell-project` used incorrect variance when projecting through `&Cell<T>`

Also known asRUSTSEC-2020-0164
Published
Sep 16, 2022
Updated
Nov 8, 2023
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

Blast Radius

1 pkg affected
🦀cell-project

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects crates.io packages — download data is not available via public APIs for these ecosystems.

Description

Overview

The issue lies in the implementation of the cell_project macro which used field as *const _ instead of field as *mut _. The problem being that *const T is covariant in T while *mut T is invariant in T. Keep in mind that &Cell<T> is invariant in T, so casting to *const T relaxed the variance, and lead to unsoundness, as shown in the example below.

use std::cell::Cell;
use cell_project::cell_project as cp;

struct Foo<'a> {
    x: Option<&'a Cell<Foo<'a>>>,
}

impl<'a> Drop for Foo<'a> {
    fn drop(&mut self) {
        // `ourselves` is an &Cell<Self>.
        // NB: `Drop` is unsound.
        if let Some(ourselves) = self.x.as_ref() {
            // replace `self` (but this doesn't actually replace `self`)
            let is_x_none = ourselves.replace(Foo {
                x: None,
            }).x.as_ref().is_none();
            // if we just moved out of `self`, and we had a `Some` originally,
            // how come this is a `None`?
            if is_x_none {
                println!("how did we get a None?");
            }
        }
    }
}

fn main() {
    let foo = Cell::new(Foo {
        x: None,
    });
    let x = cp!(Foo<'_>, foo.x);
    x.set(Some(&foo));
}

MIRI error

$ cargo +nightly miri run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `<snip>`
error: Undefined Behavior: not granting access to tag <untagged> because incompatible item is protected: [Unique for <2472> (call 796)]
   --> $RUST_STD_PATH/src/rust/library/core/src/cell.rs:404:31
    |
404 |         mem::replace(unsafe { &mut *self.value.get() }, val)
    |                               ^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <untagged> because incompatible item is protected: [Unique for <2472> (call 796)]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information

    = note: inside `std::cell::Cell::<Foo>::replace` at $RUST_STD_PATH/src/rust/library/core/src/cell.rs:404:31
note: inside `<Foo as std::ops::Drop>::drop` at src/main.rs:14:29
   --> src/main.rs:14:29
    |
14  |               let is_x_none = ourselves.replace(Foo {
    |  _____________________________^
15  | |                 x: None,
16  | |             }).x.as_ref().is_none();
    | |______________^
    = note: inside `std::ptr::drop_in_place::<Foo> - shim(Some(Foo))` at $RUST_STD_PATH/src/rust/library/core/src/ptr/mod.rs:486:1
    = note: inside `std::ptr::drop_in_place::<std::cell::UnsafeCell<Foo>> - shim(Some(std::cell::UnsafeCell<Foo>))` at $RUST_STD_PATH/src/rust/library/core/src/ptr/mod.rs:486:1
    = note: inside `std::ptr::drop_in_place::<std::cell::Cell<Foo>> - shim(Some(std::cell::Cell<Foo>))` at $RUST_STD_PATH/src/rust/library/core/src/ptr/mod.rs:486:1
note: inside `main` at src/main.rs:32:1
   --> src/main.rs:32:1
    |
32  | }
    | ^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

Affected Versions

All versions of the cell-project crate before 0.1.4 are affected.

Mitigation

This was fixed in Issues/4, and released as version 0.1.4. So just updating to the latest version will include the fix (which may result in a compile error on unsound usage).

Acknowledgements

This was discovered and fixed by @SoniEx2 in cell-project: Issues/3 and Issues/4

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🦀crates.iocell-projectall versions0.1.4

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for cell-project. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update cell-project to 0.1.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-p75v-367r-2v23 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-p75v-367r-2v23 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-p75v-367r-2v23. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Overview The issue lies in the implementation of the `cell_project` macro which used `field as *const _` instead of `field as *mut _`. The problem being that `*const T` is covariant in `T` while `*mut T` is invariant in `T`. Keep in mind that `&Cell<T>` is invariant in `T`, so casting to `*const T` relaxed the variance, and lead to unsoundness, as shown in the example below. ```rs use std::cell::Cell; use cell_project::cell_project as cp; struct Foo<'a> { x: Option<&'a Cell<Foo<'a>>>, } impl<'a> Drop for Foo<'a> { fn drop(&mut self) { // `ourselves` is an &Cell<Self>.
O3 Security · Impact-Aware SCA

Is GHSA-p75v-367r-2v23 in your dependencies?

O3 detects GHSA-p75v-367r-2v23 across crates.io dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.