| # [FTP](../README.md)-028: Handle Rights |
| |
| _The Right Stuff_ |
| |
| Field | Value |
| ----------|-------------------------- |
| Status | Accepted |
| Authors | kulakowski@google.com, bprosnitz@google.com |
| Submitted | 2019-04-01 |
| Reviewed | 2019-12-12 |
| |
| ## Summary |
| |
| Annotate required or excluded handle rights in FIDL, specifically: |
| |
| 1. Have a mechanism to **describe rights** on all handle kinded types (e.g. |
| `Protocol`, `request<Protocol>`, `handle`) |
| |
| 2. On serialization, **enforce rights filtering**, i.e. the description of |
| rights determines a mask, which indicates the set of rights which one must |
| have. |
| |
| 3. On deserialization, **enforce rights validation** and the set of rights |
| provided. |
| |
| ## Motivation |
| |
| Handle rights are a tool for attenuating privilege within Fuchsia. Prior to this |
| FTP, FIDL had no way to describe handle rights. Several places with important |
| restrictions or requirements on handle rights currently relegate such a |
| description to comments on methods. |
| |
| Examples of desirable rights restrictions include read-only IPC or VMO handles, |
| and non-executable VMO handles. |
| |
| Examples of desirable right requirements include writable IPC handles. |
| |
| ## Design |
| |
| ### Rights Constraints |
| |
| This proposal adds a new type of constraint to FIDL: a rights annotation. This |
| can be applied to all handle types in FIDL data structures and method |
| parameters: handles, protocols, and protocol requests. |
| |
| Rights constraints MAY be specified on typed handle declarations: |
| `handle<*subtype*, *RC*>`. They cannot be specified on plain handles because |
| rights only have meaning within the context of a particular handle subtype. When |
| rights constraints are not specified on handle declarations, rights will be |
| forwarded with the same rights (no change to existing behavior). |
| |
| Example: |
| |
| `handle<vmo, zx.rights.READ | zx.rights.WRITE>` read and write are required, any |
| other rights are removed when sending to clients |
| |
| `handle<vmo>` no rights specified, forward existing rights |
| |
| At least one right MUST be specified for each right constraint (e.g. |
| `handle<vmo, >` is illegal). |
| |
| Rights constraints cannot be specified for server and client protocol endpoints |
| (`myprotocol` and `request<myprotocol>`). These will always have the rights |
| `zx.TRANSFER | zx.WAIT | zx.INSPECT | zx.WRITE | zx.READ | zx.SIGNAL | zx.SIGNAL_PEER` |
| and subtype set to channel. If custom rights are needed (for |
| instance for an unmovable protocol), `handle<*channel*, *RRC, ORC*>` can be |
| used. (This may be further refined with the use of templates, and will be |
| discussed in future FTPs). |
| |
| Rights constraints MUST follow a "default deny" policy. All rights must be |
| explicitly listed. |
| |
| ### Syntax |
| |
| Handle rights constraints are bits-typed expressions that can be specified with |
| the help of bitwise operators. In the examples above, `|` is the bitwise-OR |
| operator. The syntax is meant to be general and apply to bits-typed values |
| elsewhere in the FIDL language as well. In the zx library, there will be a bits |
| definition zx.rights containing all of the rights. |
| |
| While a rights specification repeating `zx.rights.[*right*]` is verbose, there are |
| proposals to shorten it in future FTPs so it is considered out of scope of this |
| FTP. |
| |
| ### Bindings |
| |
| Bindings MUST respond to reception and deserialization of a handle with |
| incorrect required rights by destroying the message and closing the channel with |
| epitaph `ZX_ERR_ACCESS_DENIED`. |
| |
| Bindings MUST respond to serialization of a handle with incorrect required |
| rights by failing to serialize, destroying the message, and disallowing sending. |
| Bindings MUST close the channel as well with `ZX_ERR_BAD_STATE`. |
| |
| ### Defaults |
| |
| There will be no defaults for handle rights. The reasoning here is the |
| following: |
| |
| * Defaults have unclear behavior that can be inconsistent across object types. |
| It isn't transparent to the user what the default rights are for a given |
| object. |
| |
| * Defaults discourage use of fine grained handle rights — it becomes easy to not |
| consider rights when writing a FIDL definition. |
| |
| * If there were defaults, for many object types the most suitable candidates |
| would be no rights or maximum rights. A maximum right default would limit the |
| effectiveness of the changes in this FTP, but a no right default would have |
| the same effect as not having defaults. Neither of which are very helpful. |
| |
| ### Reuse of Handle Declarations |
| |
| A consequence of not having defaults is that specifying rights can be verbose. |
| In order to improve this, it will be possible to give a name to an entire handle |
| declaration with the aliasing features (i.e. *using* keyword). |
| |
| `using readable_vmo = handle<vmo, zx.READ>` |
| |
| An alternative to this might be allowing aliases for rights constraints (e.g. an |
| "`io`" alias for `zx.READ | zx.WRITE`), but this provides a layer of indirection |
| that obscures the rights, especially if they are used broadly. By allowing |
| aliasing at the object level, it limits usage to locations where the type is |
| identical. |
| |
| ### Parameterizability |
| |
| We want to create generic messages containing channels whose rights constraints |
| can be parametrized. |
| |
| For instance, consider `fuchsia.mem.Buffer` which holds a `handle<vmo>`. It should |
| be possible to say `fuchsia.mem.Buffer:C` with constraints `C` flowing to |
| constraining the `handle<vmo>`. |
| |
| Generalized type aliasing is a start here, and the introduction of templates |
| will further satisfy this need. While this is out-of-scope for this proposal, |
| this requirement must be considered for related work. |
| |
| ## Implementation Strategy |
| |
| Reception of messages should rely on the `zx_channel_read_etc` system call to |
| provide rights information at the point-of-call. The rights information will be |
| used by bindings to validate required rights are present and filter out any |
| additional received rights beyond the required rights. |
| |
| Sending of messages should rely on `zx_channel_write_etc` which will decrease |
| sent rights to the set of specified rights and validate that all required rights |
| are present, returning `ACCESS_DENIED` if validation fails. The bindings will be |
| responsible for closing the channel after receiving this response. To match |
| existing behavior, `ZX_HANDLE_OP_MOVE` will be used on this system call which is |
| equivalent to calling `zx_handle_replace` and then `zx_channel_write`. When no |
| rights are specified `ZX_RIGHT_SAME_RIGHTS` will be used in place of the rights. |
| |
| An intent to implement doc describing the implementation in detail is in |
| progress. |
| |
| ## Ergonomics |
| |
| In the long term this is an ergonomics improvement. Rights documentation and |
| checking is done in a standard way across FIDL, rather than ad hoc comments and |
| checks. |
| |
| ## Documentation and Examples |
| |
| This will require documentation changes to: |
| |
| * [FIDL language specification] |
| |
| * [Kernel rights documentation] (pointing to how rights can be specified) |
| |
| ## Backwards Compatibility |
| |
| ### ABI Compatibility |
| |
| Rights changes MUST NOT break ABI compatibility. |
| |
| ### Source Compatibility |
| |
| Rights changes MAY break source compatibility. Breaking source compatibility is |
| unexpected, however and should be clearly documented if binding authors choose |
| this path. |
| |
| ### Adding Required Rights |
| |
| It is backwards compatible on the message recipient side to have an additional |
| required right, as it only gives more capability. However, on the message sender |
| side, adding a required right is a backwards incompatible change as the right |
| now needs to be present before sending. |
| |
| ### Removing Required Rights |
| |
| It is backwards compatible on the sender side to remove required rights, but |
| backwards incompatible on the recipient side. The recipient will now not be |
| receiving a right that it expects. |
| |
| ### Adding Optional Rights |
| |
| It is backwards compatible to add optional rights. |
| |
| ### Removing Optional Rights |
| |
| It is backwards compatible on the sender side to remove optional rights, but |
| backwards incompatible on the recipient side. The recipient will now not be |
| receiving a right that it previously may have received. |
| |
| ### Ambient assumptions |
| |
| There are likely to be ambient assumptions that the deployment of this model |
| will break. For example, clients may be assuming that all VMOs received over a |
| connection are mappable, even though servers do not intend to provide this |
| guarantee. |
| |
| One can see this state as the entire motivation for this FTP: to remove this |
| pervasive and implicit contracts. |
| |
| ## Performance |
| |
| Microbenchmarks show that `zx_channel_write_etc` and `zx_channel_read_etc` have |
| very similar performance. For a 64 byte message with 1 handle, a |
| `zx_channel_write`/`zx_channel_read` take 962ns while |
| `zx_channel_write_etc`/`zx_channel_read_etc` take 1000ns. |
| |
| The handles array needed when reading or writing handles will increase from 256 |
| bytes `(ZX_CHANNEL_MAX_MSG_HANDLES * sizeof(zx_handle_t))` to 1024 bytes |
| `(ZX_CHANNEL_MAX_MSG_HANDLES * sizeof(zx_handle_info_t))`. Similarly, the array |
| that is stack allocated when writing handles will increase from 256 bytes to |
| 1280 bytes `(ZX_CHANNEL_MAX_MSG_HANDLES * sizeof(zx_handle_disposition))`. |
| |
| In order to keep stack allocation to a max of 256 bytes, we will need to heap |
| allocate handle tables if they are too big (> 16 handles for read or > 12 |
| handles for write). As part of this change, we will look at the combined stack |
| allocation requirements of both message size and handle tables (we only consider |
| message size today). |
| |
| ## Security |
| |
| This is a security improvement. It enables more accurate auditing of our API |
| surfaces. It moves permissions checks into the bindings, which can be better |
| reviewed than the same checks across all call sites. |
| |
| For instance, once rights are fully used, it will be easy to audit all places |
| transferring ownership of executable VMOs. This would prove difficult today. |
| |
| ## Testing |
| |
| Each bindings implementation should be unit tested. |
| |
| The roll out of this feature should also ensure that implementations of FIDL |
| protocols that are modified to use this feature test the new functionality. |
| |
| ## Drawbacks, Alternatives, and Unknowns |
| |
| #### Drawback: Public APIs Will Be Verbose |
| |
| In a certain sense this feature is "noisy". In typical execution of the system, |
| the missing-rights paths may never be taken. And yet they still take up real |
| estate in the public system API. |
| |
| We believe that the cost here is worth the clarity and precision, and that good |
| use of aliases will cover most needs. |
| |
| Consider for instance two classes of problems which can be avoided or reduced: |
| |
| 1. Breakage at a distance due to incompatible rights: A failure may occur very |
| far from the source (possibly even way downstream in some other process if |
| the handle was transferred again). |
| |
| 2. Undocumented assumptions cause compatibility issues: Some clients/servers may |
| pass handles with more rights than strictly needed leading to their peers |
| assuming they can rely on those rights being present leading to compatibility |
| issues should these assumptions prove faulty. |
| |
| We realize that spelling out rights is annoying but equate it to type |
| information for the capability. |
| |
| #### Alternative: Lower and Upper Bounds for Rights |
| |
| Initial design called for lower and upper bounds on rights (e.g. "without |
| executable right", or “with writable right”). From a security standpoint, the |
| line of thinking is that Fuchsia should adopt a “default deny” policy for all |
| capabilities (and rights). |
| |
| So if a capability (or right) isn’t explicitly mentioned anywhere then it |
| shouldn’t be granted. Component manifests already behave this way for sandboxing |
| and ideally so should FIDL APIs. |
| |
| We may want to additionally allow expressing explicitly "optional rights", i.e. |
| rights which may or may not be provided. |
| |
| Hence, from a constraint syntax standpoint, i.e. what people type when writing |
| FIDL APIs, we are gearing towards listing rights, and marking some as optional. |
| |
| Note that from a constraint semantics standpoint, i.e. what would need to be |
| expressed in the JSON IR and implemented by bindings, this syntactic change |
| continues to express lower & upper bounds checks. |
| |
| #### Mandatory Source Compatibility |
| |
| In Fuchsia, rights are generally represented as a `uint32` and the value of a |
| right can change at any time. Because of this, it might seem reasonable to |
| expect changes in rights values in FIDL to not substantively cause changes in |
| the generated source code. However, there may be some use cases for breaking |
| source compatibility - such as generating specific methods (such as write()) if |
| a given right is present (in this case `zx.rights.WRITE`). Because of this, we |
| aren’t prescribing that rights changes can’t break source compatibility. |
| |
| <!-- xrefs --> |
| [FIDL language specification]: /docs/reference/fidl/language/language.md |
| [Kernel rights documentation]: /docs/concepts/kernel/rights.md |