-
-
Notifications
You must be signed in to change notification settings - Fork 48
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
Trust boundary specification #124
base: main
Are you sure you want to change the base?
Conversation
|
||
### In the standard library | ||
|
||
Unsafe maths functions on numeric primitives will be marked `unsafe-1`. There is nothing requiring to be marked `unsafe-2` or `unsafe-3` currently so these annotations will stay unused for now. |
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.
There is nothing requiring to be marked
unsafe-2
orunsafe-3
currently
This doesn't seem quite right. We have standard library types that do FFI, and standard library types that do pointer math.
Am I missing something?
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.
The idea is to only mark functions that expose the unsafety to the user (i.e. that have a narrow contract). Array
does raw pointer operations, but these operations are fully controlled by the preliminary checks that Array
enforces and as a result can't cause problems (i.e. Array
has a wide contract).
This is a generalisation of the checks for FFI. You can only use the FFI from a safe package, but this isn't transitive: a package being safe means that you trust that package to use the FFI correctly and to expose a safe interface to other packages. The same thing would apply to any unsafe operation.
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.
I think this answer does more to answer my other comment than this one. Or maybe I just don't get it.
If we ignore pointer math for the moment and focus on FFI, can you explain why you don't think the FFI-using functions in the net
package need to be marked with unsafe-3
?
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.
Because I can't cause my program to crash or to have some other kind of UB by using these functions since the inputs to the FFI calls are fully controlled. In addition, since you need to have access to AmbientAuth
(or something derived from it) in order to use the stuff in net
, there is no need for another layer of protection through --safe
.
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.
How can the compiler verify that those inputs are controlled properly as you say?
It is my understanding of this RFC that the compiler is going to enforce that all functions that call FFI include the unsafe-3
annotation. Is that wrong? It sounds now like you're saying that adding the annotation is optional, based on whether you as the code author think you created a safe wrapper for the function.
If that's how this is intended to work, I think this is a step backward from how the --safe
flag works now, since it enforces that no packages use FFI other than the ones you whitelist. If the new trust boundary works based on these annotations, and the annotations are optional, then you're not able to enforce anything, and an uncareful or unscrupulous package author can sneak an unsafe Pony API through a --safe-{x}
-protected compilation simply by leaving out the annotations...
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.
The compiler will enforce that
- Every package that calls
unsafe-1
functions is marked--safe-1
or higher - Every package that calls
unsafe-2
functions is marked--safe-2
or higher - Every package that calls
unsafe-3
functions or uses the FFI is marked--safe-3
or higher
The compiler won't be able to enforce that the exposed interface is safe, but that doesn't change anything for the FFI. In the current state of the compiler and language, I can have a function that calls sqrt
without any input validation, and even if I mark the package it is declared in as trusted, it won't prevent untrusted packages from using it with negative numbers and causing UB. This RFC will not change anything on that front.
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.
Okay, thanks for spelling it out further. I understand what's being proposed now.
The part I didn't understand is the point that FFI calls would be treated as unsafe-3
function calls, without needing to be annotated, and that the annotation enforces a compiler requirement on the call site, rather than fulfilling a compiler requirement within the body.
After reading your comment here, and @plietar's comment below, I understand better.
|
||
### In the language | ||
|
||
New syntax is needed to mark unsafe operations. We propose to use new annotations for this purpose: `unsafe-1`, `unsafe-2` and `unsafe-3`. These annotations map directly to the various levels of the trust boundary. These annotations are only allowed on function definitions, for example `fun \unsafe-1\ foo()`. |
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.
Would these annotations apply transitively, like the question mark used for partial functions?
That is, if foo
is marked with unsafe-1
, am I required to put the same mark on any function that calls foo
?
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.
No, these annotations aren't transitive for the reasons I explained in my reply to your other comment.
I don't think there's much point treating (potential) undefined behaviour and FFI as separate unsafety levels. You can achieve one with the other. I'm also not sure guarding against If I understand the RFC correctly, I can call any function marked as I find Rust's model very nice. Unsafe operations (such as dereferencing a pointer, or calling an unsafe function) requires an A common complaint about Rust's unsafe mechanism is the use of the keyword unsafe in both context, even though they have opposite meanings (as pointing out in the RFC), which leads to confusion. I would suggest we use something like: var p : Pointer[Foo ref] = ...
p() // error: Pointer's apply function is marked as unsafe
safe // We explicitly assert that what we're doing here is safe
let x : Foo ref = p() // dereferences p
end
struct Pointer[A]
fun \unsafe\ apply(): this->A => compile_intrinsic In short, marking a function as |
@plietar Regarding the different safety levels for potential undefined behaviour and FFI, I'll give a concrete example of how this could be useful. Suppose you have access to raw pointer operations in Pony with In the old Pony playground, which AFAIK wasn't sandboxed in a container/VM, all builds were made with If we were to have only one safety level for both undefined behaviour and the FFI we'd lose that capability. Regarding It also needs to be separated from I agree that it would be nice to have a way to audit packages for unsafe operations, but I'm not sure that a var p : Pointer[Foo ref] = ...
p()
let x : Foo ref = p() \unsafe-2\
struct Pointer[A]
fun \unsafe-2\ apply(): this->A => compile_intrinsic |
Rendered.