Implement a system similar to OpenBSD's pledge()
In OpenBSD, pledge()
is a function to
restrict operations to those you pledged to use. For example, you can pledge to
only write to STDOUT/STDERR, and have the program crash if you try to write to a
file instead. Such an API is useful to guard a program against exploits. It's
also useful as a step towards sandboxing, making Inko more attractive in the
embedded space (e.g. compared to Lua); especially when we get rid of the GC.
Crucially, a change in capabilities doesn't affect existing files, sockets and other resources opened/created before the pledge. This allows you to set up everything you need, then lock the system down.
You can call pledge multiple times (unless the pledge
capability is
disabled) to further reduce capabilities, but you can never add capabilities.
The API I have in mind would roughly be as follows:
There exists a module std::sandbox
which provides one function: pledge()
.
Similar to OpenBSD it takes a single string argument that lists the capabilities
you pledge to use. An Array of Strings could also work, but would require an
extra allocation, and is a bit more verbose to write out:
pledge(Array.new("foo", "bar"))
vs just plede("foo bar")
.
The VM in turn maintains the list of enabled capabilities. By default all of the available ones are enabled, and pledging disables all but the listed capabilities. The VM checks these capabilities when performing operations such as opening files, writing to STDOUT, etc.
The result would be something like this:
import std::sandbox
fn main {
# Setup code here
# ...
sandbox.pledge('stdio rpath wpath')
# The rest of the code would be called here
}
Attempting to perform an operation not allowed results in a panic, terminating the entire program.
Available capabilities
I'm not sure yet what the full list of capabilities would be, but some of the ones I can think of right now are:
ffi
-
unix
: UNIX sockets -
net
: IPv4 and IPv6 sockets -
stdio
: STDOUT, STDERR and STDIN -
rpath
: read-only file operations -
fpath
: write-only file operations -
pledge
: allows use ofpledge
itself -
spawn
: allows spawning of new Inko processes -
exec
: allows spawning of new OS processes -
env
: access to environment variables
Scoping capabilities
Option one is to have a global set of capabilities shared by all processes. Option two is to scope them per process, and have newly spawned processes inherit the capabilities of the process that spawned them. The latter could be interesting: you can have the main process with all capabilities, spawn a new process, then lock that one down separately. Implementation wise I don't think this will make a big difference, other than processes needing a tiny bit more memory to store the capabilities.
Structure
We can probably use a single 64 bits integer, and use each bit as the state of a
capability. A value of 1
means the capability is enabled, and 0
means
disabled.
Another option is to just use one bool
for every capability. This needs a bit
more space over time, but may make it cheaper to look up capabilities, though I
doubt it would be something you'd notice.