The first version of this project is based upon multiple precedent experiments aiming at highlighting the different points needed for the requirements of VLC within a context of sandboxing.
The different prototypes are described below.
The second prototype was much different form the first one in the sense it wasn't design from a security point of view, but instead as an evaluation of the software architecture points that will be needed to be addressed by the project.
The goal wasn't about removing right and resource access like the first prototype did, or even about designing a security policy. Instead, we focused on the requirements for the process isolation.
From there, we distinguished multiple models for spreading the VLC pipeline across multiple processes, which sums up into the following points:
- integration model: proxies or ad-hoc request
- execution model: how requests are handled across processes
- resource model: what are the mechanism to transfer system resources
The main point of design was the integration model, which can be summed up as the tradeoff between how easily it is to integrate the previous code while keeping the same paradigms and improving maintenance time versus how easily it is to switch incremental part of the code to be sandboxed.
Bringing history and other references here, COM in the Windows ecosystem is typically an instance of the first kind, with proxy objects mapping a remote state to the current process, with an interface allowing a normal usage of the code. It is strongly object-based, making use of the virtual table indirection to redirect the proxy in a strategy pattern way.
The second point was also important in the design. One property we want to keep is that the relation happens-before for requests happening in a process restricted to the set of events forwarded to another process also respect the same happens-before relation in that other process, ensuring that synchronicity is respected even across multiple processes.
The third point was mostly technical on the Linux side, but led to a complex
challenge on the Windows side. Indeed, transferring a system resource needs the
use of DuplicateHandle
with process handles having the PROCESS_DUP_HANDLE
access right on them, allowing a bi-directional transfer of resources and thus
potentially allowing one process to steal the access rights on resources
belonging to another process if done incorrectly.
Developing a minimal working architecture for each targetted system led to two kinds of possibles architectures:
-
The orchestrator architecture, where a priviledged process sets up the communication channels between two processes but doesn't interfere afterwards and thus define behaviour and security policy at creation time.
-
The broker architecture, where a priviledged process interface with multiple processes, and only exposes a communication channel to itself. It is then responsible to route the messages between the different actors and will need to check the source and destination of each message to avoid attacks, thus defining behaviour and security policy for each interaction.
The first model was very convincing for the following reasons:
- The communication channel setup between two processes can be seen as an unforgeable token allowing the system to communicate between the two parties involved. It can also be transferrable across processus boundaries. Thus, it joins the concept of capability-based security and allows a more natural approach by letting the access checks, the process boundary integrity and the messaging multiplexing to the operating system. Since the same work is needed from the operating system, it doesn't really reduce the attack surface of the mechanism, but operating systems are expected to have better security audits and stronger usage among a wider list of scenario. Thus it makes sense to remove the maintenance cost from the VLC side given the security stakes.
We can also note that it's always possible to build one model on top of the other as long as we introduce multiplexing, by using this multiplexing layer to build the other. For instance, it's possible to build a capability-based broker by adding new communication channel towards the broker when two processes needs to be able to communicate, instead of multiplexing the one used by the broker.
Like described in the threat model, the overall goal of the project is to sandbox the multimedia pipeline first. The second prototype focused on the output pipeline, including the decoders and audio/video outputs so this third one started with the beginning of the pipeline.
It benefits the real world situation because most security issues raised are linked to the demux or access parts of the pipeline, so it makes sense to start integrating isolation from there first, especially given that even if access may have access to the filesystem, demux usually needs zero access to any exterior resources.
The goal of this new prototype version is also to consolidate the previous design while formalizing and conceiving reusable parts for the different elements outside VLC.
Its current first step is to move the access into its own process, including IPC, RPC, security rules, interception mechanisms and VLC stubs to find a way to organize all of them together.
The second step will then move the demux into its own process, allowing to have an experimental process where we can try to ensure no resources are available.
A first analysis of the interaction made by the access with the rest of the application led to the following points:
-
LibVLC instance: Used indirectly to create new VLC objects (like the access itself) and use/forward the logger infrastructure to the module.
-
VLC variables: Read-only access to the global configuration and parent variables. There's no need for neither callback support (only v4l2 access is using them) nor write access to parents objects (but maybe to local objects?) and obvious non-working access modules like imem.c where a function pointer address is forwarded from libvlc are excluded from the prototype.
-
vlc_interrupt_t: Mechanism allowing the setup of an interruption context local to a given thread. It's used by the access to determine whether the IO operation must be cancelled. VLC
_i11e
suffixed IO functions are using this interruption context to abort the IO whenever it's triggered. It also exposes an API to forward the interruption context from one thread to the interruption context of another thread, but there's no multi-processus crossing currently. -
cache/prefetch: Those stream_filter modules are currently created by the same function creating the access.
-
input_item: The access has read/write access to the input_item_t element, which has an entity semantic and is in particular used by the user interface to display the item information.
This can be summed up with the following diagram.
Like mentioned in the previous section, being able to reuse components outside of libvlc / VLC is a major design point of the current prototype. The different elements that have been described are developped in this section.
Note that those elements are expected to be reusable:
- libsandbox layer
- libinterception layer
- the RPC layer
- the IPC layer
But everything still need to be described in VLC, and in particular the link between the RPC interfaces and the VLC API need to be written specifically for VLC.
For the command flow part, we want to translate vlc specific function call into rpc to another process. To make it work, it involves the following layers:
- The low level platform-specific IPC layer to send payload and handles to a different process.
- The RPC serialization and deserialization layer.
- The VLC proxy layer linking the vlc interfaces to the rpc interfaces.
This view doesn't account for security details for now, which will be added later in the page.
One main aspect handled in this view is the schisme between windows and linux handling of resources handles.
[mbu: it would be helpful to inventory the different types of handles and file descriptors required to be passed across privilege domains. restricting object types can be done on windows to avoid type confusions, i don't know yet if it is possible on linux file descriptors.]
In addition to the IPC, RPC and proxyfication layers, we add a libsandbox component whose role is to create, setup and start the sandbox worker/broker processes, and then manage the resource accesses, rights, handle type checks and custom policy in a multiplatform manner.
The policy and mechanisms that libsandbox must typically implement are:
-
how much ambient authority to leave to the process to be created, so that it can directly acquire resources;
-
access checks when trying to acquire new resources using a global identifier (e.g. file or registry key by name, process by pid, etc.) when the ambient authority is not enough;
-
list of resources to be inherited at process creation;
-
process mitigations on windows (e.g. handle bruteforce prevention, etc.);
-
system calls to allow on OSes with system call filtering capabilities (e.g. win32k.sys on windows, seccomp on linux) ;
-
etc.
Then the interception framework might also be externalized by providing an initialization method to setup the interception of both syscalls (by intercepting the c function call) or vlc functions (either for proxification or setting up the proxification).
The interception can use different methods to inject its redirection functions.
The first generic one is done at compile time and consists of replacing the function by their redirection one, and expose two global variable to determine whether functions must be redirected and to which handler. Then, it's possible to check this value at each call, thus providing an access-time mechanism.
The second one uses the dynamic linking properties to redirect during the runtime the different function to their proxified counterparts, thus making the check at linking time and never after. This approach can however interfere with other software trying to intercept library and system calls (e.g. antiviruses, debugging and profiling tools, etc.)
-
How to debug and trace within the sandbox?
-
How to report traces when it crashes on the user side? (multiprocess libunwind?)