inferred implicit parameters for ergonomic object capabilities
a system that elegantly provides the security benefits of an effect system and ocaps, while also being convenient to use.
inspired by scala's implicit paramaters and the object-capability model (ocaps).
background: what is an ocap
put simply, an object-capability is an object that represents a capability to do something.
for example, on unix systems, having a file descriptor gives you the capability to read and/or write that file, depending on how it was created.
where this becomes useful is when the ability to create these objects is restricted. returning to the fd example, a setguid program might open all the relevant files as root, then drop it's privileges to that of a regular user. if this is done before interpreting user input, it means whatever that input causes the program to do, it can't do anything that requires root, other than accessing to those specific files.
step 1: implicit parameters
when calling functions, implicit parameters are automatically filled in with a value from the surrounding context unless it is explicitly provided.
implicit parameters are primarily identified by their type, so their name can be omitted if they are not named directly.
def add(a: Integer)(using b: Integer) = a + b;
def add_more(a: Integer)(using Integer) = add(a) + 1
given Integer = 2;
add(0) // 2
add_more(1) // 4
add_more(5)(using 10) // 16
implicit parameters are useful for programs that are going to be passing around the same arguments to a lot of functions.
step 2: using implicit parameters for ergonomic object-capabilities
the main problem with ocaps is simple: passing all those capability tokens around is tedious and annoying.
without implicit parameters, if all the functions in your codebase log and access the filesystem, then ever function call in your code will have two extra parameters for the logging and filesystem handles.
with implicit parameters, you can omit those params from the call, and they will automatically be filled, in if the containing function has implicit parameters of a compatible type.
step 3: inferred implicit parameters for even less boilerplate, while maintaining control
while implicit parameters exist to repetition in function calls, inferred parameters exist to reduce repetition in function definitions
inferring implicit parameters is opt-in per-function (via a simple syntax such as implicit ...
at the end of the argument list), and inferred parameters will be expanded in auto-generated docs.
the logic for inferring parameters is simple: if the containing function opts in, any function calls that would report missing implicit parameters will instead result in those parameters being added to the containing function.
note that if you want to manipulate a parameter directly, you would still have to name it as an implicit parameter, it could not be inferred. this is to prevent functions from containing references to identifiers they do not declare.
ideas for proof-of-concept implementations
it should be fairly easy to implement a prototype of this via code transformation.
a good candidate would be Julia, since it has a macro system, types, and is dynamic.
potential use in a faster and more secure operating system
modern OSes have many security features that require dynamic checking, often with hardware support.
one obvious example is process memory isolation, which prevents one program from accessing the memory of another.
there are a few past attempts to replace these dynamic security checks with static analysis (i'm sure they exist, but unfortunately i can't find them right now), however these were mostly held back by the compiler technology of the time (most of them used a modified version of C)
the main limitation of this would be having to distribute programs as source code in order to get substantial benefit, but with modern JIT compiler technology (as well as global compiler caches), this should be much more manageable.
instead of having an external manifest that tells what permissions an application needs, these permissions would be obvious based on the signature of that program's main
function.
more performant?
ring transitions and syscalls are expensive, and there are other costs associated with process memory isolation, such as having to have a general-purpose heap allocator within every process. these in-process allocators can only receive whole pages from the OS, and more importantly, the can only return whole pages to the OS, and only under certain circumstances.
having a single shared address space has the potential to improve performance significantly.
more secure?
wouldn't removing the kernel/userspace split make things less secure?
well, if that's all you were doing, then sure!
but there's a few more pieces of the puzzle: 1. most important stuff is in userspace anyways 2. we're not just removing it, we're replacing it, and we're replacing it with something much easier to use.
filesystems
the biggest security hole of modern desktop operating systems is the filesystem, where keeping files secure requires creating fake “users” for a program and using setuid hacks.
mobile operating systems manage this by simply locking down the filesystem and requiring the use of alternate apis for inter-process communication.
our operating system would take a different approach, where a program can only access parts of the filesystem given to it as objects, either by via a drag&drop file manager, or via the system shell, which would logically be a repl for our new programming language.
an example of ocap filesystem access is in the experimental FileSystemDirectoryHandle
browser api.
related work
Addendum 2024-09-30: isolation blocks
it has come to my attention that i have neglected to specify a useful concept: isolation blocks.
any function calls inside an isolation block will not resolve implicit parameters to a declaration outside the block.
this is not strictly necessary for the model to work, the same effect can be accomplished by using wrapper functions and passing parameters explicitly, but it makes things much easier to use.
this is expecially useful for interactive use, where it essentially allows you to enter a sandbox just by starting an isolation block. specific ocaps can be imported into the sandbox with the equivalent of scala's given
keyword.
Addendum 2024-10-08: $PWD as an implicit parameter
one concept that is very pervasive in UNIX systems is that of a “current directory”.
there is one big problem with this: it's a process-wide variable.
one solution to this that has emerged is the openat()
family of functions, these allow you to open a file relative to a file descriptor.
unfortunately, this is almost never used, due to the simple fact that passing around dir handles everywhere is kinda annoying.
under this new paradigm, everything that would care about the “current directory” instead takes an implicit DIR handle which all paths are opened relative to.
this removes pitfalls such as chdir()
in multi-threaded applications, while also making it clear in auto-generated docs which functions care about the current directory.
if we require all paths to be simple relative paths (i.e. not starting with /
and not containing ..
), then we get the ocap-like isolation described before, and each function would essentially run in its own chroot.