Hello everyone, I am Hugo Le Feuvel, PhD student at the University of Manchester.
In this talk, I will present the result of my research on compartments interface vulnerabilities,
a work that will appear in NDSS 23. This is the result of a collaboration between Manchester,
Bucharest, Rice and Unicraft.io. Before starting to talk about interface
vulnerabilities, let me bring a little bit of necessary background. And a very important
notion in this work is compartmentalization. Compartmentalization is about decomposing
software into lesser-privileged components, such that components only have access to what
they need to do their job on. Compartmentalization is not particularly something new, so let
me illustrate it with a real-world example, web servers. Typically, web servers are composed
of components that do, on the one hand, privilege things like listening to port 80, and on the
other hand, of other components that perform risky operations like parsing network-provided
data. If we have these two components in the same process, then this process has to be
root, and that's problematic because if an attacker manages to compromise the network-facing
component, for example, then it will immediately own the root process. So what people do in
practice is decomposing or compartmentalizing the server into two entities in separate processes,
the master, which is privileged and not exposed to risky operations, and the worker, which
is deprivileged and exposed to network data. Both entities then communicate over shared
memory. Thus, if the worker gets compromised, it will not be able to perform privileged
operations and will remain contained. Recently, we have seen really nice advances in the field
of compartmentalization. People have been taking more fine-grained, more arbitrary,
and more automatic approaches to compartmentalization. And what these work do is taking arbitrary
applications, identifying a particular component that may be untrusted or risky, or trusted
and critical, and compartmentalizing it automatically or semi-automatically. The granularity of
the component can be very variable, ranging from libraries to blocks of code. Notice that
I'm talking about compartments here, not processes, as the isolation technology too
is very variable. In short, the goal of these works is quite ambitious. It's about compartmentalizing
legacy software with a low engineering effort and a low performance cost. Unfortunately,
as we're highlighting in this work, things are not as easy as they might seem. And privileged
separated software, cross-component interfaces are the attack surface. And there, all sorts
of things can go wrong security-wise. Let me give you a few examples. Let's say we have
two compartments. One on the left, malicious, and the other one on the right, trusted, protecting
some secret. The compartmentalization mechanism guarantees us that Compartment 1 cannot access
Compartment 2's memory directly. So that doesn't work. However, Compartment 1 is still able
to do legitimate API calls to Compartment 2 with, for example, an invalid pointer. If
Compartment 2 doesn't validate the pointer, it will risk exploitation. Another example
is the usage of corrupted indexing information, for example, a size, index, or bounds, as
is done in this function. Another one is the usage of a corrupted object, such as a tampered
file pointer. And there are many others which will go through partially in the next slide.
In this work, we unify all of these vulnerabilities under the concept of compartment interface
vulnerabilities, or SIVs. SIVs encompass traditional confused deputies, IAGO attacks, which are
SIVs specific for the system called API, and their references and their influences under
influence and probably many others. They are all attacks revolving around misuse of a legitimate
interface. SIVs are very common when compartmentalizing and modified applications, as we further
highlight in this talk. They affect all compartmentalization framework because they are a fundamental
part of the problem of privilege separation. To put it in more precise words, we define
SIVs as the set of vulnerabilities that arise due to lack of or improper control and data
flow validation at Compartment Boundaries. We observe three classes of SIVs, data leakages,
data corruption, and temporal violations. Within data leakages, we differentiate between
address leakages, which can be leveraged to de-rentamize compartments and mount further
attacks, and compartment confidential data leakages, which result in information disclosure.
Both are due to data oversharing and sharing of uninitialized memory. We have already illustrated
a range of data corruption attacks in the previous slide, but generally, there are
not to happen in situations where interface-crossing data is used without appropriate sanitization.
They can affect control as well as non-control data.
Finally, temporal violations include vulnerabilities like expectation of API usage ordering, usage
of corruptive synchronization primitives, or a shared memory time of check to time of
use. Temporal violations are usually caused by a wide range of behaviors, including missing
copies, double fetches, and generally lack of enforcement of API semantics. This is a
broad and succent overview, but the paper provides a full taxonomy including an analysis
of existing defenses. So having observed and characterized the problem, we asked a few questions.
How many SIVs are there at legacy-imported APIs? Are all APIs similarly affected by SIVs,
for example, taking library API generally versus module APIs generally? Do we observe
systematic differences? How hard are these SIVs to address when compartmentalizing?
And finally, how bad are they? If for some reason you don't fix one of them or just
decide to not fix them at all, what is the impact on the guarantees that compartmentalization
can give to you? We believe that it is really critical to understand
these points to be able to provide countermeasures that are adequate, systematic, and usable.
And so the approach that we take in this work to answer these questions is to design a tool,
and more particularly a fuzzer, specialized to detect SIVs at arbitrary interfaces, and
we call this tool Comfuzz. Then we apply Comfuzz at scale to a range of applications and interfaces
to gather a dataset of real-world SIVs. Finally, we study, systematize, patronize
the resulting dataset to extract numerous insights on the problem of SIVs in compartmentalization.
In the next slides, I will give a quick overview of Comfuzz before focusing on the dataset
and insights. So let me give you a high-level overview
of the fuzzer first. Taking unmodified applications, we instrument
them to intercept cross-compartment calls. Compartments are freely defined, for example,
a particular library boundary or an internal component interface.
We based our prototype on dynamic binary instrumentation using Intel PIN, but also explored other
instrumentation approaches, for example, LLVN-based. The interface between the trusted and untrusted
components is automatically detected using binary debug information.
Our fuzzing monitor then drives the exploration by ordering mutations of the data flow to
simulate attacks from the malicious compartment to the trusted compartment.
The workload used to drive the program is application-specific, for example, benchmark
tools, test suites, custom workloads, etc. You could even plug another fuzzer like OSS
there. Finally, the fuzzer automatically triages
and stores crash reports that includes de-duplicating, reproducing, minimizing, etc.
The paper provides much greater details on these technical matters, and I will be happy
to elaborate if you have questions. Using Comfuzz, we gathered a substantial
dataset that we carefully dissected. Here you can see the paper's big table that
summarizes the dataset. Let's have a closer look at it.
Overall, we applied Comfuzz to 25 applications and 36 APIs, for a total of 39 scenarios.
We considered a selection of library APIs, module APIs, and internal component APIs, trying
to focus on scenarios that make sense in popular software.
In fact, 16 of these scenarios have been previously considered by about 12 studies in the literature,
and the attacks that we find apply to them as well.
In total, we find 629 SIVs. We classify these SIVs in five impact classes, read impact,
write impact, execution, memory allocator corruption, and null point under reference.
With this data, the first questions that we try to answer are how many SIVs are there
at legacy or unmodified arbitrary APIs, and are all APIs or code similarly affected?
And looking into this, we quickly confirmed that SIVs are absolutely widespread among
unmodified APIs or code. Having said that, we also highlighted significant
disparities of prevalence among scenarios, and that's the really interesting part.
For example, we observed variations of SIV counts from 0 to 105 across APIs.
That's quite significant. Take a look at this plot, which represents
for each scenario the number of vulnerable API endpoints versus non-vulnerable.
It clearly shows that SIV prevalence among applications and APIs is very heterogeneous.
We have large and almost totally SIV-free APIs, and small and fully vulnerable APIs.
In fact, we show an entire absence of correlation between API size and SIV count in this dataset.
So while clearly, yes, SIVs are widespread, no, not all APIs are similarly affected.
This motivates us to look into the patterns and effects that influence these observations.
And doing so, we observe recurring APIs and patterns that result in SIVs.
This really comforts us in the idea that the presence of SIVs is influenced by structural
properties of the API, rather than API size or quantity of shared data.
In this talk, I will present one of these patterns, but there are more in the paper.
And the particular pattern I want to go through concerns modular APIs.
Indeed, we noticed that modular or module APIs are the most SIV-vulnerable interfaces
in our study. On average, we observe that module APIs feature
more SIVs and worse SIVs than any other class of APIs.
And looking at the structure of these interfaces, it makes sense.
Unlike library APIs, module APIs must be very generic and yield high performance.
As a consequence, we have patterns with the application exposing its core internals
and its core states to the module to achieve their generosity and performance.
But this results in a much larger attack surface exposed to the module.
Take the example of this data structure exposed to potential malicious modules by the Apache
HTTP core. It is a very complex with over 75 fields,
60% of which point us, referencing core data structures like memory pools, connection
state structures, or mutexes. What we observe with this pattern is a
somewhat counter-intuitive thing. Modularity is not always good for compartmentalization,
and in many cases, it can even be counterproductive.
This is only one of the patterns that we highlight, and there are more in the paper.
Now, having shown that SIVs are widespread but affecting applications unequally,
or APIs, let's look at their concrete security impact.
And the first thing that we confirm is that they are quite impactful. In fact,
over 75% of scenarios present in our dataset show at least one right vulnerability.
And worse than that, about 70% of write and read and 50% of execute vulnerabilities
are arbitrary, which means that the attacker, which means that when the attacker controls
a write or read primitive, then they are likely to be able to read and write anywhere.
And while only a smaller portion of these scenarios have execution impact,
it is likely that read and write primitives will be combinable to achieve execution capabilities.
In this talk, I will be concretely illustrating this impact with practical scenarios
and real-world SIVs taken from the dataset, where we demonstrate key extraction from a protected
OpenSSL. Once again, here, we show more details in the paper.
So here, we assume a scenario with two compartments, where the goal is to isolate OpenSSL.
For example, from a compromised web server engine X.
Isolating OpenSSL, or part of OpenSSL, is a popular application of compartmentalization,
both in the literature and in the industry. Thus, here, the compartment interface and
therefore the attack surface is the OpenSSL public API. Unfortunately, we find several SIVs
that enable for read, write, and execution impact. Take this option setting primitive, for example,
which is part of the OpenSSL public API. It differences an interface crossing pointer,
sets it, and returns it, clearly resulting in an arbitrary read and write oracle.
Any attacker that can compromise the application's control flow will likely be able to extract
SSL keys easily. Thus, clearly, if the API is not carefully enough sanitized,
the benefits will be pretty low, at most a form of weak hardening.
Now, you could tell me that it's not a good idea to protect at the public API anyways,
and that we should rather choose the OpenSSL internal key API that's much smaller.
So, let's take a look at it. This time, we have NGINX and most of OpenSSL in the untrusted compartment,
while we have the small key handling part of OpenSSL together with the keys in the protected compartment.
Unfortunately, here too, we find several SIVs. Take a look at this function of the internal key
API, for example. I only put the signature for simplicity's sake because the function is implemented
in per-generated assembly. You can manipulate the in pointer to point to the key that you cannot
directly access, encrypt with a known key, and then decrypt to get the secrets. Hence, here again,
attackers that can manage to compromise the application are likely to be able to easily
extract the key. Unfortunately here, fixing the SIVs requires to make the component stateful,
which is a fairly drastic design change. Overall, through these two examples,
I showed how existing OpenSSL isolation strategies collapse when confronted to SIVs,
and how important they are security-wise. To conclude this talk, let's take a quick look
at countermeasures. How do we tackle SIVs? Overall, we see two ways. First, making progress on
automatic and systematic countermeasures. Our paper highlights the limitations as part of our SIV taxonomy.
Second, learning from our study of patterns. We also believe that software component APIs
should be designed to feature low compartmentalization complexity in the first place. We provide a set
of guidelines to achieve this. The two approaches are complementary. Even in the presence of
countermeasures, well-designed APIs are wishable, as the first point is known to be fundamentally
harder. I will not have enough time to go over all the guidelines, but let me try to give you the
gist of them. First, not every interface is a good boundary for privilege separation. Maybe a particular
API doesn't fit privilege separation, and that's fine. In this case, it will be hard to harden anyways.
Second, we recommend that major attention should be dedicated to reducing the complexity of
interface crossing objects. Avoiding, for example, sharing of resource handle, system resource
extracts, synchronization primitives, et cetera. If this is not possible, it should bring us back
to the first point. The interface is probably not the right point to compartmentalize. For example,
because components are too deeply entangled. Third, compartmentalizable components should
enforce API semantics to be safe. For example, ordering or concurrency support. Under distrust
scenarios, it is not acceptable anymore to assume that the caller will respect them or face the
consequences. We are slowly coming towards the end of this talk, so let me summarize the key points
that I wanted to make. Civs should be at the center of every compartmentalization approach, and you
will likely not achieve tangible security benefits without considering them. API design patterns
influence the presence of civs and their severity. Overall, it's not so much about the size of the
API. It's about the complexity of API crossing objects. Addressing civs is not just a matter of
writing a few checks. In fact, strong solutions often require refactoring the API. Thus,
compartmentalizing apps goes much further than just setting and enforcing bounds.
We want this work to be an appeal for more research towards addressing the problem of civs,
systematically finding them, addressing them, or telling you what interface may good compartmentalization
boundaries. If you are interested in this work, I invite you to check out our paper and the code
and data set of Confuzz. I will now be more than happy to take questions. Thank you.
Thank you, Hugo, for this very accessible talk on this important topic of securing interfaces.
One question maybe that I can start with is something that you brought up yourself as well.
You say it's more about compartmentalization, but it also applies obviously to TEs. Can you comment
a bit on that? Is that something you consider Confuzz, your physics could be extended to
something like Gromine? Actually, maybe there are two different parts. I think the conceptual part
about compartment interface vulnerabilities, maybe we could remove the compartment out of
interface, out of compartment interface vulnerabilities, and just get interface vulnerabilities. I think
it has also been described by other works previously, notably some of the work that you did, Joe.
I think that applies to TEs really, really well. I think it's just a generic problem about
interfaces, and that fully applies to TEs. Regarding the fuzzer, from a very technical point of view,
I think that it might need some adaptation to be run on existing TE software, but it's absolutely
feasible. I think that it could apply there as well. We didn't really explore it because
obviously at some point we needed to restrict the scope of what we're doing, but I think it makes
sense. Following up on that as well, I think you mentioned in your slides one of the technologies
that you could use for compartmentalization. It's not only TEs, it's also something like Cherry.
It uses capabilities, and I'm wondering, TEs are not great in these vulnerabilities because
you have these confused specialty attacks that you explained also, where you have a pointer
that you essentially can dereference. With Cherry, with capabilities, you have sort of
native mitigations for many of those, capabilities I think were made with the idea of avoiding
confused deputy. Can you comment a bit on what underlying technology can mean for the vulnerability
of compartmentalization? I'm not sure if I can, I don't think I can share my screen, but maybe
I can. But you can put a link maybe in the chat for people. Actually in the paper we did
talk about this, so I'm just going to share my screen, but maybe I can. I'm sorry, I just
broke everything. I just posted the link, I don't know if I triggered something terrible.
I think I see the link, I think you unmuted that or something. So the paper goes in data,
can you summarize maybe in the minute that remains? Absolutely, yes. So Cherry provides
some features that as you said are really nice in addressing some of the spatial part
of the compartment interface spectrum, of the SIV spectrum. It does not solve everything,
it's not magic. Like many of the leakage issues remain, many of the temporal issues remain
as well, because to some extent they are a little bit more high level than just spatial
things on memory. So they still apply. For example, the issues with ordering of interface
calls. If you have an interface that has some ordering expectations, for example calling
function one before function two, and you don't respect that, Cherry is not necessarily
going to help you. So this is going to remain. So it does address part of it, but it's not
necessarily going to help you. Thank you.