blob: fefa696388bf15e4c204093d71c9e95d03d1b2e5 [file] [log] [blame] [view]
# Life of a handle in FIDL
This page gives a step-by-step explanation of how FIDL transfers a [Zircon
handle] from one process to another. In particular, it focuses on the various
meanings of "handle rights" and how handle rights are validated.
## Scenario
Consider a simple client and server communicating over the following protocol:
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples.docs/life_of_a_handle.test.fidl" region_tag="protocol" %}
```
Suppose that we remove `zx.rights.WRITE` from the handle rights, but only
recompile the server, not the client. What happens when the client creates a VMO
and passes it to `Method`?
## Diagram
In this scenario, the client is acting as **sender** and the server is acting as
**receiver**. We use those terms below because this is what matters for the
purposes of transferring handles. If the method returned a handle, then the same
steps would apply with the roles reversed.
[See below](#explanation) for a detailed explanation of the diagram.
![Diagram of sending a handle over FIDL](images/life-of-a-handle.svg)
## Explanation {#explanation}
1. **User code (sender)**
* Assume the sender obtains the VMO using the [`zx_vmo_create`] syscall. The
returned handle `h1` has default rights for a VMO: `DUPLICATE`,
`TRANSFER`, `READ`, `WRITE`, `MAP`, `GET_PROPERTY`, and `SET_PROPERTY`.
* Call `Method`, passing `h1` to it.
1. **FIDL bindings (sender)**
* Wrap `h1` in a **handle disposition** specifying the rights from the FIDL
type: `MAP`, `READ`, and `WRITE`. This tells the kernel what rights to
provide when transferring `h1`. The bindings don't know what rights `h1`
_actually_ has. (They don't know for sure that it references a VMO either,
but unlike rights this is usually represented in the static type system,
making it hard to pass the wrong handle type by accident.)
```c
zx_handle_disposition{
.operation = ZX_HANDLE_OP_MOVE,
.handle = h1,
.type = ZX_OBJ_TYPE_VMO,
.rights = ZX_RIGHT_MAP | ZX_RIGHT_READ | ZX_RIGHT_WRITE,
}
```
* Invoke the [`zx_channel_write_etc`] syscall (or similar).
1. **Kernel**
* Ensure that `h1` exists in the sender process's handle table.
* Ensure that `h1` refers to a VMO.
* Ensure that `h1` has (at least) the rights `MAP`, `READ`, and `WRITE`.
* Restrict the rights to only include `MAP`, `READ`, and `WRITE`, removing
the rights `DUPLICATE`, `TRANSFER`, `GET_PROPERTY`, and `SET_PROPERTY`.
We'll refer to this restricted handle as `h2`. This is equivalent to
invoking the [`zx_handle_replace`] syscall.
* Enqueue the message with `h2` instead of `h1`.
1. **FIDL bindings (receiver)**
* Invoke the [`zx_channel_read_etc`] syscall (or similar).
* Unwrap `h2` from the returned **handle info**. Unlike the handle
disposition, the handle info stores the handle's _actual_ type and rights
as reported by the kernel.
```c
zx_handle_info{
.handle = h2,
.type = ZX_OBJ_TYPE_VMO,
.rights = ZX_RIGHT_MAP | ZX_RIGHT_READ | ZX_RIGHT_WRITE,
}
```
* Get the expected type and rights from the FIDL type: `MAP` and `READ`.
* Ensure that `h2` has (at least) those rights.
<!-- TODO(fxbug.dev/89504): In the future, the following point will only
apply to debug mode. Explain that here once that is the case. -->
* Since `h2` has the unexpected right `WRITE`, invoke the
[`zx_handle_replace`] syscall to get a new handle `h3` that only has the
rights `MAP` and `READ`.
1. **User code (receiver)**
* Service `Method` using the `h` argument, which is `h3`.
[`zx_channel_read_etc`]: /docs/reference/syscalls/channel_read_etc.md
[`zx_channel_write_etc`]: /docs/reference/syscalls/channel_write_etc.md
[`zx_handle_replace`]: /docs/reference/syscalls/handle_replace.md
[`zx_vmo_create`]: /docs/reference/syscalls/vmo_create.md
[Zircon handle]: /docs/concepts/kernel/handles.md