[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)