forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 435
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
rust: check range and add type invariant to Error (issue #295) #324
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
So this implementation of #295 is not zero-cost, is that correct? I am wondering if a blanket runtime check to enforce a type invariant is overkill in this context?
The kernel people can do this in C, at runtime, if they wanted to, e.g. by adding an
if/pr_warn
(or evenif/WARN
) in thePTR_ERR()
macro. But they don't. Does anyone know if this is a conscious choice, or an oversight? Did anyone check if the kernel people care? What happens in the kernel if you do return an negative errorint
outside-MAX_ERRNO:-1
?Is it a good use of "The Power of Rust" to insert non-zero-cost runtime checks that are possibly too paranoid?
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.
PTR_ERR() usually works in tandem with IS_ERR which does a runtime check like this. Because it's for a different semantic (either pointer or error), they don't print warnings.
But I think your point is valid. It may be overkill in some situations, so we have an unchecked version that relies on users to guarantee validity.
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.
This could lead to a memory safety issue if not careful. If we have a
Result<SomePointer>::Err(e)
ande
is outsideMAX_ERRNO
range, when we convert thisResult
into pointer, this will be recognized as a success, and the pointer points to some random kernel memory location.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.
Kernel C has exactly the same issue: if you call
ERR_PTR(x)
wherex
is "negative-er" than-MAX_ERRNO
, then you end up with a random pointer that's notIS_ERR()
. My point is: it's perfectly possible to check/warn/fix these issues at runtime in C, yet this has not been done by the kernel community.So my question is: are we "fixing" things that are not seen as a problem upstream? And are we adding non-zero cost runtime checks to accomplish this? How does that help us, or the upstream community?
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.
This is really a contract between the caller and callee doesn't it? In C there are no simple ways to represent this invariant in the type system, so pretty much the caller just assumes that the return value is sane, so there're no checks. In Rust we have the opportunity to represent this in the type system.
However, I do think that in many cases this check may not be desirable. In that case the unsafe unchecked constructor should be used:
would be:
I think this is actually good. It would prevent people from just try to convert an arbitrary int to errno, and force the caller to reason about whether the value is indeed an error.
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.
Off-topic: @TheSven73 do we need the
extern "C"
calls toIS_PTR/ERR_PTR
? Since we will be having checks againstMAX_ERRNO
anyway we can just cast them tousize
and check them?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.
(Replying to @TheSven73 -- again GitHub did not update with the new replies :)
Those are good questions. Let me write some context (which you already know, but I am saving these explanations for future discussions) first.
There are two ways we can introduce Rust into the kernel. One is as a C-like replacement, keeping semantics as close as possible (e.g. in the case here). This is what many people may expect, but that would mean we need to go the unsoundness-way, and therefore one of the major advantages of Rust is gone. There are other advantages to Rust, of course, but many may wonder whether "just a nicer language" is worth it.
The other way is to be fully sound. Of course, this means we cannot take for granted even "trivial" things (e.g. like the case here), where the caller is "supposed to do the right thing". Thus, to remain sound, we need to either take a runtime hit or have
unsafe
blocks (plus their proofs, plus*_unchecked()
functions). Nobody likes either, but the upside is huge: it is what leads us to the "holy grail" of being able to write kernel-space memory-safe code; i.e. without the risk of exploits and without moving things to user-space.So, with that context in mind, the answers would be:
It is not so much about fixing, but about being a requirement for us in order to remain sound.
That depends: in each instance, we have to decide whether to take a runtime hit or go the
unsafe
/*_unchecked()
way.I think, in general, the latter is best kept within
rust/kernel/
code (since we reap the benefits across many modules) and/or when performance is critical, while the former is best for "leaf" modules (to be able to write them in the safe subset as much as possible) and when performance does not matter much (e.g. initialization of a driver).In some cases, though, the only way is the runtime hit, because there may be no way to prove the
unsafe
call is sound.For us, it is what enables us to claim that Rust allows us to write UB-free and memory-safe code/drivers.
For the upstream community, it gives Linux the ability to have kernel-space memory-safe drivers without having to move everything into user-space (or into a multi-rings model). This should allow Linux to improve its security & reliability (due to no UB), maintainability (due to easier reviews of "leaf" modules), etc.
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.
Calling the C macros from Rust is probably more robust - if the "error pointer" implementation changes in C, things will continue to work.
Also, the "error pointer" mechanism could differ per-architecture:
linux/include/linux/err.h
Lines 10 to 18 in 7884043