blob: b5266b4400a8713dfbd422d449f1965cc3ee2007 [file] [log] [blame] [view] [edit]
# Rubric for writing Starnix syscalls
## Processing arguments
Syscalls should process arguments in the order they are provided from userspace.
For example, arguments should be validated in the order they are provided to the
syscall. Validation order is especially important if different the syscall
produces different Errno values for different validation errors because we need
to return the correct Errno value to userspace if there are multiple ways in
which the arguments are invalid.
If the syscall argument needs to read userspace memory (e.g., if the argument
is a `UserAddress` or a `UserRef`), the syscall should read userspace memory
using arguments in the order those arguments are provided. The order in which we
read from userspace memory is visible to userspace because those reads can
generate faults, which are reported to userspace.
## Error handling
Userspace should not be able to use syscalls to crash the Starnix kernel. For
example, syscalls should not use Rust APIs that panic (e.g., `unwrap` or
`expect`) when they encounter invalid or unexpected parameters.
## Flags or options
Many syscalls take a bitfield argument (often a `u32`) that carries the flags
or options for the syscall. A good practice is to validate this bitfield by
checking whether the argument contains an unknown bits before doing more
detailed processing of the argument.
Example:
```
if flags & !(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW) != 0 {
track_stub!(...)
return error!(EINVAL);
}
```
Use the `track_stub` macro to track the unknown flag. Tracking unsupported
features helps people who are debugging userspace code notice that the issue
they are seeing might be caused by missing functionality in Starnix.
Starnix often uses the `bitflags` macro to define a Starnix-internal type for
bitfields passes as arguments to syscalls. In most cases, syscalls should use
the `from_bits` method to convert the raw syscall argument into the
Starnix-internal type because `from_bits` lets the caller explicitly handle the
case where the raw value contains unknown bits.
## Arithmetic on user-space provided values
Syscalls should avoid using arithmetic on values provided from userspace that
can cause numerical overflow. For example, if a syscall receives two numerical
arguments from userspace, adding those values (without validation) can cause a
numerical overflow if userspace provides excessively large values. In Rust,
numerical overflow causes a panic, which is not the correct way of handling
invalid arguments.
Instead, either validate the range of the numerical values or use a function
like `checked_add`, which lets the calling code handle overflow explicitly.
Consider using the `UserValue` instead of a raw numerical type to make it easier
to validate values from userspace.
## User addresses
When working with userspace addresses, do not use raw numerical values or raw
pointers. Instead, use the `UserAddress` type for addresses. If the address is a
pointer to a specific struct in userspace, use the `UserRef` type, which has the
specific struct as a type parameter. If the address is a pointer to a struct
that has a different layout on different architectures, use the
`MultiArchUserRef` type. The `UserRef` and `MultiArchUserRef` types are also
useful for processing arrays of objects.
Syscalls should not read the same location from userspace memory more than once
because another thread in userspace could modify userspace memory while the
syscall is executing. Instead, copy from userspace into a variable inside the
syscall implementation.
Syscalls that write to userspace memory need to be careful because userspace
might provide addresses that overlap each other. For this reason, syscalls
should not interleave reading and writing userspace memory. Instead, syscalls
should perform all their reads from userspace before performing their writes.