6 ms·
Let rand = main as usize (2022)
- vlovich123 2y ago> 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 2y agoIt'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 2y agoNo, 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 2y agoIn 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...
- deleted 2y ago[deleted]
- sans-seraph 2y agoRust'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#/fn_to_numeric_cast_any 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 https://www.ralfj.de/blog/2022/04/11/provenance-exposed.html
- comex 2y agoIt 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/function-pointers.html https://rust-lang.github.io/unsafe-code-guidelines/layout/fu...
- duped 2y agoI 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 2y ago>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 2y agoYou’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 2y agoOverflowing 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 2y agoSorry, 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).
- haileys 2y ago> 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 2y agoUnsafe 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 2y agoExactly. 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 2y ago> 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 2y ago> 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 2y agoThe 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 https://doc.rust-lang.org/reference/unsafety.html [2]: https://doc.rust-lang.org/reference/behavior-not-considered-unsafe.html https://doc.rust-lang.org/reference/behavior-not-considered-...
- hun3 2y ago> 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?
- ryukoposting 2y agoA 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.
- nialv7 2y agoReally, 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.
- CUViper 2y agoEven 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.
- akira2501 2y agoAs an aside getauxval(3) allows access to AT_RANDOM which is "the address of sixteen bytes containing a random value."
- dist-epoch 2y ago> 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-binary-protections-and-bypasses/aslr https://book.hacktricks.xyz/binary-exploitation/common-binar...
- fargle 2y ago> It's not debatable at all you do appear to be debating it...
- g15jv2dp 2y agoStating facts is not "debating".
- worik 2y ago> Stating facts is not "debating". But alternative facts?
- fargle 2y ago"ASLR is a significant barrier to attacks." that is a position statement, not a fact. i don't care for, as is a very common theme here on HN, techbro-splaining that "it's a settled/widely known/etc. fact" when it is an opinion. actually, that's really all the author pointed out - didn't say it wasn't valuable, just said it could be debated. thus goading, of course, a snarky response (including a "random" quote from an unnamed book) about what ASLR does and therefore there can be no debate. i might agree with you, i might not agree you. but presenting facts, opinions, and arguments to support to reject a position sounds like a debate to me. what i will also say is that any universally qualified statement about the value of a security hardening feature, risk of a vulnerability, etc. is always wrong until the threat model and all other engineering factors are properly weighed. what is "significant" to situation A may be "security theater" in situation B.
- bitwize 2y agoIt'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 2y agoFTA: 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 2y agoWould 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 2y ago> Would fork() alone cause another ASLR roll? No, that's fundamentally impossible.
- sim7c00 2y agomaybe execve. the loader/linker (ld in linux) are responsible for loading the address. I think with fork they are not re-loaded but it copies (clones page tables/pages etc?) the addr space. Also, if you print your addrs in hex: '0x5fdbbf654600' you can see its aligned to some place. if you'd do number >> 8 it will be '0x005fdbbf6546' which might be more useful if you don't want the least significant bits to be all unset in your random value.
- Dwedit 2y agoIt's basically the XKCD random number generator: https://xkcd.com/221/ https://xkcd.com/221/ Also on Windows, randomized address space layout changes only on reboot.
- dwattttt 2y agoIt can change under other (rare) circumstances. Otherwise a collision between an already-chosen base address for one module and an allocation in your process would result in a failure to load that module into your process.
- qweqwe14 2y agoThere's also the good old trick of measuring duration between two instants and using that as a (crude) randomness source. Also on Linux there's the AT_RANDOM entry in the aux vector, which provides any program with 16 random bytes.
- puzzledobserver 2y agoMight having correlated random variables (in this case, rand and the address of main) unintentionally cause vulnerabilities like the Debian OpenSSL incident [0]? [0] https://lists.debian.org/debian-security-announce/2008/msg00152.html https://lists.debian.org/debian-security-announce/2008/msg00...