Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions