Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

actix-service Cell::get_mut() is unsound #83

Closed
Shnatsel opened this issue Jan 8, 2020 · 21 comments
Closed

actix-service Cell::get_mut() is unsound #83

Shnatsel opened this issue Jan 8, 2020 · 21 comments

Comments

@Shnatsel
Copy link

@Shnatsel Shnatsel commented Jan 8, 2020

The following code is unsound:

pub(crate) fn get_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.as_ref().get() }
}

This uses Rc::as_ref() to obtain a reference to the underlying data, which does not guarantee uniqueness. It is possible to obtain several mutable references to the same memory location by calling this function repeatedly:

let mycell = Cell::new(vec![1,2,3]);
let ref1 = mysell.get_mut();
let ref2 = mysell.get_mut(); // obtained a second mutable reference; UB starts here

This may lead to arbitrary memory errors triggered from safe code, the most common of which would be use-after-free. These two references do not need to exist in the same function to trigger undefined behavior, they only need to exist at the same point in time.

A proper way to implement Cell:get_mut() would be calling Rc::get_mut() which guarantees uniqueness instead of Rc::as_ref().

@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 8, 2020

This is internal code. There is no repeated call to get_mut() anywhere in code

@fafhrd91 fafhrd91 closed this Jan 8, 2020
@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 8, 2020

Please, don’t start

@Shnatsel

This comment has been minimized.

Copy link
Author

@Shnatsel Shnatsel commented Jan 8, 2020

These two references do not need to exist in the same function to trigger undefined behavior, they only need to exist at the same point in time.

An easy way to see if this is a problem in practice is replace .as_ref() with .get_mut().unwrap() and see if anything panics.

@cdbattags

This comment has been minimized.

Copy link
Member

@cdbattags cdbattags commented Jan 8, 2020

@fafhrd91, I don't think "unsound" was meant to be personal or offensive.

Why close this so quickly?

@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 9, 2020

I need unit test that shows UB. Unit test must use public api.

@Nemo157

This comment has been minimized.

Copy link

@Nemo157 Nemo157 commented Jan 10, 2020

The unsoundness of the Cell API is publicly exposed through AndThenService::{clone, call}. The code in this playground exhibits MIRI detected UB from multiple unrelated &mut First borrows existing simultaneously while using only the safe public API of actix-service.

Running the code via cargo miri results in:

error: Miri evaluation error: not granting access to tag <untagged> because incompatible item is protected: [Unique for <7869> (call 4775)]
  --> /Users/nemo157/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-service-1.0.2/src/cell.rs:35:18
   |
35 |         unsafe { &mut *self.inner.as_ref().get() }
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Miri evaluation error: not granting access to tag <untagged> because incompatible item is protected: [Unique for <7869> (call 4775)]
   |
   = note: inside call to `actix_service::cell::Cell::<(First, &mut Second)>::get_mut` at /Users/nemo157/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-service-1.0.2/src/and_then.rs:54:29
   = note: inside call to `<actix_service::and_then::AndThenService<First, &mut Second> as actix_service::Service>::call` at /Users/nemo157/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-service-1.0.2/src/pipeline.rs:160:9

(@repnop identified that AndThenService allowed accessing these APIs and helped developing the PoC)

@JohnTitor

This comment has been minimized.

Copy link
Member

@JohnTitor JohnTitor commented Jan 10, 2020

I think it's worth to fix, re-opening.

@JohnTitor JohnTitor reopened this Jan 10, 2020
@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 15, 2020

@Nemo157 @repnop nice! finally some real code!

@cdbattags

This comment has been minimized.

Copy link
Member

@cdbattags cdbattags commented Jan 15, 2020

Hehehe thanks @fafhrd91, @Nemo157 and @repnop!

fafhrd91 added a commit that referenced this issue Jan 15, 2020
@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 15, 2020

should be fixed in master

@fafhrd91 fafhrd91 closed this Jan 15, 2020
@Nemo157

This comment has been minimized.

Copy link

@Nemo157 Nemo157 commented Jan 15, 2020

