Skip to content

Destroying locked Mutex in libstd triggers miri in safe code #85434

Closed

Description

@chorman0773

I tried this code through miri:

use std::sync::Mutex;

fn main(){
    let m = Mutex::new(5i32);
    
    core::mem::forget(m.lock());
}

I expected to see this happen: No observable behaviour, including from miri (aside from "Unsupported Operation" errors).

Instead, this happened:
miri reports undefined behaviour in "Destroying locked mutex" when calling pthread_mutex_destroy (Note: this report is correct, calling pthread_mutex_destroy on a locked mutex is prescribed to be undefined behaviour by POSIX)

Meta

This was tested on all latest versions of rustc, all using miri 0.1.54, on play.rust-lang.org:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=28904dec86ec2f64bb03163bedf37299

Miri Backtrace

error: Undefined Behavior: destroyed a locked mutex
  --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/mutex.rs:78:17
   |
78 |         let r = libc::pthread_mutex_destroy(self.inner.get());
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ destroyed a locked mutex
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
           
   = note: inside `std::sys::unix::mutex::Mutex::destroy` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/unix/mutex.rs:78:17
   = note: inside `<std::sys_common::mutex::MovableMutex as std::ops::Drop>::drop` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/mutex.rs:98:18
   = note: inside `std::ptr::drop_in_place::<std::sys_common::mutex::MovableMutex> - shim(Some(std::sys_common::mutex::MovableMutex))` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:192:1
   = note: inside `std::ptr::drop_in_place::<std::sync::Mutex<i32>> - shim(Some(std::sync::Mutex<i32>))` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:192:1
note: inside `main` at src/main.rs:8:1
...
[9 frames before `main` omitted manually]

This is caused by issue presented in #31936. However, I believe that it deserves new attention given that it causes miri to fail in safe code (and is not a miri false positive, as miri is correctly reporting undefined behaviour in calling pthread_mutex_destroy).

Activity

  1. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    on May 18, 2021
  2. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on May 18, 2021
  3. hameerabbasi commented on May 18, 2021

    @hameerabbasi
    Contributor

    Assigning a priority according to the WG-prioritization discussion on Zulip and removing I-prioritize.

    @rustbot modify labels +P-high -I-prioritize

  4. added
    P-highHigh priority
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on May 18, 2021
  5. RalfJung commented on May 22, 2021

    @RalfJung
    Member

    However, I believe that it deserves new attention given that it causes miri to fail in safe code

    Well, Miri is just a tool to detect issues like this. It looks like back then the decision was made to ignore what POSIX says and instead to rely on what certain implementations do. Since Miri checks POSIX compliance as strictly as it can, this leads to Miri errors in safe code.

    Ideally we'd work towards getting the spec changed (there seems to be no good reason for this UB, and without something like Rust it is unclear why anyone in reasonable code would even want to do this). That's probably very hard though. Are there work-arounds where we can avoid calling pthread_mutex_destroy when the lock is still held? Seems hard to do that atomically...

  6. chorman0773 commented on May 22, 2021

    @chorman0773
    ContributorAuthor
  7. nbdd0121 commented on May 26, 2021

    @nbdd0121
    Member

    We could pthread_mutex_trylock, and if we successfully acquire the lock, do pthread_mutex_unlock and pthread_mutex_destroy. If we couldn't acquire the lock, just leak...

  8. RalfJung commented on May 26, 2021

    @RalfJung
    Member

    I was about to ask what if someone else acquires the lock again just after we pthread_mutex_unlock... but that cannot happen since we have &mut Mutex here.

    So, yeah, that could work.

  9. m-ou-se commented on May 27, 2021

    @m-ou-se
    Member

    do pthread_mutex_unlock and

    Posix says it's UB to call that function on a different thread than the one that locked the mutex, so that wouldn't work, unfortunately.

  10. chorman0773 commented on May 27, 2021

    @chorman0773
    ContributorAuthor

    I assume that was in the case that successfully called pthread_mutex_trylock, so it's being unlocked on the thread that locked it.

  11. m-ou-se commented on May 27, 2021

    @m-ou-se
    Member

    Oh, right. I misread the comment. :)

    Then the only question that remains is whether it's okay to not call pthread_mutex_destroy but still drop/reuse the memory. Without calling destroy, pthread doesn't know that memory is no longer a mutex it is managing. If it has some kind of linked list of mutexes or something, that could result in UB.

  12. chorman0773 commented on May 27, 2021

    @chorman0773
    ContributorAuthor

    It probably has to be properly leaked. I would assume that the raw mutex needs to be properly treated as Pinned.

  13. 4 remaining items

  14. m-ou-se commented on May 27, 2021

    @m-ou-se
    Member

    Yeah, either parking_lot, or something other thread parker based, or maybe something not thread parker based but directly using a futex (or whatever the platform supports) might be a better fit for std. It's a bit tricky to make those trade offs for all std users at once, but not having to deal with all this pthread stuff (and not having to Box them, etc.) would be very nice.

  15. added and removed
    P-highHigh priority
    on May 31, 2021
  16. m-ou-se commented on May 31, 2021

    @m-ou-se
    Member

    Lowering priority to P-medium because of the previous discussion in #31936.

  17. DemiMarie commented on Jan 28, 2022

    @DemiMarie
    Contributor

    A more extreme option is to get rid of pthread mutexes entirely. (So, implement our mutex/rwlock/condvars directly using atomics and platform-specific syscalls, like we already do for thread::park() on most platforms.) That's still somewhere on my to do list.

    I honestly think this is the only reasonable solution. POSIX’s semantics are not a good fit for us.

  18. m-ou-se commented on Jan 28, 2022

    @m-ou-se
    Member

    A more extreme option is to get rid of pthread mutexes entirely. (So, implement our mutex/rwlock/condvars directly using atomics and platform-specific syscalls, like we already do for thread::park() on most platforms.)

    [..] either parking_lot, or something other thread parker based, or maybe something not thread parker based but directly using a futex (or whatever the platform supports) [..]

    We discussed this in a recent library team meeting, and we'll be going forward with this.

  19. DemiMarie commented on Jan 28, 2022

    @DemiMarie
    Contributor

    A more extreme option is to get rid of pthread mutexes entirely. (So, implement our mutex/rwlock/condvars directly using atomics and platform-specific syscalls, like we already do for thread::park() on most platforms.)

    We discussed this in a recent library team meeting, and we'll be going forward with this.

    Yay!!! That gives Rust an opportunity to have better performance than pthreads, too. The one caveat is robust mutexes, but I don’t think Rust needs those.

  20. m-ou-se commented on Feb 7, 2022

    @m-ou-se
    Member

    A more extreme option is to get rid of pthread mutexes entirely. (So, implement our mutex/rwlock/condvars directly using atomics and platform-specific syscalls, like we already do for thread::park() on most platforms.)

    [..] either parking_lot, or something other thread parker based, or maybe something not thread parker based but directly using a futex (or whatever the platform supports) [..]

    We discussed this in a recent library team meeting, and we'll be going forward with this.

    That effort is now tracked here: #93740

  21. m-ou-se commented on Apr 13, 2022

    @m-ou-se
    Member

    This has been fixed on Linux. We no longer use pthread locks on Linux. :)

  22. added 2 commits that reference this issue on Jun 19, 2022
  23. added a commit that references this issue on Jun 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-concurrencyArea: ConcurrencyC-bugCategory: This is a bug.I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-mediumMedium priorityT-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions