Is it possible to create 2 variables with consecutive addresses with miri?

I'm trying to check if some unsafe code is unsound, and for this, I need different "allocated objects" with consecutive addresses, like this:

let a = 2;
let b = 1;
assert_eq!(
    &b as *const _ as usize - &a as *const _ as usize,
    core::mem::size_of::<i32>()
);

This passes when compiled with rustc, but fails with miri. It seems like miri randomized variable addresses. Is it possible to make this pass with miri?

Use a #[repr(C)] struct instead of locals. Using #[repr] the only way to enforce layout constraints.

5 Likes

The underlying reason that Miri rejects your code is because a and b are from two different allocations (as in, pieces of memory set aside by the compiler, not necessarily data from the heap), and you can't really compare a pointer to one allocation with a pointer from another.

Like @RustyYato said, the way you make them have consecutive addresses which can be compared is by putting them inside a struct which makes guarantees about the position of its fields in memory, then taking pointers to those fields.

The only way to do this -- MIRI or not -- is to allocate one object containing your two subobjects.

From the safety requirements of offset:

Both the starting and resulting pointer must be either in bounds or one byte past the end of the same allocated object. Note that in Rust, every (stack-allocated) variable is considered a separate allocated object.

And even if you switch to wrapping_offset:

In particular, the resulting pointer remains attached to the same allocated object that self points to. It may not be used to access a different allocated object. Note that in Rust, every (stack-allocated) variable is considered a separate allocated object.

So there's no sound way to ever use two separate allocated objects next to each other. You must have them be the same allocated object.

3 Likes

I know that I can't compare/make/etc pointers to different allocated objects, and it's exactly the point.

I think that some code is unsound because it does that, and wanted to prove this using miri. But the code checks if addresses are consecutive and so with miri the unsound path is never executed.

I'll still contact the developer of the code, but with miri saying "this code is surely wrong" it would be easier =)

This example is casting pointers with different provenance to integers. It is not undefined behavior to compare two integers*, so miri will not help you here. miri will tell you about UB if the integers were casted back to pointers and dereferenced out of bounds, as an example. But you have to actually trigger UB to get miri to notice.

* Although that could change. This issue was discussed recently in Pointers Are Complicated II, or: We need better language specs (ralfj.de)

I was exploring code that causes UB after a check, e.g.:

let a = 2;
let b = 1;
assert_eq!(
    &b as *const _ as usize - &a as *const _ as usize,
    core::mem::size_of::<i32>()
);
let _: String = unsafe { mem::uninitialized() }; // Or use offset_from or whatever

The check always fails with miri, so the UB isn't reported by it.

I'm trying to piece together the XY problem. Please correct me if I'm wrong, but it appears there is an existing crate (as yet unnamed) which triggers undefined behavior, and you are unable to prove that it triggers UB with miri because the assertion fails.

That the assertion fails is significant, because as pointed out earlier this assertion violates the principles of pointer provenance. While it is still worth digging deeper into the code beyond that assertion, this is probably the first thing that should be fixed in the existing crate. One could argue that the assertion fails in miri specifically because it is triggering UB...

2 Likes

You are totally right. Though I've already reported the bug and the affected versions of the crate are already yanked. Sorry for not mentioning it before.

I don't see how it could be UB to do this, since it's safe code.

Can you do this?

    let ra = [4,3];
    assert_eq!(
        &ra[1] as *const _ as usize - &ra[0] as *const _ as usize,
        core::mem::size_of::<i32>()
    );

In your example ra is a single object, within which adjacency is guaranteed, whereas it sounds to me like @maybewaffle is talking about two distinct objects which happen to be accidentally adjacent (and the broken code in question assumes that "just" adjacency is a sufficient condition for the subsequent code to be safe, when in reality the subsequent code relies on the addresses actually being within the same object).

Making the code not trigger the assert is not the point. The point (as I understand it) is to make the code unsound in a way that Miri can detect, and the assert triggering under Miri (where it does not when run alone) prevents the unsoundness so that Miri can't detect it.

1 Like

UB might be a bit strong. Maybe “unsound” is a better fit. Regardless, safe code is guaranteed to not cause memory safety issues, and this is no exception. The code is memory safe, even if it is unsound.

Safe code cannot be unsound, modulo bugs in the compiler. The comparison between the integers is perfectly fine, since it uses no unsafe blocks.

2 Likes

To build on @alice's comment... Converting pointers to integers and comparing them is okay, but trying to use the result of that for pointer arithmetic is where you introduce the UB.

I'd be interested to see what kernel developers think about that rule though, because they do those sorts of operations all the time (both in C and Rust kernels).

Once you convert a pointer to an integer it is fine to do arithmetic on that integer and then convert it back to a pointer for as long as the resulting pointer is within the bounds of an allocation which has had it's address leaked by a pointer to integer cast. (I may have remembered a detail wrong, but the idea should be correct)

I would be very wary about what is safe with integer to pointer casts, without seeing it explicitly written down somewhere in the language rules.

Ok the code is sound, just logically dubious. :joy:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.