News for hackers. Stuff that matters.
Re:Let rand = main as usize by vlovich123 on Thursday June 06, 2024 @02:23PM (#40600693)
> For those expecting to the usual Rust guard rails, it's surprising that the compiler allows casting between arbitrary raw pointer types outside of an unsafe block. This feels really dangerous— even though we can't do anything with the pointer outside of an unsafe block, creating a raw pointer usually implies that an unsafe block will eventually do something with it. I kind of wish that this pointer casting required unsafe, just because this code should send up red flags, and probably deserves a close look during code review.

I think the general philosophy is that unsafe only demarcates potentially unsound code whereas casting between different pointers isn't technically unsound even though it can cause unsoundness in unsafe code if done incorrectly. I agree with the author that casting between unrelated pointer types should probably be considered unsafe but would probably require a new edition which would mean Rust 2027 at the earliest (assuming someone is motivated enough to push it through the bureaucracy).

Re:Let rand = main as usize by nemothekid on Thursday June 06, 2024 @03:30PM (#40601418)
>I agree with the author that casting between unrelated pointer types should probably be considered unsafe but would probably require a new edition which would mean Rust 2027 at the earliest

As I understand it, unsafe pretty much says "what you are doing here may violate memory safety". Casting doesn't do that, only dereferencing. If you'd like to increase the scope to also include "things that might violate memory safety for another code block", then shouldn't compile either:

    let mut foo = unsafe { int_ref as *const u32 as usize };
    foo = foo + 1;
    let bar = unsafe {*(foo as *const u32)}
the mutation of foo is also "unsafe" under this definition, and the compiler shouldn't let you modify pointers in any manner.
Re:Let rand = main as usize by vlovich123 on Thursday June 06, 2024 @04:11PM (#40601828)
You’ve changed the goal to something I didn’t state and then demonstrate that it’s a bad idea. I agree it’s impossible to restrict unsoundness to only appear within unsafe, but that’s not the goal.

Of course unsafe code can generate unsoundness in safe code. The main difference is that unsoundness would be more bounded somewhere between unsafe blocks as you’ve written which improves code review and the speed with with issues are found.

I’ll also note that the +=1 is also potentially unsound in release builds since Rust doesn’t do overflow checks at runtime (although since it presumably originates from a valid address that’s not possible in practice). It’s the one practical tradeoff Rust chose to make to allow UB in sounds code so that code wasn’t overly verbose while retaining good performance at runtime.

Re:Let rand = main as usize by zepton on Thursday June 06, 2024 @04:19PM (#40601904)
Overflowing addition is never UB in Rust - it is defined to wrap around in release builds (i.e. it would be a compiler bug if adding 1 to 255_u8 in a release build produced any value other than 0_u8).
Re:Let rand = main as usize by vlovich123 on Thursday June 06, 2024 @04:32PM (#40602048)
Sorry, was thinking of signed integer overflow which while considered sound is simultaneously considered to be a a bug in your code (hence the panic in debug mode and requires the use of wrapping_add if you intend the wrapping).
Re:Let rand = main as usize by comex on Friday June 07, 2024 @12:05AM (#40605227)
Rust behaves as you describe for both signed and unsigned.
Re:Let rand = main as usize by sans-seraph on Thursday June 06, 2024 @04:26PM (#40601968)
The +=1 in the above code is defined behavior. Unlike in C, the Rust compiler is not allowed to assume that overflow does not happen, and must restrict its optimizations accordingly. The undefined behavior in this code would be a result of the dereference in the next line. If there existed a check to ensure that overflow had not occurred prior to the dereference, then this code would be well-defined. And because overflow is defined behavior in Rust, the aforementioned overflow check could not be optimized away, as it could in C.
Re:Let rand = main as usize by mgaunard on Thursday June 06, 2024 @06:18PM (#40602995)
overflow of unsigned integers is well-defined in C.

You're confusing it with overflow of signed integers.

Re:Let rand = main as usize by vlovich123 on Thursday June 06, 2024 @04:30PM (#40602019)
Sorry. Not UB but a likely a logical bug in the code (and a potential security exploit).
Re:Let rand = main as usize by nemothekid on Thursday June 06, 2024 @04:55PM (#40602291)
>You’ve changed the goal to something I didn’t state and then demonstrate that it’s a bad idea.

No - I guess I should have been more clear but I don't think `unsafe` demarcates the boundary between sound and unsound. I think what happens in unsafe are things that potentially memory unsafe or thread unsafe. Pointer casts are not included in that - I feel that would only provide a false sense of safety.

Re:Let rand = main as usize by vlovich123 on Thursday June 06, 2024 @05:53PM (#40602808)
Except unsafe is used more than just for memory and thread safety. Unsafe can acquire whatever semantics you want it to. It’s just that Rust the standard library and standard language has mandated that memory and thread unsoundness is always unsafe. But I can easily make an additional constraint that I annotate as unsafe and the compiler will help me enforce it (if I recall correctly the embedded guys use this when interacting with hardware even though there’s no memory or thread safety issues & I’ve seen it in other places too). It’s a fairly arbitrary choice about what’s considered safe by default vs unsafe and you can always expand the surface area of unsafe.

As for false sense of safety or not, that’s a value judgement whereas we can actually derive metrics about it (eg. build a version of the compiler that require it be annotated unsafe and then investigate now illegal call sites to count how many errors per instance there turned out to be).

Re:Let rand = main as usize by umanwizard on Thursday June 06, 2024 @07:35PM (#40603684)
It’s technically true that you can make unsafe mean whatever you want in your own projects, but redefining it to include nondeterministim that doesn’t itself result in UB would be such a fundamental change to the semantics broadly accepted by the rust community that it’s very unlikely they would do so for language constructs like `as`.

That said, I think `as` is generally a code smell and the one large professional Rust project I’ve worked on banned it in CI via clippy.

Re:Let rand = main as usize by thinkharderdev on Friday June 07, 2024 @07:01AM (#40607388)
> As I understand it, unsafe pretty much says "what you are doing here may violate memory safety".

I don't think this is true in general. Unsafe is used pretty frequently for things that are themselves memory safe but may violate invariants which can cause memory unsoundness in other places. An example would be `std::str::from_utf8_unchecked` which is not itself memory unsafe. But various safe methods on `str` are memory safe ONLY if the str contains valid UTF8

Re:Let rand = main as usize by haileys on Thursday June 06, 2024 @04:16PM (#40601869)
> I kind of wish that this pointer casting required unsafe, just because this code should send up red flags, and probably deserves a close look during code review.

If a pointer cast performed in safe code can cause unsoundness in unsafe code elsewhere, that's a bug in the unsafe code. All bets are off if your unsafe code is that trusting of data it receives from safe code.

This is a good argument for why pointer casting should be safe - it forces the point and pushes you to find the right abstraction. No pointer cast done in safe code should ever be able to cause unsoundness.

Re:Let rand = main as usize by _flux on Thursday June 06, 2024 @04:23PM (#40601942)
Unsafe code can rarely validate pointers it receives and must depend on the properties of the other code to work safely. It just doesn't depend on the safety properties of that other code.
Re:Let rand = main as usize by haileys on Thursday June 06, 2024 @04:27PM (#40601981)
Exactly. So you must use Rust's encapsulation features like modules and visibility to ensure that any particular piece of unsafe code _cannot_ receive a pointer that can't be proven to be valid.
Re:Let rand = main as usize by darby_nine on Thursday June 06, 2024 @04:26PM (#40601966)
> If a pointer cast performed in safe code can cause unsoundness in unsafe code elsewhere, that's a bug in the unsafe code.

Converting from pointer to integer (as in the given example) cannot possibly lead to unsafe code that would not have already been unsafe with an arbitrary integer value. There's nothing unsafe about accessing an address without dereferencing it.

Casting to a pointer from an integer should probably be considered generally unsafe.

Re:Let rand = main as usize by haileys on Thursday June 06, 2024 @04:31PM (#40602042)
> Casting to a pointer from an integer should probably be considered generally unsafe.

The pointer can't be assumed to be valid anyway without other guarantees. It could have been valid at some point, and then freed at another point, and is now dangling.

You'll notice that std::ptr::null and std::ptr::dangling are also safe functions. This is intentional - the language designers are telling you that you cannot rely on the fact that a piece of data is of a pointer type to trust that it's valid.

Re:Let rand = main as usize by blt on Thursday June 06, 2024 @02:35PM (#40600810)
It's only dangerous if you can also make the reverse cast, right? From ints to function pointers. Does Rust also allow that in safe code?
Re:Let rand = main as usize by comex on Thursday June 06, 2024 @02:51PM (#40601011)
It does not.

Oddly enough, it doesn't even allow it in unsafe code, not with a normal cast. You have to use transmute. I believe this is due to concerns about targets where function and data pointers have different representations.

https://rust-lang.github.io/unsafe-code-guidelines/layout/fu...

Re:Let rand = main as usize by duped on Thursday June 06, 2024 @04:30PM (#40602018)
I believe it has more to do with the fact that function pointers are effectively &'static T (static references) and references are forbidden from being null. But it's probably a bit of both.

In other words `0usize as fn ()` is insta-undefined behavior, and you can't have that in safe code.

Re:Let rand = main as usize by sans-seraph on Thursday June 06, 2024 @02:47PM (#40600951)
No, Rust does not allow safe conversions from integers to function pointers. The code `main as usize as fn()` will result in a "non-primitive cast" error. In order to convert from an integer or raw pointer to a function pointer, the unsafe function `std::mem::transmute` must be used.
Re:Let rand = main as usize by blt on Thursday June 06, 2024 @03:19PM (#40601308)
In that case, then a linter warning seems more appropriate for pointer->int than requiring "unsafe". I feel "unsafe" should not be diluted to mean "unwise". But what do I know, I'm a C++ programmer...
Re:Let rand = main as usize by sans-seraph on Thursday June 06, 2024 @04:06PM (#40601781)
Rust's linting tool, Clippy, provides a lint that will produce a warning when a function pointer is cast to any integral type: https://rust-lang.github.io/rust-clippy/master/index.html#/f...

The broader topic of whether it is safe, or wise, to cast between pointers and integers in general is an area of active research. Ralf Jung's blog is required reading on this topic: https://www.ralfj.de/blog/2022/04/11/provenance-exposed.html

Re:Let rand = main as usize by EE84M3i on Thursday June 06, 2024 @04:55PM (#40602296)
The current behavior is clearly documented in the Rust Reference[1]:

>The following language level features cannot be used in the safe subset of Rust: > Dereferencing a raw pointer. > Reading or writing a mutable or external static variable. > Accessing a field of a union, other than to assign to it. > Calling an unsafe function (including an intrinsic or foreign function). > Implementing an unsafe trait.

It also calls out the behavior in noted in this specific post in "Behavior not considered unsafe"[2]:

> Exposing randomized base addresses through pointer leaks

[1]: https://doc.rust-lang.org/reference/unsafety.html

[2]: https://doc.rust-lang.org/reference/behavior-not-considered-...

Re:Let rand = main as usize by hun3 on Thursday June 06, 2024 @06:25PM (#40603064)
> I kind of wish that this pointer casting required unsafe, just because this code should send up red flags, and probably deserves a close look during code review.

How about a new lint instead?

Re:Let rand = main as usize by ryukoposting on Thursday June 06, 2024 @11:51PM (#40605158)
A key thing to consider is that there's nothing innately unsafe about casting a pointer to a number. It's when you go the other way around that problems crop up.

As long as dereferencing a raw pointer is considered unsafe, you're fine. Casting it has no actual effect at the machine level.

Re:Let rand = main as usize by nialv7 on Friday June 07, 2024 @10:10AM (#40608912)
Really, the only constraint for the semantic of unsafe blocks is that programs that does not contain unsafe blocks cannot have undefined behavior, everything else is by choice.

For example, in theory you can make pointer dereferencing safe (!), and make every operation that might create a invalid pointer unsafe. Rust chose to do this the other way around, probably out of usability reasons.

Re:Let rand = main as usize by CUViper on Friday June 07, 2024 @10:36AM (#40609167)
Even if a pointer was guaranteed correct at the time of creation, it can't be known safe to dereference in the future unless you put a lifetime on it, and then it's just a reference.