-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validity of function pointers #72
Comments
I think we should not require that they point to allocated, executable memory and we should forbid NULL, but otherwise permit any initialized value. As you say, I don't see much value to permitting some bits to be uninitialized. |
I recently learned that function pointers could, in principle, have alignment requirements. However, currently the alignment seems to be 1 for all supported platforms. The fn docs call out the non-NULL property as does the Nomicon, but they do not call out alignment. Miri also does not check fn ptr alignment as it has no good way to figure out what the requirements for that are, and anway it currently would not do anything. So should those docs be amended to call out the alignment situation? Which place should they point at if someone wants to figure out the fn ptr alignment for their platform? There is no such thing as Also, should https://doc.rust-lang.org/reference/types/function-pointer.html or https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html call out the non-NULL property and/or the alignment? |
I don't think we should put any alignment requirement into the validity invariant. LLVM having this notion is no reason to -- in fact, the function pointer alignment patch fixed a wrong assumption made by LLVM's optimizations about what happens when you take a pointer to a known function. Trying to establish alignment requirements for arbitrary pointers to unknown functions would go far beyond that and is too much headache for too little reward:
|
Oh, and all of this is assuming we're only interested in allowing legitimate pointers to actual functions you can jump to. It's potentially useful if one can just smuggled arbitrary non-null addresses through a function pointer that is never called. It's certainly less of a footgun. People are already being bitten relatively frequently by function pointers being non-null, making the niche larger can only make that (marginally) worse. |
That's fine for me as long as LLVM does the same. |
Function pointers aren't really different from other pointers (in fact, they'll be literally indistinguishable once the long-awaited untyped |
I see. So I guess what I mean is "as long as we don't add any |
Also see this discussion. Currently at least docs would have to keep open the possibility that function pointers have to be aligned to "something" in the future. OTOH, I feel adding something like that at rust-lang/nomicon#149 would not be very useful, as we couldn't even define the "something". Would it be worth trying to write a (small) RFC that basically just says "for function pointers, NULL is and will be the only invalid value"? Just to get that out of the way? |
I'd like an RFC to that effect, and, because it's such a simple statement, I'd like that to be considered for entry into the proposed "normative statements about Rust list" that the Reference can hopefully begin to grow (rust-lang/reference#629). |
Is there any way to create a dangling function pointer "safely"?
|
With the proposed wording for a future RFC, the only value you can't assign to a function pointer is A particular example of where we need unaligned function pointers came up in // In C: typedef void (__CRTDECL* _crt_signal_t)(int);
type sighandler_t = Option<extern "cdecl" fn(c_int) -> ()>;
// In C: #define SIG_... ((_crt_signal_t)-i) // where i = [0, 4]
pub const SIG_DFL: sighandler_t = None;
pub const SIG_IGN: sighandler_t = Some(1_usize as );
pub const SIG_GET: sighandler_t = Some(2_usize as );
pub const SIG_SGE: sighandler_t = Some(3_usize as );
pub const SIG_ACK: sighandler_t = Some(4_usize as ); so even if Windows requires functions to be 2-byte or 4-byte aligned, that would still require that unaligned function pointers aren't invalid (cc @retep998). |
I am not sure what you were trying to accomplish here, but this makes little sense.
Are they unaligned though? On all currently supported platforms, the alignment is 1. |
Miri currently does some kind of type validation for function pointers:
What rule is Miri using to reject this code? As far as I can tell the only thing agreed-upon above is that fn pointers may not be null. |
Indeed, Miri currently checks that the pointer actually points to a function: |
There are a number of FFI apis that require this. It's kind of annoying that something that's a |
It makes some sense that a fn pointer must be valid because once created, since they are safe to call. I'm just here because // Note that we won't actually use these functions ever, we'll instead be
// testing the pointer's value elsewhere and calling our own functions.
INIT.call_once(|| unsafe {
let get = mem::transmute::<usize, _>(0x1);
let set = mem::transmute::<usize, _>(0x2);
init(get, set);
}); and I can't find any documentation that I could point the maintainers to which says that they can't do this. |
Then would |
Certainly its safety invariant requires that it points to an actual function. Its validity invariant, however, could be much weaker than that -- and indeed I see no good reason to make it any stronger than "non-null". So I would be fine with relaxing this check in Miri. Whether we also want to relax this check for const validation is a different question, those checks are somewhat more strict than Miri's checks already anyway (e.g. they recurse through references). |
If we don't relax it for const validation, then at least loosening the error message from "This is UB" to "This is not supported in const" seems like it might be nice, if possible. |
If this is permitted, I would prefer to not get an error from Miri. Perhaps selfishly, producing an error on this code makes Miri unable to find anything further in codebases which depend on a library that does this. There are quite a few. I feel like this is normally the kind of place you'd get a warning, but I don't think Miri does warnings (at the moment?). |
Another example of something like this in the wild is in This seems like it should be fine for most of the suggested validity requirements here, but the "FIXME: Check if the signature matches" comment at https://github.com/rust-lang/rust/blob/450ef8613ce80278b98e1b1a73448ea810322567/compiler/rustc_const_eval/src/interpret/validity.rs#L585 could cause issues if the signature had to match to avoid UB. |
Following this discussion, rust-lang/rust#94270 relaxes the function pointer validity check in Miri to just check for non-null. (The CTFE check is unchanged, that is a separate discussion.) |
Miri: relax fn ptr check As discussed in rust-lang/unsafe-code-guidelines#72 (comment), the function pointer check done by Miri is currently overeager: contrary to our usual principle of only checking rather uncontroversial validity invariants, we actually check that the pointer points to a real function. So, this relaxes the check to what the validity invariant probably will be (and what the reference already says it is): the function pointer must be non-null, and that's it. The check that CTFE does on the final value of a constant is unchanged -- CTFE recurses through references, so it makes some sense to also recurse through function pointers. We might still want to relax this in the future, but that would be a separate change. r? `@oli-obk`
So this is closed as... team has consensus? But where is the consensus documented? If "nowhere", shouldn't the issue track writing down the consensus? |
Discussing the validity of function pointers.
Clearly, the must be non-NULL. Since we exclude some bit patterns, we likely also do not want to allow the entire value to be uninitialized. We could allow some bits to be uninitialized as long as there is at least one bit initialized to 1, but, uh, why?^^
Anything else? Do fn ptrs have to point to allocated executable memory? This discussion concludes that the answer ought to be "no", because there's little to no benefit and it would interact badly with unloading shared libraries.
The text was updated successfully, but these errors were encountered: