[HN Gopher] Let rand = main as usize (2022)
       ___________________________________________________________________
        
       Let rand = main as usize (2022)
        
       Author : wonger_
       Score  : 78 points
       Date   : 2024-06-06 17:55 UTC (5 hours ago)
        
 (HTM) web link (codeandbitters.com)
 (TXT) w3m dump (codeandbitters.com)
        
       | vlovich123 wrote:
       | > 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).
        
         | blt wrote:
         | 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?
        
           | sans-seraph wrote:
           | 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.
        
             | blt wrote:
             | 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...
        
               | sans-seraph wrote:
               | 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
        
           | comex wrote:
           | 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...
        
             | duped wrote:
             | 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.
        
         | nemothekid wrote:
         | > _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.
        
           | vlovich123 wrote:
           | 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.
        
             | zepton wrote:
             | 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).
        
               | vlovich123 wrote:
               | 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).
        
             | sans-seraph wrote:
             | 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.
        
               | vlovich123 wrote:
               | Sorry. Not UB but a likely a logical bug in the code (and
               | a potential security exploit).
        
               | mgaunard wrote:
               | overflow of unsigned integers is well-defined in C.
               | 
               | You're confusing it with overflow of signed integers.
        
             | nemothekid wrote:
             | > _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.
        
               | vlovich123 wrote:
               | 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).
        
         | haileys wrote:
         | > _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.
        
           | _flux wrote:
           | 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.
        
             | haileys wrote:
             | 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.
        
           | darby_nine wrote:
           | > 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.
        
             | haileys wrote:
             | > _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.
        
         | EE84M3i wrote:
         | 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-...
        
         | hun3 wrote:
         | > 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?
        
       | akira2501 wrote:
       | As an aside getauxval(3) allows access to AT_RANDOM which is "the
       | address of sixteen bytes containing a random value."
        
       | dist-epoch wrote:
       | > It's debatable whether this is effective at turning away
       | attacks, but that's the goal, and ASLR is enabled on almost every
       | operating system in use today.
       | 
       | It's not debatable at all, ASLR is a significant barrier to
       | attacks.
       | 
       | Quote from a random hacking book:
       | 
       | > By doing so, it makes it significantly harder for an attacker
       | to predict the location of specific processes and data, such as
       | the stack, heap, and libraries, thereby mitigating certain types
       | of exploits, particularly buffer overflows.
       | 
       | https://book.hacktricks.xyz/binary-exploitation/common-binar...
        
       | bitwize wrote:
       | It's not quite the same, but it made me think of how in the Atari
       | 2600 game _Yars ' Revenge_, the TV static-like "neutral zone" in
       | the middle of the screen is literally just the game's code from
       | the ROM taken as a bitmap and placed in the right part of the
       | console's playfield. I think they XOR together two different
       | sections of code, scrolling in different directions.
        
       | Someone wrote:
       | FTA: Even in the best circumstances, a program can only acquire
       | one random value this way
       | 
       | Can it?                 let rand = if(fork() == 0) {main as
       | usize} else {std::process::exit(0)}
       | 
       | (For those who wonder: I know this code has 'some' issues)
        
         | deathanatos wrote:
         | Would fork() alone cause another ASLR roll? I feel like if fork
         | _just_ forks -- duplicates the memory space  & execution, with
         | all the pages being CoW -- the layout of the child is going to
         | be the same as the parent.
         | 
         | Ran the slightly modified:                 fn main() {
         | if fork() == 0 {               dbg!(main as usize);           }
         | else {               dbg!(main as usize);           }       }
         | 
         | which got me,                 [src/main.rs:7:9] main as usize =
         | 105397413561856       [src/main.rs:5:9] main as usize =
         | 105397413561856
        
           | mike_hock wrote:
           | > Would fork() alone cause another ASLR roll?
           | 
           | No, that's fundamentally impossible.
        
       | Dwedit wrote:
       | It's basically the XKCD random number generator:
       | https://xkcd.com/221/
       | 
       | Also on Windows, randomized address space layout changes only on
       | reboot.
        
       ___________________________________________________________________
       (page generated 2024-06-06 23:00 UTC)