-
Notifications
You must be signed in to change notification settings - Fork 210
Handle ZST #104
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
Handle ZST #104
Conversation
Firstly, and most importantly, passing an empty slice does not mean that we have a null pointer on our hands. Granted you could construct an empty zero-sized slice from a null pointer using |
@newpavlov I didn't say it returns null, I said "doesn't promise anything about the pointer" (meaning it can be null, or even dangling) I'm not a C expert, but from what I know stuff that aren't explicit are undefined, not OK. i'll try to research other OS to see what all of them currently do, but I would be warry here. |
I know
7.1.4.p1. |
Well, interpreting it like this would make Rust cc @RalfJung |
@Aaron1011 landed a PR in Miri that explicitly allows any pointer if the length is 0. The authority to contact here would be the Linux syscall docs and maybe the glibc docs. The C standard has no bearing here, if anything we'd have to figure out what LLVM's rules are. Cc @gnzlbg
That's because its argument is marked as
It certainly promises that the ptr is non-NULL. Beyond that, most likely the pointer can also not be "dangling" in the sense that it points to a deallocated location. It can, however, most definitely be something like the constant 0x100 -- there does not have to be actually allocated memory there. (It is unclear whether LLVM makes a difference between "random constant cast to an integer" and "pointer that used to point to a valid allocation" -- but chances are there those are not the same, and the latter is "more dangerous" than the former.) |
@RalfJung I agree that the C standard is not the right authority here. as i've said before, if you know anyone from glibc/linux/llvm to tag on this I would love to hear if there are some docs/guidelines specifying what is right here. and btw, also passing ZST pointers to intel's memset/memcpy gave me problems. about the nullness, the nomicon says:
what am I missing? |
The Nomicon is wrong (and the fix landed recently). Slices are never NULL.
Would be interesting if you can reproduce that. References are guaranteed to always be safe to pass to functions like memset/memcpy as they always are non-null, aligned, and dereferencable. It is UB to create a reference that violates this (even if the reference is never used). Raw pointers are a different matter, of course. I am not sure what exactly you mean by "ZST pointer". |
http://man7.org/linux/man-pages/man2/getrandom.2.html Says that it writes size bytes to the buffer, so getrandom(NULL, 0, ...) should be ok (nothing is written to the output buffer). It also says that if the pointer passed is outside the address space, it returns an error code. |
I mean, doesn't this crate has tests for this corner case? EDIT: apparently not.. |
@RalfJung |
@RalfJung We also had this problem here: rust-bitcoin/rust-secp256k1#141 (comment) |
The commit comment talks about 0x1 though, not 0x0. Not sure what SGX does that breaks this, but in "normal" environments this works fine. Or maybe the actual bug is elsewhere. |
@RalfJung I'm not sure what is your "dereferencable for accesses of size 0" definition. if I'll go back to memset/memcpy which is the SGX example then again the C standard states:(7.24.1p2)
|
in particular llvm defines for memcpy:
https://llvm.org/docs/LangRef.html#id435 Edit: After thinking about this I don't think llvm has anything to do with this discussion. only linux/glibc docs for |
While it is true that |
@gnzlbg right. back to the topic :) I probably made a mistake by comparing to the C standard. the problem is that linux doesn't have docs/guidelines, at least non that I could find. and it seems better to go on the safe side and not provide NULL/0x01/pointer to random data location to the As for actual definitions here and evidence i'm not sure how to check this without sending an email to the linux mailing list asking about it. |
In particular, we guarantee that |
I'm sorry but both you and @RalfJung keep repeating that, but is there an official declaration to what is null? i'm unsure null always equal 0, could casting the alignment to a pointer result in NULL in some weird architecture? idk. My point is that even an aligned pointer that points to nowhere might not be valid (as it's not valid for |
Null is all bits set to zero. |
Not in Rust. |
@gnzlbg but we're passing an FFI boundry to C here. |
It doesn't really matter what C requires for |
If there is a platform where NULL is never all bits zero Rust cannot support it, so you can't write Rust code that calls C there. |
right and the docs don't specify if the pointer should be valid and readable when the length is 0. |
The docs say that the function writes |
The function declaration provided by kernel docs does not make the pointer |
If you have a reason to believe that this API does something with the pointer when |
I think we have enough information to resolve this one way or another. Here are the important questions:
@newpavlov, @dhardy I would say (3) is the only potential reason to merge this PR. What do you think? |
@dhardy the only thing I would like to see here is |
Another thing, the
If that was written also for |
The question is, what is "valid"? "valid" is a property relative to a size -- a pointer that is valid for accesses of size 4 might not be valid for accesses of size 8. So what is "valid for accesses of size 0"? Everything I know says that this just means "non-null and aligned" (possibly with an exception for "logical pointers" in the sense of this paper that are dangling, but that wouldn't affect integers cast to a pointer). In particular, that is what LLVM demands for such functions.
Correct. And a slice is always appropriately aligned, so we are good.
Do you have any reference or indication for this? I think this is valid, and in my reading neither the LLVM docs nor the C standard say otherwise. Rust doesn't care much about C, but for LLVM we do rely on that being valid.
I am curious if this is based on misunderstandings of Rust behavior (of which there clearly are a lot; I hope fixing the Nomicon will help here in the long run), bugs in other parts of the toolchain, or actual mismatches on what is considered "valid".
I am less worried about performance and more worried about a cargo cult of "Rust's empty slices need special treatment when interfacing with other stuff", in the absence of data/specs showing that this really is the case. |
Suit yourself; personally I'm more worried about wasting a lot of time on easily-solvable matters.
This may just be a side-effect of chunking |
If you are not sure, then inform yourself: open a Linux kernel documentation bug, send an email to the Linux kernel mailing list asking the question, etc. Speculating about this is a waste of everybody's time - we see the same docs you do, but talking about them here doesn't fix anything. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, we should mention that there is no system call when dest.is_empty()
.
@RalfJung when talking about FFI llvm's reference doesn't matter much because dynlibs might be compiled with gcc/other compilers. I'm not saying i'm 100% right, and my life would be easier if it turns out i'm wrong :) |
Which part of the C standard does it make you think that it doesn't work? |
Specifically both the "outside address space" and a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) |
True, for
I'd argue a ZST pointer "points to" no memory, so in particular it doesn't point to non-modifiable storage... but well, I think we can agree that the standard is less clear than it could be. That's true for many parts of the standard though. ;) Lucky enough for us, what C has to say about |
|
@RalfJung I'm away from my laptop but I'm curious now why this commit rust-bitcoin/rust-secp256k1@e8a1513 fails with (in particular this is the test that crashes: https://github.com/rust-bitcoin/rust-secp256k1/blob/master/src/key.rs#L478) |
@dhardy UPD: Although, it may be worth from consistency PoV, we could document that passing zero-length slices to @elichai |
Agreed, but only for consistency reasons (and to just get this resolved).
Good idea. Docs update should probably be in second paragraph of the I agree that we should look into |
|
If you end up adding a check for slices of zero length, inlining this function inlines the check, which might allow the check and the |
Yes, alternatively inlining |
@dhardy @newpavlov I added the necessary docs. Can we merge this? |
For future reference and for the sake of honesty etc. For actual code references even if this check wasn't there: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/char/random.c#n1642 the kernel will always use the I'm not saying that this PR is necessarily bad, just wanted to admit when I'm mistaken. and I now think that I was mistaken. |
See #104 for why this was added in the first place. Also, per our docs: > If `dest` is empty, `getrandom` immediately returns success, making > no calls to the underlying operating system. Signed-off-by: Joe Richey <[email protected]>
#291 inadvertantly removed this check See #104 for why this was added in the first place. Also, per our docs: > If `dest` is empty, `getrandom` immediately returns success, making > no calls to the underlying operating system. Signed-off-by: Joe Richey <[email protected]>
#291 inadvertantly removed this check See #104 for why this was added in the first place. Also, per our docs: > If `dest` is empty, `getrandom` immediately returns success, making > no calls to the underlying operating system. Signed-off-by: Joe Richey <[email protected]>
Almost all zst passes to FFI is undefined behavior
(i.e.
memcpy(NULL,NULL,0)
is UB, same forgetrandom
)This checks that the slice isn't a ZST. otherwise rust doesn't promise anything about the pointer from
slice.as_mut_ptr()
I do not think this is actually a vulnerability/deserve advisory, but if someone disagrees I would report this to https://github.com/RustSec/advisory-db (@tarcieri)
because most implementations will probably be fine with it. though I had problems in the past with Intel's libraries that passing NULL+0 to
memset
resulted in SEGFAULT.