VYPR
Moderate severityNVD Advisory· Published Dec 31, 2020· Updated Aug 4, 2024

CVE-2020-35899

CVE-2020-35899

Description

actix-service crate before 1.0.6 implements an unsound Cell that allows multiple mutable references to the same data, violating Rust's memory safety guarantees.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

actix-service crate before 1.0.6 implements an unsound Cell that allows multiple mutable references to the same data, violating Rust's memory safety guarantees.

Vulnerability

Description

The actix-service crate, prior to version 1.0.6, provided a bespoke implementation of a Cell type [1][2]. This custom cell used Rc::as_ref() to obtain references to the underlying data without distinguishing between mutable and immutable references [4]. As a result, calling get_mut() on the same Cell instance could yield multiple mutable references to the same memory location, directly violating Rust's core guarantee of exclusive mutable access [2][4].

Exploitation

This unsoundness can be triggered through the public API of the crate [4]. An attacker with the ability to invoke get_mut() on the same cell repeatedly can obtain two or more mutable references simultaneously, leading to undefined behavior (UB) [2][4]. The issue is classified as a "Low" severity vulnerability (CVSS 5.5, MEDIUM) with a local attack vector, low privileges required, and no user interaction needed [2].

Impact

The impact is primarily on memory safety. Concurrent mutable references can lead to arbitrary memory corruption, most likely a use-after-free [4]. This can result in a denial-of-service condition (availability impact rated HIGH), though confidentiality and integrity are not directly affected [2]. The vulnerability is categorized under memory corruption [2].

Mitigation

The fix was implemented in pull request #158 and merged into the repository [3][4]. The unsound custom Cell was removed and replaced with Rc<RefCell>, which enforces Rust's borrowing rules at runtime [3][4]. Users should update to actix-service version 1.0.6 or later [2]. The advisory notes that performance impact is expected to be negligible [4].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
actix-servicecrates.io
< 1.0.61.0.6

Affected products

2

Patches

1
a67e38b4a07c

Remove unsound custom Cell (#158)

https://github.com/actix/actix-netSergey "Shnatsel" DavidoffJul 19, 2020via ghsa
7 files changed · +49 98
  • actix-service/CHANGES.md+6 0 modified
    @@ -1,5 +1,11 @@
     # Changes
     
    +## Unreleased
    +
    +### Fixed
    +
    +* Removed unsound custom Cell implementation that allowed obtaining several mutable references to the same data, which is undefined behavior in Rust and could lead to violations of memory safety. External code could obtain several mutable references to the same data through service combinators. Attempts to acquire several mutable references to the same data will instead result in a panic.
    +
     ## [1.0.5] - 2020-01-16
     
     ### Fixed
    
  • actix-service/src/and_then_apply_fn.rs+11 11 modified
    @@ -2,9 +2,9 @@ use std::future::Future;
     use std::marker::PhantomData;
     use std::pin::Pin;
     use std::rc::Rc;
    +use std::cell::RefCell;
     use std::task::{Context, Poll};
     
    -use crate::cell::Cell;
     use crate::{Service, ServiceFactory};
     
     /// `Apply` service combinator
    @@ -16,7 +16,7 @@ where
         Fut: Future<Output = Result<Res, Err>>,
         Err: From<A::Error> + From<B::Error>,
     {
    -    srv: Cell<(A, B, F)>,
    +    srv: Rc<RefCell<(A, B, F)>>,
         r: PhantomData<(Fut, Res, Err)>,
     }
     
    @@ -31,7 +31,7 @@ where
         /// Create new `Apply` combinator
         pub(crate) fn new(a: A, b: B, f: F) -> Self {
             Self {
    -            srv: Cell::new((a, b, f)),
    +            srv:  Rc::new(RefCell::new((a, b, f))),
                 r: PhantomData,
             }
         }
    @@ -67,7 +67,7 @@ where
         type Future = AndThenApplyFnFuture<A, B, F, Fut, Res, Err>;
     
         fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    -        let inner = self.srv.get_mut();
    +        let mut inner = self.srv.borrow_mut();
             let not_ready = inner.0.poll_ready(cx)?.is_pending();
             if inner.1.poll_ready(cx)?.is_pending() || not_ready {
                 Poll::Pending
    @@ -77,7 +77,7 @@ where
         }
     
         fn call(&mut self, req: A::Request) -> Self::Future {
    -        let fut = self.srv.get_mut().0.call(req);
    +        let fut = self.srv.borrow_mut().0.call(req);
             AndThenApplyFnFuture {
                 state: State::A(fut, Some(self.srv.clone())),
             }
    @@ -108,7 +108,7 @@ where
         Err: From<A::Error>,
         Err: From<B::Error>,
     {
    -    A(#[pin] A::Future, Option<Cell<(A, B, F)>>),
    +    A(#[pin] A::Future, Option<Rc<RefCell<(A, B, F)>>>),
         B(#[pin] Fut),
         Empty,
     }
    @@ -129,10 +129,10 @@ where
             match this.state.as_mut().project() {
                 StateProj::A(fut, b) => match fut.poll(cx)? {
                     Poll::Ready(res) => {
    -                    let mut b = b.take().unwrap();
    +                    let b = b.take().unwrap();
                         this.state.set(State::Empty);
    -                    let b = b.get_mut();
    -                    let fut = (&mut b.2)(res, &mut b.1);
    +                    let (_, b, f) = &mut *b.borrow_mut();
    +                    let fut = f(res, b);
                         this.state.set(State::B(fut));
                         self.poll(cx)
                     }
    @@ -255,11 +255,11 @@ where
     
             if this.a.is_some() && this.b.is_some() {
                 Poll::Ready(Ok(AndThenApplyFn {
    -                srv: Cell::new((
    +                srv: Rc::new(RefCell::new((
                         this.a.take().unwrap(),
                         this.b.take().unwrap(),
                         this.f.clone(),
    -                )),
    +                ))),
                     r: PhantomData,
                 }))
             } else {
    
  • actix-service/src/and_then.rs+9 8 modified
    @@ -1,16 +1,17 @@
     use std::future::Future;
     use std::pin::Pin;
     use std::rc::Rc;
    +use std::cell::RefCell;
     use std::task::{Context, Poll};
     
     use super::{Service, ServiceFactory};
    -use crate::cell::Cell;
    +
     
     /// Service for the `and_then` combinator, chaining a computation onto the end
     /// of another service which completes successfully.
     ///
     /// This is created by the `ServiceExt::and_then` method.
    -pub(crate) struct AndThenService<A, B>(Cell<(A, B)>);
    +pub(crate) struct AndThenService<A, B>(Rc<RefCell<(A, B)>>);
     
     impl<A, B> AndThenService<A, B> {
         /// Create new `AndThen` combinator
    @@ -19,7 +20,7 @@ impl<A, B> AndThenService<A, B> {
             A: Service,
             B: Service<Request = A::Response, Error = A::Error>,
         {
    -        Self(Cell::new((a, b)))
    +        Self(Rc::new(RefCell::new((a, b))))
         }
     }
     
    @@ -40,7 +41,7 @@ where
         type Future = AndThenServiceResponse<A, B>;
     
         fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    -        let srv = self.0.get_mut();
    +        let mut srv = self.0.borrow_mut();
             let not_ready = !srv.0.poll_ready(cx)?.is_ready();
             if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
                 Poll::Pending
    @@ -51,7 +52,7 @@ where
     
         fn call(&mut self, req: A::Request) -> Self::Future {
             AndThenServiceResponse {
    -            state: State::A(self.0.get_mut().0.call(req), Some(self.0.clone())),
    +            state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
             }
         }
     }
    @@ -72,7 +73,7 @@ where
         A: Service,
         B: Service<Request = A::Response, Error = A::Error>,
     {
    -    A(#[pin] A::Future, Option<Cell<(A, B)>>),
    +    A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
         B(#[pin] B::Future),
         Empty,
     }
    @@ -90,9 +91,9 @@ where
             match this.state.as_mut().project() {
                 StateProj::A(fut, b) => match fut.poll(cx)? {
                     Poll::Ready(res) => {
    -                    let mut b = b.take().unwrap();
    +                    let b = b.take().unwrap();
                         this.state.set(State::Empty); // drop fut A
    -                    let fut = b.get_mut().1.call(res);
    +                    let fut = b.borrow_mut().1.call(res);
                         this.state.set(State::B(fut));
                         self.poll(cx)
                     }
    
  • actix-service/src/apply_cfg.rs+15 13 modified
    @@ -2,8 +2,9 @@ use std::future::Future;
     use std::marker::PhantomData;
     use std::pin::Pin;
     use std::task::{Context, Poll};
    +use std::rc::Rc;
    +use std::cell::RefCell;
     
    -use crate::cell::Cell;
     use crate::{Service, ServiceFactory};
     
     /// Convert `Fn(Config, &mut Service1) -> Future<Service2>` fn to a service factory
    @@ -26,7 +27,7 @@ where
         S: Service,
     {
         ApplyConfigService {
    -        srv: Cell::new((srv, f)),
    +        srv: Rc::new(RefCell::new((srv, f))),
             _t: PhantomData,
         }
     }
    @@ -53,7 +54,7 @@ where
         S: Service,
     {
         ApplyConfigServiceFactory {
    -        srv: Cell::new((factory, f)),
    +        srv: Rc::new(RefCell::new((factory, f))),
             _t: PhantomData,
         }
     }
    @@ -66,7 +67,7 @@ where
         R: Future<Output = Result<S, E>>,
         S: Service,
     {
    -    srv: Cell<(T, F)>,
    +    srv: Rc<RefCell<(T, F)>>,
         _t: PhantomData<(C, R, S)>,
     }
     
    @@ -102,10 +103,8 @@ where
         type Future = R;
     
         fn new_service(&self, cfg: C) -> Self::Future {
    -        unsafe {
    -            let srv = self.srv.get_mut_unsafe();
    -            (srv.1)(cfg, &mut srv.0)
    -        }
    +        let (t, f) = &mut *self.srv.borrow_mut();
    +        f(cfg, t)
         }
     }
     
    @@ -117,7 +116,7 @@ where
         R: Future<Output = Result<S, T::InitError>>,
         S: Service,
     {
    -    srv: Cell<(T, F)>,
    +    srv: Rc<RefCell<(T, F)>>,
         _t: PhantomData<(C, R, S)>,
     }
     
    @@ -157,7 +156,7 @@ where
             ApplyConfigServiceFactoryResponse {
                 cfg: Some(cfg),
                 store: self.srv.clone(),
    -            state: State::A(self.srv.get_ref().0.new_service(())),
    +            state: State::A(self.srv.borrow().0.new_service(())),
             }
         }
     }
    @@ -172,7 +171,7 @@ where
         S: Service,
     {
         cfg: Option<C>,
    -    store: Cell<(T, F)>,
    +    store: Rc<RefCell<(T, F)>>,
         #[pin]
         state: State<T, R, S>,
     }
    @@ -213,8 +212,11 @@ where
                 },
                 StateProj::B(srv) => match srv.poll_ready(cx)? {
                     Poll::Ready(_) => {
    -                    let fut = (this.store.get_mut().1)(this.cfg.take().unwrap(), srv);
    -                    this.state.set(State::C(fut));
    +                    {
    +                        let (_, f) = &mut *this.store.borrow_mut();
    +                        let fut = f(this.cfg.take().unwrap(), srv);
    +                        this.state.set(State::C(fut));
    +                    }
                         self.poll(cx)
                     }
                     Poll::Pending => Poll::Pending,
    
  • actix-service/src/cell.rs+0 57 removed
    @@ -1,57 +0,0 @@
    -//! Custom cell impl, internal use only
    -use std::task::{Context, Poll};
    -use std::{cell::UnsafeCell, fmt, rc::Rc};
    -
    -pub(crate) struct Cell<T> {
    -    inner: Rc<UnsafeCell<T>>,
    -}
    -
    -impl<T> Clone for Cell<T> {
    -    fn clone(&self) -> Self {
    -        Self {
    -            inner: self.inner.clone(),
    -        }
    -    }
    -}
    -
    -impl<T: fmt::Debug> fmt::Debug for Cell<T> {
    -    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    -        self.inner.fmt(f)
    -    }
    -}
    -
    -impl<T> Cell<T> {
    -    pub(crate) fn new(inner: T) -> Self {
    -        Self {
    -            inner: Rc::new(UnsafeCell::new(inner)),
    -        }
    -    }
    -
    -    pub(crate) fn get_ref(&self) -> &T {
    -        unsafe { &*self.inner.as_ref().get() }
    -    }
    -
    -    pub(crate) fn get_mut(&mut self) -> &mut T {
    -        unsafe { &mut *self.inner.as_ref().get() }
    -    }
    -
    -    #[allow(clippy::mut_from_ref)]
    -    pub(crate) unsafe fn get_mut_unsafe(&self) -> &mut T {
    -        &mut *self.inner.as_ref().get()
    -    }
    -}
    -
    -impl<T: crate::Service> crate::Service for Cell<T> {
    -    type Request = T::Request;
    -    type Response = T::Response;
    -    type Error = T::Error;
    -    type Future = T::Future;
    -
    -    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    -        self.get_mut().poll_ready(cx)
    -    }
    -
    -    fn call(&mut self, req: Self::Request) -> Self::Future {
    -        self.get_mut().call(req)
    -    }
    -}
    
  • actix-service/src/lib.rs+0 1 modified
    @@ -12,7 +12,6 @@ mod and_then_apply_fn;
     mod apply;
     mod apply_cfg;
     pub mod boxed;
    -mod cell;
     mod fn_service;
     mod map;
     mod map_config;
    
  • actix-service/src/then.rs+8 8 modified
    @@ -1,16 +1,16 @@
     use std::future::Future;
     use std::pin::Pin;
     use std::rc::Rc;
    +use std::cell::RefCell;
     use std::task::{Context, Poll};
     
     use super::{Service, ServiceFactory};
    -use crate::cell::Cell;
     
     /// Service for the `then` combinator, chaining a computation onto the end of
     /// another service.
     ///
     /// This is created by the `Pipeline::then` method.
    -pub(crate) struct ThenService<A, B>(Cell<(A, B)>);
    +pub(crate) struct ThenService<A, B>(Rc<RefCell<(A, B)>>);
     
     impl<A, B> ThenService<A, B> {
         /// Create new `.then()` combinator
    @@ -19,7 +19,7 @@ impl<A, B> ThenService<A, B> {
             A: Service,
             B: Service<Request = Result<A::Response, A::Error>, Error = A::Error>,
         {
    -        Self(Cell::new((a, b)))
    +        Self(Rc::new(RefCell::new((a, b))))
         }
     }
     
    @@ -40,7 +40,7 @@ where
         type Future = ThenServiceResponse<A, B>;
     
         fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
    -        let srv = self.0.get_mut();
    +        let mut srv = self.0.borrow_mut();
             let not_ready = !srv.0.poll_ready(cx)?.is_ready();
             if !srv.1.poll_ready(cx)?.is_ready() || not_ready {
                 Poll::Pending
    @@ -51,7 +51,7 @@ where
     
         fn call(&mut self, req: A::Request) -> Self::Future {
             ThenServiceResponse {
    -            state: State::A(self.0.get_mut().0.call(req), Some(self.0.clone())),
    +            state: State::A(self.0.borrow_mut().0.call(req), Some(self.0.clone())),
             }
         }
     }
    @@ -72,7 +72,7 @@ where
         A: Service,
         B: Service<Request = Result<A::Response, A::Error>>,
     {
    -    A(#[pin] A::Future, Option<Cell<(A, B)>>),
    +    A(#[pin] A::Future, Option<Rc<RefCell<(A, B)>>>),
         B(#[pin] B::Future),
         Empty,
     }
    @@ -90,9 +90,9 @@ where
             match this.state.as_mut().project() {
                 StateProj::A(fut, b) => match fut.poll(cx) {
                     Poll::Ready(res) => {
    -                    let mut b = b.take().unwrap();
    +                    let b = b.take().unwrap();
                         this.state.set(State::Empty); // drop fut A
    -                    let fut = b.get_mut().1.call(res);
    +                    let fut = b.borrow_mut().1.call(res);
                         this.state.set(State::B(fut));
                         self.poll(cx)
                     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.