It's possible to get around the 'static bound by using Arc, see this playground (I've also removed the unnecessary &mut Second, that was where I was trying to trigger the UB but miri caught the duplicate &mut (First, &mut Second) before it even got to checking the &mut Second itself).

@repnop

This comment has been minimized.

Copy link

@repnop repnop commented Jan 15, 2020

its also worth noting that adding a + 'static bound is a breaking change. I think ideally this should be fixed internally in a way that doesn't break the public API, especially in a patch version if possible

@Shnatsel

This comment has been minimized.

Copy link
Author

@Shnatsel Shnatsel commented Jan 15, 2020

Undefined behavior occurs when external code attempts to obtain two mutable references to the same data. The lifetime of the data is irrelevant to this issue.

If Rc<RefCell> were used instead of a custom implementation it would panic on the provided testcase instead of triggering undefined behavior. It should be possible to bring the behavior of the current implementation in line with Rc<RefCell>.

If panicking is undesirable, it is possible to return Option or Result from this function instead of panicking, see RefCell::try_borrow_mut for an example of such API.

I wonder, what was the original rationale for migrating from Rc<RefCell> to a custom implementation?

@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 15, 2020

@Nemo157 new playground panics with BorrowMutError, is that what should happen?

@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 15, 2020

@repnop i dont see how this could be fixed internally

@Nemo157

This comment has been minimized.

Copy link

@Nemo157 Nemo157 commented Jan 15, 2020

If you run the code from the playground under MIRI it fails before the BorrowMutError with:

error: Miri evaluation error: not granting access to tag <untagged> because incompatible item is protected: [Unique for <7923> (call 4813)]
  --> /Users/nemo157/.cargo/git/checkouts/actix-net-8b378701d4b3767e/5940731/actix-service/src/cell.rs:35:18
   |
35 |         unsafe { &mut *self.inner.as_ref().get() }
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Miri evaluation error: not granting access to tag <untagged> because incompatible item is protected: [Unique for <7923> (call 4813)]
   |
   = note: inside call to `actix_service::cell::Cell::<(First, Second)>::get_mut` at /Users/nemo157/.cargo/git/checkouts/actix-net-8b378701d4b3767e/5940731/actix-service/src/and_then.rs:54:29
   = note: inside call to `<actix_service::and_then::AndThenService<First, Second> as actix_service::Service>::call` at /Users/nemo157/.cargo/git/checkouts/actix-net-8b378701d4b3767e/5940731/actix-service/src/pipeline.rs:160:9
   = note: inside call to `<actix_service::pipeline::Pipeline<actix_service::and_then::AndThenService<First, Second>> as actix_service::Service>::call` at src/main.rs:36:18
   = note: inside call to `<Second as actix_service::Service>::call` at /Users/nemo157/.cargo/git/checkouts/actix-net-8b378701d4b3767e/5940731/actix-service/src/and_then.rs:97:31
   = note: inside call to `<actix_service::and_then::AndThenServiceResponse<First, Second> as core::future::future::Future>::poll` at src/main.rs:55:35
   = note: inside call to `block_on::<actix_service::and_then::AndThenServiceResponse<First, Second>>` at src/main.rs:47:5
   = note: inside call to `main` at /Users/nemo157/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/src/rust/src/libstd/rt.rs:67:34

because at this point there exist two independent &mut (First, Second) on the stack referencing the same tuple. (The first is within the AndThenServiceResponse::poll stack frame, at and_then.rs:97, the second is at the shown snippet where MIRI is raising an error).

@Restioson

This comment has been minimized.

Copy link

@Restioson Restioson commented Jan 16, 2020

Would it be possible to simply change it to Rc<RefCell<T>>? Alternatively, could it be made unsafe perhaps? This would mean that the safety would have to be evaluated at the call-site.

@Nemo157

This comment has been minimized.

Copy link

@Nemo157 Nemo157 commented Jan 16, 2020

As a PoC this patch applied to actix-net passes all tests, and when the second playground is run against it under Miri it soundly fails with thread 'main' panicked at 'already borrowed: BorrowMutError' from within the AndThenServiceResponse. Presumably this requires benchmarking/more exhaustive testing which I don't have time to do, but if someone wants to take the patch and get it merged feel free (I license it under Apache-2.0 OR MIT, though I don't consider it to be creative enough to be copyrightable).

@fafhrd91

This comment has been minimized.

Copy link
Member

@fafhrd91 fafhrd91 commented Jan 16, 2020

this patch is boring

@CJKay

This comment has been minimized.

Copy link

@CJKay CJKay commented Jan 16, 2020

this patch is boring

So is resolving silent data corruption.

@bbqsrc

This comment has been minimized.

Copy link

@bbqsrc bbqsrc commented Jan 16, 2020

@fafhrd91 seriously? Please just stop writing Rust. You do not respect semver, you do not respect soundness, so why are you using a language predominantly based around doing these things right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.