8000
Skip to content

UB after Rc::increment_strong_count with &mut T from prior Rc::get_mut or get_mut_unchecked. #154859

@zachs18

Description

@zachs18

I tried this code under Miri:

use std::rc::Rc;

fn main() {
    let rc = Rc::new(1);
    let ptr = Rc::into_raw(rc);
    let mut rc = unsafe { Rc::from_raw(ptr) };
    let val = Rc::get_mut(&mut rc).unwrap(); // or get_mut_unchecked
    *val = 2;
    unsafe { Rc::increment_strong_count(ptr) };
    *val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
    unsafe { Rc::decrement_strong_count(ptr) };
    *val = 4;
    dbg!(*rc);
}

(playground)

I expected to see this happen: No UB is reported, and it prints 4

Instead, this happened: Miri reports UB under both Stacked Borrows and Tree Borrows at the *val = 3; line

The program is a bit weird, but by my reading, it upholds the safety contract of Rc::increment_strong_count.

The pointer must have been obtained through Rc::into_raw and must satisfy the same layout requirements specified in Rc::from_raw_in. The associated Rc instance must be valid (i.e. the strong count must be at least 1) for the duration of this method, and ptr must point to a block of memory allocated by the global allocator.

There is no mention of there not being allowed to be existing &mut Ts as given out by Rc::get_mut or Rc::get_mut_unchecked, only that the strong count must be at least 1. And, conceptually, I don't think that things which only need to access the refcounts of an Rc<T> should cause aliasing issues for the T.

Tree Borrows error
error: Undefined Behavior: write access through <452> at alloc198[0x10] is forbidden
  --> src/main.rs:10:5
   |
10 |     *val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
   |     ^^^^^^^^ Undefined Behavior occurred here
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental
   = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/tree-borrows.md for further information
   = help: the accessed tag <452> has state Frozen which forbids this child write access
help: the accessed tag <452> was created here, in the initial state Reserved
  --> src/main.rs:7:15
   |
 7 |     let val = Rc::get_mut(&mut rc).unwrap();
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the accessed tag <452> later transitioned to Unique due to a child write access at offsets [0x10..0x14]
  --> src/main.rs:8:5
   |
 8 |     *val = 2;
   |     ^^^^^^^^
   = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference
help: the accessed tag <452> later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x10..0x18]
  --> src/main.rs:9:14
   |
 9 |     unsafe { Rc::increment_strong_count(ptr) };
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = help: this transition corresponds to a loss of write permissions
   = note: stack backtrace:
           0: main
               at src/main.rs:10:5: 10:13
           1: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71
           2: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166:18: 166:21
           3: std::rt::lang_start::<()>::{closure#0}
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18: 206:75
           4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:287:13: 287:31
           5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
           6: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
           7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
           8: std::rt::lang_start_internal::{closure#0}
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:175:24: 175:49
           9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
           10: std::panicking::catch_unwind::<isize, {closure@std::rt::lang_start_internal::{closure#0}}>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
           11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
           12: std::rt::lang_start_internal
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:171:5: 193:7
           13: std::rt::lang_start::<()>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5: 210:6

error: aborting due to 1 previous error
Stacked Borrows error
error: Undefined Behavior: attempting a write access using <470> at alloc198[0x10], but that tag does not exist in the borrow stack for this location
  --> src/main.rs:10:5
   |
10 |     *val = 3; // We still have a `&mut i32`, even though the strong count is 2 at this point
   |     ^^^^^^^^ this error occurs as part of an access at alloc198[0x10..0x14]
   |
   = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows 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
help: <470> was created by a Unique retag at offsets [0x10..0x14]
  --> src/main.rs:7:15
   |
 7 |     let val = Rc::get_mut(&mut rc).unwrap();
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <470> was later invalidated at offsets [0x0..0x18] by a SharedReadOnly retag
  --> src/main.rs:9:14
   |
 9 |     unsafe { Rc::increment_strong_count(ptr) };
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: stack backtrace:
           0: main
               at src/main.rs:10:5: 10:13
           1: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5: 250:71
           2: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:166:18: 166:21
           3: std::rt::lang_start::<()>::{closure#0}
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18: 206:75
           4: std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:287:13: 287:31
           5: std::panicking::catch_unwind::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
           6: std::panicking::catch_unwind::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
           7: std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
           8: std::rt::lang_start_internal::{closure#0}
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:175:24: 175:49
           9: std::panicking::catch_unwind::do_call::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:581:40: 581:43
           10: std::panicking::catch_unwind::<isize, {closure@std::rt::lang_start_internal::{closure#0}}>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:544:19: 544:88
           11: std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#0}}, isize>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:359:14: 359:40
           12: std::rt::lang_start_internal
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:171:5: 193:7
           13: std::rt::lang_start::<()>
               at /home/zachary/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5: 210:6

error: aborting due to 1 previous error

Related issue: #136322 . If this is determined that this shouldn't be UB (I think it shouldn't be UB), then I think it would have the same fix mentioned in #136322 (comment) .

The Miri error (under both Stacked and Tree Borrows) would be fixed by #132553 , as it does not make a reference covering T to access the refcounts (tested under Miri after a rebase).

Meta

rustc --version --verbose:

rustc 1.96.0-nightly (2972b5e59 2026-04-03)
binary: rustc
commit-hash: 2972b5e59f1c5529b6ba770437812fd83ab4ebd4
commit-date: 2026-04-03
host: x86_64-unknown-linux-gnu
release: 1.96.0-nightly
LLVM version: 22.1.2

@rustbot label T-libs

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.needs-triageThis issue may need triage. Remove it if it has been sufficiently triaged.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0