Skip to content

UB in Error::downcast_mut #451

Description

@dfoxfranke

A few weeks ago @Shnatsel reported zkat/miette#469 in miette. I noticed that the affected code was copied from eyre which in turn copied it from anyhow. Eyre already fixed it (eyre-rs/eyre@9f4ecc4) but anyhow still has the UB.

Adding this test to tests/test_context.rs,

#[test]
fn test_downcast_mut() {
    let (mut err, dropped) = make_chain();

    err.downcast_mut::<HighLevel>().unwrap().message = "high context";
    err.downcast_mut::<MidLevel>().unwrap().message = "mid context";
    err.downcast_mut::<LowLevel>().unwrap().message = "low error";

    assert_eq!(err.downcast_ref::<HighLevel>().unwrap().message, "high context");
    assert_eq!(err.downcast_ref::<MidLevel>().unwrap().message, "mid context");
    assert_eq!(err.downcast_ref::<LowLevel>().unwrap().message, "low error");

    assert!(dropped.none());
    drop(err);
    assert!(dropped.all());
}

and running cargo +nightly miri test --test test_context test_downcast_mut produces

error: Undefined Behavior: trying to retag from <135379> for Unique permission at alloc43804[0x38], but that tag only grants SharedReadOnly permission for this location
   --> src/ptr.rs:170:18
    |
170 |         unsafe { &mut *self.ptr.as_ptr() }
    |                  ^^^^^^^^^^^^^^^^^^^^^^^ this error occurs as part of retag at alloc43804[0x38..0x50]
    |
    = 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: <135379> was created by a SharedReadOnly retag at offsets [0x38..0x50]
   --> src/ptr.rs:89:18
    |
 89 |             ptr: NonNull::from(ptr),
    |                  ^^^^^^^^^^^^^^^^^^
    = note: this is on thread `test_downcast_m`
    = note: stack backtrace:
            0: anyhow::ptr::Mut::<'_, HighLevel>::deref_mut
                at src/ptr.rs:170:18: 170:41
            1: anyhow::error::<impl anyhow::Error>::downcast_mut::<HighLevel>
                at src/error.rs:560:18: 560:46
            2: test_downcast_mut
                at tests/test_context.rs:124:5: 124:36
            3: test_downcast_mut::{closure#0}
                at tests/test_context.rs:121:23: 121:23

The diagnosis and fix is basically the same as for the other crates. The guilty code is

anyhow/src/error.rs

Lines 558 to 559 in 917a169

let addr =
(vtable(self.inner.ptr).object_downcast)(self.inner.by_ref(), target)?.by_mut();
which coerces a &T into a &mut T. The fix is to extend the vtable with an object_downcast_mut function which can avoid creating shared references and be used here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions