Dear viewer, these subtitles were generated by a machine via the service Trint and therefore are (very) buggy. If you are capable, please help us to create good quality subtitles: https://c3subtitles.de/talk/339 Thanks!
Thanks for the introduction.
Hey, guys, it's a pleasure to be here.
Awesome. Yeah,
let me tell you some story from the
trenches that we are fighting
where we are fighting every day.
This is joint work with Bova Quittance
itself, Lazlo Chakiris, George
Candia, our Sheika and
Don song. Most of the work has been done
at UC Berkeley.
Bova and Laszlo did most of the
heavy programing work and heavy lifting.
While I only could add some of the
support libraries and a
little bit of some of the other stuff,
but most of the.
Programing praise goes to the
goes of the two students,
also blame me for all the
bad jokes in the presentations.
It's it's not their fault.
So let's start right away.
We live on an ugly planet
at Buck Planet, and
it's just terrible out.
There are so many bugs.
It's just crazy.
And we are overrun by them.
If you just look at what happened
in the last couple of years,
for example, pointers not working,
memory corruption.
Bugs are abundant.
They are everywhere.
They are just literally everywhere.
And there are so many CVS's
are out there
that an attacker
can gain some form of control, flow,
hatcheck
permissions or capabilities.
And this is just as
we just picked a couple of programs
out there, like Acrobat Firefox,
i.e.
General OS X and Linux books
that allow an outside attacker to gain
control, flow or code execution
capabilities on a system.
And the attacks are on the rise.
And all of these attacks
rely on some fundamental memory
corruption, vulnerability somewhere in
the background.
And we are being overrun
by all these books, even though we are
investing more and more time into fuzzing
and tools that find books
that are just way too many out there to
that that we can fix.
Manoly.
So we have to come up with some
techniques that allow us to
have some proactive steps
against these books and vulnerabilities
that are in our software to protect them
against different form of
control for high tech attacks.
And just to name two prominent examples,
this year, there was the the Heartbleed
vulnerability, which was a memory safety
or a memory corruption vulnerability and
shell shock as well.
And some of these vulnerabilities, the
sleep in slumber and the software for
many, many years.
And suddenly they are found and they can
be exploited on a very,
very wide scale.
And I argue that we have to come up with
some proactive defense mechanism
that protects us against these kinds of
vulnerabilities.
So let me take a step back
and explain to you what memory safety
is all about and why we don't have it
yet.
So at the most
basic level, memory safety
relies on some form of or a memory
safety, corruption or lack thereof relies
on some form of invalid reference.
So, for example, there can be dangling
pointers, could be a temporal
issue. So, for example, at one point in
time, in a language like C
or C++, we have a memory object
and we have a pointer to that memory
object. So if you have a valid reference,
but we free that object at one
point in time and it becomes an invalid
object. And there's a there's a dangling
pointer to that object which can lead to
some form of memory corruption.
Then we reference it or they
are out of bounds. Point to imagine that
we are working and iterating
through an area
and as soon as we step outside
of the area, we have an out of bounds
pointer, which is some form of spatial
memory safety violation.
Both of these pointers are still fine
as long as we don't reference them.
But something bad will happen if
the pointer is read, written or freed
again and we end up with some form of
corruption.
So otherwise, there's no violation.
The threat model that we are going to
have for this talk looks as follows.
We assume that our attacker can
read and write arbitrary data and
the whole process in which
the attacker can read code as well.
But the attacker cannot modify
the code as it is being executed.
And we'll discuss some of these
limitations later on.
Or the attacker can influence program
loading. So if the attacker has control
of the whole process image before
we can actually set up our defense
mechanism, there's nothing we can do,
so to
speak.
A little bit more permission wise,
there's there are explanations for the
attacker on the code,
but the attacker can change to the
code pointers.
There is reason, right, for the
heap and read and write for the stack.
So just
so that we are all on the same page.
I'll quickly walk you through some
form of control flow, high tech attack as
it is being used nowadays to
sidestep all the defense mechanisms that
are active on current systems
so that we can we can understand how
the defense mechanisms that I'll present
later on will work.
So we have this simple
C program on the left hand side.
We define a function pointer and
there's some weird pintura magic
going on Bavji, where we assign
Q to a buffer plus some
attacker controlled input so the attacker
can kind of.
Point summer into
memory by carefully crafting
the inputs to that pointer
after that happened, the function
pointer is assigned to the function foo,
but later on, the attacker, a controlled
pointer Q is assigned a value.
So initially the
programmer intended Q to point into
the buffer, but the attacker
can override or can control.
Q To point to some function point or
later on somewhere, somewhere in memory
and as a second step of the
attack, the attacker can write
to the function pointer and instead of
pointing to the valid function that we
want to execute, the
function pointer now points to an attack,
a controlled Gachet and is allows a
control flow hatcheck attack that the
attacker can then
exploit as soon as the pointer
is dereference.
And as soon as that happens, all the bets
are off. The attacker runs, runs code
and can get control over your system.
But you might say we've got all these
fancy defense mechanisms out there are.
What about these existing defenses?
So, yeah, that's actually true.
We've got a bunch of defense mechanisms
like data execution prevention, which
prohibit an attacker from injecting code.
That's why I assumed in the beginning
that the attacker cannot
modify the ongoing code.
So this is all fair and nice.
But in addition to that, a problem
on current systems is that the attacker
can restitch individual
Gachet right next to each other and
thereby execute arbitrary colts'
code, so-called return oriented
programing or trump oriented programing,
if you want to look it up.
There's a Asla, which is a nice
probabilistic defense mechanism that
shuffles memory around, but it's only a
probabilistic defense.
And if we assume that the attacker can
read arbitrary memory, the attacker can
easily sidestep this defense
mechanism by reading out some pointers
and thereby reconstructing the
randomization algorithm that was used
and thereby leaked the correct addresses
and craft an attack carefully
that sidesteps this defense missile
defense mechanism.
And the same goes for four stacked
categories that protect against
overriding the return instruction
point on the stack.
If the attacker can read out the stack
from before that, the attacker can
carefully craft an attack that sidesteps
the defense mechanism.
So it looks like they're almost right,
if you want to know more.
There are a bunch of nice papers out
there or you can just watch the
talk from last year
where we evaluated all the different
kinds of war games that are in
yet in memory and how these
these war games all play together and how
an attacker can exploit them.
So now that we know that
we cannot just use C-code
or C++ code because they are the code
is full of of bugs, you
might say, ha ha.
Memory safety to the rescue bill just
moved to safe language, right, instead
of coding in C or C++
program, language research
has come up with a whole bunch of
languages. There's Python,
there's Java there, C
sharp or there swift.
These are all memory, safe language
languages. And these problems that I just
talked about would completely go away.
Sounds good, right?
So where do we stand?
Let's assume let's look at
the Dropbox's uploader for
your files.
It's written in three three thousand
lines of code, completely
memory safe.
Everything is good, right?
There's no way you can exploit that cut.
But imagine to run these three
thousand lines of Python code.
What do you need on top of that?
Well, for one, there's the Python
runtime, half a
million lines of code.
It's written in C.
And again, we have all the memory
corruption vulnerabilities, in addition
to that, we used to Lipsey,
to all the courts to do all the
system court, two and a half million
lines of code.
And on top of that, there's a thing
called the Linux Linux kernel or
the the Windows kernel, another 16
million lines of secret.
So C-code or C++ code,
low level languages are not going to get
away.
There's no way we can fix all these bugs.
And it's just way too much of an attack
surface. And we need to come up with
strong defense mechanisms for those.
So if you look at the current state of
defense mechanisms, a
lot of the code that runs on
the system, even if you program in a safe
language, is actually unsafe.
And only a small, tiny little
subset is written in safe
code, if at all.
So if you compare.
The how how close are we to safe
languages?
We are way off and there's
a huge way to go
and we need to get much better.
So you might say, OK, fair
enough.
We cannot rewrite everything in a in a
safe language, so let's just retrofit
safe language on top of the
old languages. Right.
Sounds like a good idea because memory
safety first, all these books
would go away and
academia and industry came
up with a bunch of defense mechanisms on
top of that.
So could that you can just plug into your
compiler and more
or less easily just recompile your
software to a couple of hundred
hours of of code changes and you're good
to go.
So they're soft bound plus seats,
which is a great defense mechanism that
retrofits memory safety on top of C
and C++, but it comes with
a one almost one hundred and twenty
percent price tag.
So it's fairly expensive.
Also, it's not exactly
compatible with all the code.
But it runs most of the code or a lot of
the code, they're secure to try
to retrofit memory safety on top
off of the C language and restricts
or enforces a strong
or type system that then comes with some
form of memory safety on top of it.
But again, it comes as a 60, 60
percent price tag, which is way too
expensive in practice
or address sanitizers, which is
great for debugging and a great tool.
But it's on one hand it's only
probabilistic.
And again, it adds 73 percent
overhead.
So even though we know that
as the tools are right now, they have way
too much overhead.
That's what we want to do.
We want to retrofit memory safety
on top of these existing
compilers and then enforce it at
runtime to ensure that we are protected
at all times.
And just to show you where this
overhead comes from.
I'm going to give you an example of
how soft bound would
enforce memory safety on this on a small
program.
So we've got a couple of lines of code.
We have a buffer that is that is
allocated.
We have our pointer cue again, which
is from the from the motivating example,
which is assigned the initial
base address of the buffer, plus some
user controlled input
southbound as a compiler based
transformation that adds additional
checks in the background
that are then executed at runtime.
So first of all, it
assigns metadata to all the pointers.
So as soon as as we declare
buffer here, there are two additional
variables declared that contain
the the lower
and upper bounds of the
of the buffer itself.
So we have a lower pointer and an upper
pointer, and those are carried along
all the all the accesses and so on
if we do have assignments.
From other types or
across pointers, we propagate
metadata, so we see that culo,
the two variables, Culo and Kuepper
are assigned to the lower bounds of
the buffer and the upper bounds of the
buffer. So this this metadata
is carried along and can be used
for further protection.
In addition to that, we
have.
Dereference or a check whenever
that pointer is used.
So before star Q
Q equals in Putu is
actually executed, we do a check
if the current value is Q is
inside the bounds and
aboard otherwise, which protects
us from any possible attacks
against us
that would that would design it out of
bounds.
So the function pointer cannot be
overwritten in this example.
So.
What we have is or what we get
is this one hundred and sixty percent
performance overhead because they are
just way too many pointer assignments
in low lot of languages like C or C++.
And the compiler has a very hard time
at optimizing these and getting rid of
all the surplus assignments.
So in reality, or with
a perfect compiler, we should be able to
prove for many more accesses that they
are actually safe.
But it's very hard to reason about
these things on the on the compiler
level. And therefore, we have to very
high or fairly high overhead, even as
a very sophisticated compiler analysis
framework like LVM offers.
So it looks like this, right, we are
walking towards that that safe,
safe haven and we do have that
safe haven, but we are facing
a problem that we either have safety
or flexibility and performance.
So it's an either or which is
really, really bad.
We would ideally we would have to
we want to have both safety, flexibility
and performance.
You might want to know more about
memory safety.
Feel free to or read the paper
or watch last year's talk
by Andreas, who presented
southbound four for FreeBSD.
So now that we know how memory safety
works can be adapted
somehow so that we can protect only
a small set of data.
We no longer want to protect all the
data.
Because that's that's way too expensive,
right? Otherwise, we would run again into
into this high overhead bird, possibly
instead of protecting all the data that
is out there, let us just focus
on a small subset of data and
protect that small subset of data.
So just a couple of code pointers
on the heap and
a couple of pointers
and variables on a stack that we deemed
to be protection versus.
So instead of just enforcing
a probabilistic defenses for
defense, for all the data or
a strong defense mechanism for all, the
data is high overhead.
We offer strong protection for
a select subset of data.
And in addition to that, we
have a very different attack
from model to other defense mechanisms.
We assume that the attacker may modify
any unprotected data that is out there
and the attacker can freely write to any
of the data that we don't really care
about.
So instead of protecting everything a
little, we protect
a small set of data completely.
And just to give you a.
A peek preview
from we change
the overhead numbers from complete
memory safety, which faces one hundred
and twenty percent overhead
to as low as.
Only two to
eight percent overhead if we
protect only code pointers,
so we focus for our protection on
code pointers and enforce strong memory
safety for code pointers.
So anything that is used
in a contraflow decision
at one point in time.
Through an indirect jump through in the
call or anything like that, they'll be
protected by our defense mechanism,
but we don't care about any of
the data that is on the on the heap or
on the stack. And we focus only on the on
a small subset of
coach pointers that actually are
used for control flow decisions.
And therefore, we can protect
against control, flow, hatcheck attacks.
We don't protect against any data
data attack.
So to
actually enforce enforce
this, we had to come up
with a set of special techniques
that transform your program
into a protected program.
One of the core ideas that we have
as something that's been out there
in or has been used
in networking for decades but hasn't been
used in software engineering, we
separate, uh, separate
control data and,
uh, the control plane and the data plane.
So instead of having just one single view
of memory.
We separate the program memory into two
different views, so on one
hand we have the regular memory with
all the buffer's, all the pointers
and so on. And on the other hand,
we have safe memory
and our safe memory contains
code pointers and code pointers only.
The regular memory, on the other hand,
contains all other data.
So.
It looks quite bad, right?
But I guess you get the gist
so.
Safe memory contains all the safe code
pointers, regular memory contains
everything else are
just at the memory locations
for the function, pointers are not used.
The memory layout itself is unchanged.
So in the place where
a code pointer was before, there will
just be an unused block.
And a control plane, any
memory location as either a code
pointer or null and
we can imposters memory of you using
some compiler based technique.
And enforced that the safe
memory region only contains coach
pointers and nothing else using the
word transformation, but more on that
that later. So for now, just remember
that we split the memory of you.
We have a safe memory view that contains
code pointers, nothing else,
or null values.
And the regular memory is the rest of the
data.
So on the stack, we have
a different kind of technique.
We split the stack similar to
the the heat memory
into a safe stack and
a regular stack.
Has it been like this since the
beginning?
It's like a third of the slight cut of.
Let me try to do something.
That's annoying.
Uh.
So much for.
TVI out.
OK, let's let's try to continue.
So we've got the safe stack and the
regular stack on the safe stack,
we add an additional
compiler instrumentation path that looks
at all the local variables on
the on a stack frame
and everything that we can prove
in our instrumentation pass that is
safely accessed.
So any local variable that is that is
safe is pushed to the safe stack
while all the other variables that
we can approve are safe, are
pushed to the stack.
So stuff that could cause something to be
unsafe as either some of your pointer
arithmetic if it escapes
the local.
What the heck is this?
So if it escapes the local that frame or,
yeah, whatever, we push it or we
keep it under under regular SEC and we
assume that the attacker can
corrupt anything on the regular stack.
So we
ensure complete safety on the safe
stack.
We don't give any guarantees on the
regular stack.
So if you look at our small code snippet,
the variable R and
the return address would be pushed onto
the safe stack and
the buffer would be pushed
onto the unsafe stack and the attacker
could corrupt some of the other stuff on
the safe stack.
So in using this principle,
we can ensure that the attacker
can only corrupt data that we
are not interested in protecting and
we can decide how much of the data
that we want to protect.
And in our case, we protect anything
that's code Pinder's the
contains code pointers or is used in
contraflow decisions or
can be proven or is proven to be safe
on the on a staggering.
So if you look from above
at the memory
layout, we have
two areas of memory, we have safe memory
for coach pointers and regular memory
for all the other pointers for
the safe memory, we ensure
and guarantee that all the accesses
are safe.
But for the regular memory, they
are fast, but we don't give
any other guarantees.
In between safe memory and regular
memory, veivers views, hardware based
instruction level isolation, using
some technique like segmentation
or some other form
of of blinding.
The regular memory contains regular heap
stack frames and the only
code regions, and to save memory
contains likewise the safe keep
the safe stack are
in the safe stacks of the individual
threats.
So.
Now that I've I've shown the
basic overview of how we separate
code pointers and it's actually
just like in the name, code point or
separation, that all the code pointers
are in a completely different memory
space, how much protection
does that give us?
Let's look at how we can
attack code point or separation.
We have two small C program here.
It looks very similar to the to the
motivating example I used in the
beginning with a slight difference.
So instead of just assigning a function
pointer, we assign
a function pointer through a struct.
So let's assume that the function is
somewhere in the struct and we have a
double in direct reference
to the function pointer.
Again, we have
the opportunity for the attacker to clear
up the buffer, and
we know that the attacker cannot corrupt
the function pointer itself because
this function pointer is a completely
different memory space and therefore
protected. So the attacker using this
simple right, which has a different type
and our type based analysis ensures that
the function pointer is in the other
memory space detector cannot override
the function pointer itself.
But what the attacker can do, the
attacker can override the structure,
the struct pointer and
let it point to somewhere else in memory
where possibly there might
be some other function pointer lying
around.
So if you assume that this or
if you think back to that different
memory of you, that.
Code point memory of contains only code
pointers or null,
and we whenever a
region is freed, we cleared
the code pointers in addition to that.
So this memory of you only contains
the code pointers that are currently
active and in use.
But using this kind of technique,
the attacker can redirected to some other
code pointer, which is
null or a pointer to another function.
But we'll see what,
uh, what the defense mechanism in the
end is like.
So using the compiler based technique
in our analysis analysis phase, we
identify all code pointer
accesses through some static
type analysis and redirect
them to the different memory of you.
The two men will use again are separated
using instructional isolation, for
example, segmentation on eighty-six
or blinding on 64,
and we give a bunch of security
guarantees.
So using the separation
and you have to think about it for a
while until you understand the security
guarantees.
The this separation ensures
that the attacker cannot forge
any code, any new code pointers.
So using a.
Memory, right, using a
non pointer type,
the attacker can never write to that safe
memory of you.
We guarantee that any pointer
that is written into that safe memory
memory of you is either an immediate
like a fixed value.
Or assigned from an
order code pointer.
Therefore, an attacker can never
construct a code pointer to some other
location in memory,
what the attacker can do using
that doubly indirect or multiple
going through multiple interaction can
replace existing functions through these
in. So, for example, foobar.
Function can be turned
into food banks function, but it must
be a code point.
It must exist.
The current memory region must be valid
at that point in time.
And then successful attack is very
unlikely for this.
So what we basically
did is we took all the coach pointers
that are used in a program, grouped
them together.
And make them look out for
each other and protect them against other
modifications from uncowed pointers
that are out there and thereby protect
them against other
modifications.
Also,
if you think about it, all
the.
Memory safety violations are usually
not in point or arithmetic
to code pointers, but
in point or arithmetic to some other
buffer or some other input.
So it is very likely that this will be
the exploitable.
So.
This is the simple code point
separation, but can we do better
than this?
Remember, we talked about memory safety
before and here I didn't talk about
memory safety yet.
So let me see if
I could point to integrity takes
code point or separation as a baseline.
And in addition to that, and forces
memory safety for the code Pinder's.
So the sensitive pointers
that we protect are the code pointers.
And in addition to that, any pointers
used to access to sensitive
pointers. So everything that's used in
the dereference chain automatically
becomes protected as well.
In addition to that, we enforce
bounced checks, as I've discussed before,
for all the
for all the sensitive
pointers that we identified.
We could identify individual instances
of safe, sensitive
pointers, but we do an over
approximation, which does not
which does not lower security but
might increase overhead instead of
protecting individual instances, we
protect types that we identify
and deem to be sensitive.
So it's an approximation which is safe.
This approximation only only affects
performance, but we measure it on spec
two thousand six, which is a standard
benchmark that using
this full transformation, roughly six
point five percent of all
the memory accesses
are access to sensitive data.
So let's see how our example looks
like if we have
bounced checks in addition to the
to the separation.
Just like in the example of memory
safety, before we at
the lower and upper bounds
and we execute an additional
check if the
bounds are still valid and
before the attacker could
override the function pointer or
redirect a pointer to
an exception would be triggered whenever
that is referenced.
So if we compare code
point to integrity and code point
or separation, what additional kind of
security guarantees to get
our first defense mechanism
separate sensitive pointers from
regular data, both of them
are based on a or use a type A
static analysis.
But where they differ is what
sensitive pointers are for
coach point or separation.
Sensitive pointers are code pointers only
for a code point integrity.
The ads, in addition to that, pointers
to sensitive pointers, which is a
recursive definition and kind of follows
the dereference change for and
therefore protect anything that
points to
to coach pointers directly or indirectly
through anything.
We guarantee that accessing
sensitive pointers is safe.
So we use instruction, level
of granularity, separation and
four code point to integrity, in
addition, we use runtime bounce checks
on top of that.
Also, on the other hand, accessing
regular data is safe,
is fast.
So we don't impose any additional
instructions when accessing
other regular data that we don't want to
protect, and this allows us to get
very low overhead.
So what kind of security
guarantees do we have?
Four code point or integrity,
we offer a formerly guaranteed
protection, and if you are interested in
the more theoretical aspects, we do have
a formal proof in our paper.
All in all, if we enforce
memory safety for the code pointers and
the sensitive pointers that we identify
on top of it, we run
into eight point four to 10 percent,
ten point five percent overhead for
roughly six point five percent of memory
accesses, which is
almost deployable and definitely
deployable.
If you are interested in
protecting against specific attacks or
in in different security contexts
for code pointer separation.
We offer strong protection in practice,
so it will be very hard
for an attacker to find.
Uh.
Uh, an exploitable
condition, we don't say it's impossible,
but it will be very hard.
It's definitely much stronger than
any of the defense mechanisms that exist
currently. It offers complete protection
against anything that are
against any return or
into programing attacks and strong
separation for anything.
Any code pointed out is on the heap adds
almost negligible overhead.
We have zero point five to one point nine
percent overhead to appoint five suspects
CPU and one point nine percent average
overhead for the four Onyx benchmarks.
And we protect roughly two point five
percent of every Access's.
What we can also do is
we can only
use the safe stack, which
protects the
return instruction pointers on the stack,
and we we offer full protection
against return into programing attacks at
negligible overhead.
So we've got different levels of defense
mechanism that you can use whenever
or however you feel like and
how much how how
big your your budget is.
So if you want to give strong
deterministic guarantees, use code point
or integrity, if you want to protect,
if you want to use strong protection is
code for separation.
If you just want to be sure that the
returner into programing is now possible,
use the safe stack.
So enough of the
design of the system.
Let's talk about the implementation
be implemented on top of Klann,
where we collect type information
and all the transformations
are then done on an album
instrumentation pass that either
or the CPI, KPS and the safe
stack.
We have additional runtime support
for the the safe stack
and the safe heap and all the management
functions on top of that.
And currently we support X 64
and X eighty six, although
X eighty six is a bit shaky
for the systems support systems.
We support Mac OS, X, FreeBSD
and Linux.
The current status of the implementation,
this is a research prototype after all
right. Nothing is perfect.
We have great support for code or
integrity, separation and
stack pointers for Mac OS, X and
Freebasing, for example,
and are
fairly good support for
for the other architectures.
But we are working on on improving the
code quality.
We are currently in progress
of up streaming the patches.
And as you might imagine, there's a
whole bunch of changes out there.
So we believe
we built a couple of chunks
that we are up streaming one after
the other. And currently we are working
on the on the safe stack because
that's the logic.
First choice.
It has lower overheads than stack
cannery's that are currently used,
currently used. It offers full
deterministic protection and it's vitally
compatible with anything that's out
there. It's easy to add and gives you
very strong protection.
So this we are hoping to finish
integrating the safe stack soon and
we will then continue.
Mr KPS and S.P.I patches.
You can falke the current version
on GitHub. It's all out there.
Feel free to give it a try and
we are happy to hear back from you
if you, if you find bugs, if you find
other things.
Also, we are currently working on
a code review for code pointer separation
code Poinar Integrity.
There's still a couple
of bucks to be fixed.
The you can still play with the
prototype.
It's out there, download
it, try it out.
We will definitely release more packages
and an
updated source is soon.
And also
we worked.
Or we obviously.
Run into some problems this fall and
there are some changes to super complex
built systems, like, for example, when we
worked on on FreeBSD, we had
to adapt some of the larger make
files to actually get it to run.
So you might ask, is it practical?
So we compiled
the complete FreeBSD userspace
with our protection.
And in addition to that, more than one
hundred protection was one more
than 100 packages with strong protection
guarantees.
Where we can now guarantee that
contraflow hijack attacks are no longer
possible at fairly
low overheads that you can
you will run into.
So now it's up to you to do your
part, look at the code,
try to put some of the more complex
make files, try to find bugs
in our implementation,
help us get get
the word out there, gets
distributions to actually use these
patches, lets the
attacker accessible software be compiled
using a very.
Deterministic and strong defense
mechanism.
And if we compel our
software that is
reachable for codas, reachable from the
Internet, using the strong defense
mechanisms, we can stop control for high
tech attacks.
So let me conclude,
coach, point to integrity and point of
separation, offer strong protection
against contraflow, tech attacks
and the key insight that we had here
is that we offer memory safety
for coach pointers only, which allows
us to limit the overhead that
we actually secure, bringing down the
overhead from almost one hundred and
twenty percent to
less than than two to eight
percent on average for
depending on if you either
use code or separation or code point or
integrity, which is easily deployable
in practice and can be used on
a very wide scale without impacting
the runtime performance of current
software.
We do have a working prototype which
supports unmodified C
and C++ at very, very
low overhead. In practice, ops
dreaming of our patches is in
progress.
The safe stack should be available soon.
You can like you're going to Fourcade on
GitHub, you can read
the paper on our homepage
or you'll be happy to hear
from you.
If you if you have questions, if you find
bugs, if you want to audit the code or
help any in any way.
And if you continue like that,
we'll go on and
they'll be able to understand the bugs.
And then in the end, we can get rid
of them and protect against them.
And I said, I would like to close my talk
and I'm happy to answer any questions.
Thank you.
Thank you very much, Matteus player.
As you heard, he is open to questions
now. We have four microphones in the
room. There's microphone one,
microphone two, three,
and number four.
You can go up on any of them.
I just picked them.
Well, one after another.
And I will start with microphone two,
please.
Thank you for a fascinating
talk on the
approach here.
I just got one question you discussed
extensively the overhead
in one time.
What's the overhead in memory footprint?
Because normally the page
page size will be the limit for your
hardware protection.
So the overhead is not
too bad.
Um, naively, you
would just shadow the memory space,
right?
What we do, we implement we've
got several implementations where
we stored
the code pointers and the meta data.
You can use a hash map, a compacted hash
map, some form of error or
some other data structure.
It is just protected and moved, moved
to the side.
I'm not 100 percent sure if you put
numbers into into the paper,
but they are
in the low low digits.
So there's not too much memory overhead
because a four one, there are only
very few code pointers.
So we don't need to store an excessive
amount of data.
Also, we can we can
store them in very compact
representations using some form
of hash map or something like that.
That gives us the illusion of the full
memory space.
OK.
All right. Thank you.
Microphone one, please.
Yes.
And your motivating example,
the biggest chunk of potentially
vulnerable C-code
was the Linux kernel.
And I was wondering
whether this kind of protection is
practical in kind of space
and with full
permissions and hardware.
That's a very good question.
It's also a very hard question to answer
so far to see code,
we'd be perfectly fine.
Unfortunately, the Linux kernel
contains a whole bunch of
assembly code and inline assembly code,
which is very, very hard to protect.
So we cannot give any guarantees for
inline assembly that is in the in a
source code.
We could imagine some form
of annotation based
system for the programmer
has to identify assembly
or inline assembly sequences that modify
code pointers.
And if so, we could give the same
guarantees, but we would do that.
So our instrumentation pass
runs on top of
LVM does not have any type information
for the inline assembly code that you put
into the, uh, into the code.
So if you as a programmer would supply
additional annotations so that
we could reason about the inline assembly
code, we would be fine, but that would be
definitely ongoing work.
Thank you.
We have a question from the Internet.
Yes, things are still an
open question, the chat room.
The question is, how are the address
spaces separated the pointers
from the code?
Yeah, that's a good question.
On 86, we use segmentation,
a segmentation register that we set up
so we can easily enforce
we can easily use hardware, enforce the
separation on x
64.
We can we can use a set of different
techniques depending on how much overhead
you're willing to pay.
You can use Asla
so you can use a randomization based
approach, but just
use or allocate your
safe reaching somewhere in memory that
is safe from the attacker, because in
a in a 64 bit address space, the address
space is big enough so that you can you
can hide it. And in addition to that, we
guarantee that in unsafe memory
or in attacker accessible memory, there
will never be a pointer to
our, uh, our safe
memory.
Therefore, we are safe against, um,
information leaks.
Or if you are willing to pay two or
three percent overhead, you can blind
blind out a memory reachin.
So imagine it that for every
memory access that you execute
or memory, every memory reader, every
memory. Right.
That goes to unsafe memory, you
do an end to the,
uh, to the actual address that
is used and therefore blind out
there with a mask, blind outside the bits
that are to protect that memory,
which cost you like two or three percent
overhead.
All right. Microphone to please.
At the beginning of the talk, you
mentioned the Heartbleed buck.
And as far as I could tell,
none of your security measures would help
against it.
Yeah.
Um,
so the Heartbleed bug is
a piece of information that
it's basically a data leak.
Right. So you could
protect against the Heartbleed bug by
extending the protection that we
currently have to other data types
as well.
So you've seen that we basically
run the typist analysis and
the identity. We currently identify
everything that's like a code pointer or
used like a code pointer or
anywhere in the chain.
Been a code point, Christie reference.
But there's nothing that stops us from
adding additional data and increase
the protection for that additional data.
So instead of just protecting code
pointers, we can select other
data as well.
And sensitive data
types like private keys
would be very good candidates for
additional scrutiny, scrutiny
and inclusion into the set
of sensitive pointers.
So that's definitely ongoing future work.
All right.
All right, you've mentioned that
you've evaluated the performance
with the benchmark, have you also
evaluated the functionality?
You've mentioned that you've compiled the
previous to the whole freebees, the SAT
and a couple of hundred packages.
But have you also run them successfully
at all?
Yeah. So Specs CPU is
a self validating benchmark that checks
if the, uh, if
the code runs correctly and verifies
that it runs correctly and so does for.
So we did run the four Onyx benchmarks on
top of FreeBSD, which is a big
package of of benchmarks
that sell certified results and ensure
that they are correct.
And then and then you've mentioned
that you have a runtime support.
What does that entail and how portable
is?
So we do need runtime support to set
up all the data structures we
need to runtime support, to set up the
safe memory and so on, which is basically
like, do you know, compiler art
in LVM?
It's like a library that is linked into
any or included into
any executable that is compiled.
It's like a turkey has
its own library and so on.
It just contains a set of start functions
and so on. That set up the process image
you it when you execute a program,
it doesn't start at Main.
There's a whole bunch of other plot that
is executed beforehand and the adds
to that stuff that is executed
beforehand. It's a standard compiler
technique to include
some initialization functions and stuff
like that.
All right, thanks. Thanks.
All right, there's another question from
the Internet.
Yes, thank you.
Another question is, what about
applications for the runtime code,
e.g.
browsers? Can you protect these?
Yeah, so that's a good question
at the beginning and the assumptions
I put in that
there's no self
modifying code, basically, and
browsers use
a lot basically JIT compilers and
therefore recompile code all the time.
There are two answers to this question.
One of them is if you lift
the compiler, the Just-In-Time compiler
itself into into the trusted computing
base and you instrument and give
the compiler access to
all the support functions, the compiler
can produce safe code as well.
But the drawback, obviously, is
that the compiler itself then is inside
the trusted computing base in
during the time the process or the
program is executed and might be
attackable if there are bugs in a
complaint. Obviously, the other option
or the other answer is that most of the
time, even
if we don't give any strong guarantees,
the compiler, the testing
time compilers that we use in browsers
compile as safe a memory, safe language.
And inside this memory, safe language
the attacker doesn't have access to
are to
code pointers directly to
the it should it should
be safe in most cases.
But obviously, it's not a perfect answer.
That's why we included it.
And that's why the excluded
compilers or just in time
generate a code from the attack model.
You can come up with some defense
mechanism, but you'll have to verify the
compiler. That's the short answer.
All right. Thank you.
There's still three questions left at
microphone two, I guess.
So you said that
this doesn't work in all cases.
So it goes in almost every case,
but it doesn't work for some cases.
You already explained that this wasn't
work for inline assembly.
Stuff like that.
What what are the other cases?
This doesn't work.
Um, well,
not necessarily not work, but,
um, for code pointer
integrity, there are some
sometimes very Bizerte costs like
C-code. If you if you look
into what is actually compiled and
written out there, then C-code becomes
very, very ugly.
And according
to the C language
specification, you're allowed to cast
everything into void or CA.
And if you have a bunch of these costs
from Code Pinder's to void and back and
forth and all that, you end up protecting
all the pointers and then
you end up with a fairly high amount of
overhead.
So it actually happens a lot
that some pointers
are cast into into a char pointer and
then back into a struct pointer that then
contain a code pointer.
And if that happens, you might end up
protecting all the pointers,
which then results in fairly high
overhead.
And so you do have high overhead
as a as a drawback, but if you don't
support if something bad happens from
the hardware.
So if you would protect the VMS or
if you if you have access to tables,
let's say in the kernel, if the attacker,
we don't protect data memory.
So if the if the attacker
writes in to the table or something like
that, you could come up with very weird
stuff. But in userspace
you should be safe.
There so it looks like from the top that
all the work here is being done in
software.
I was wondering, do you see any
opportunities for hardware acceleration
and, say, the CPU to reduce some
of the overhead?
You're talking about
NPCs or anything else you can come up
with?
Yeah, we actually looked at
it's it's a bit of a bummer that it's not
available in real hardware yet.
But then again, Intel kind
of advertises as a
debugging feature.
There's some hints that up to that it'll
have up to 40 percent overhead.
So we'll definitely look into it as
soon as it is out there in real hardware.
What we could really profit from is
some faster implementations for
the, uh,
for the for the additional tables that we
have, like for the for the bounce
information and so on.
And Intel try to address that using using
NPLEX something
following this line of work
could be interesting or something.
Some other line look up that you can, you
can speed up using some additional
instructions. Might be interesting as
well.
Thank you. Next question.
Hi.
I wanted to ask, what is the
interaction between this and
dynamic linking, for example, what
happens or can you link
unsafe plugins
that are incompatible with this blob's
into your code and have
guarantees of safety or the other way
around, have a
yes user program that's not
compiled with this loading a
safe library.
So let's start with
the safe stack.
First, let's assume
we only use the safe stack
if you branch into unprotected
code, which is don't give any
guarantees while you're executing
unprotected code.
As soon as you return to protect the
code, it continues.
So unprotected code is perfectly
supported for a safe stack.
We don't give any guarantees while you're
executing the unprotected
code for code point
or separation or code point or integrity,
it looks a bit different.
As long as you don't modify any
of the sensitive pointers,
you're fine.
If you modify the sensitive pointers,
then
some of the pointers could be out of out
of line.
So are the light like
you would miss some of the updates
because the shadow location
would be written that is not actually
used in regular memory.
And you would miss the update.
So we did.
There are no safety guarantees and
you could miss some of the updates.
But the unsafe code,
if you segmentation, for example,
wouldn't be able to modify the code
pointers even when executing on safe
code.
So which way around
is like safer, like loading,
loading now,
unsafe coding code, loading your
safe library or the other way around?
Well, it just depends, right.
While you're executing unsafe code, there
are no guarantees.
That's basically how it looked like
things you could you could run some
some form of binary instrumentation
on top of it to kind of,
ah, pay a very high overhead
for for it unsafe code.
But it's not what you what you would
want.
All right. We have three questions and
five minutes left on four minutes.
Go ahead.
OK, not exactly a question,
just a comment.
Casting function pointers to avoid
pointers is not allowed.
I don't have the C standard memorized,
but I'm pretty sure of that.
And I.C.C.
an Excel C both one for that.
But DC does not want whatever reason.
OK, I don't know about good
casting to Cha and back is allowed.
Yeah, POSIX requires it like
dorsum if you get a
white pointer.
All right, thanks. Microphone two.
And then after that the internet
was developed a few years
ago.
So why Segan if
the fee was developed
a few center know
a few decades ago.
So why has it taken
so long to find a
simpler solution?
Well, the solution is not simple, right?
It's fairly complex.
If you run a whole bunch of
type based analysis and
all these type based analysis have only
come up to speed in the last couple of
years.
And you've seen, like I talked about,
cured, which which was proposed
in the early two thousand, I think like
2002 or 2003 or so
and. There's been a lot of research going
on, and only now we have these frameworks
available that we can actually do, these
heavy transformations are
and then run additional optimization
policies on top of it to get the overhead
low enough so that it could actually be
usable in practice.
Also, people assumed that C programmers
would write safe code, but apparently
it's not like that.
All right. One last question from the
Internet.
I think you have to read it out.
Actually, you have no clue what it's
about in the context of C++,
wouldn't all the pointers to instances of
classes containing virtual methods
be protected and by extension,
all classes containing pointers
to those classes as members?
Should I read it again
in the context of C++?
Wouldn't all the pointers to instances
of classes containing
virtual methods be protected and by
extension, all classes
containing pointers to those classes
as members?
Yeah.
So the answer is yes, the answer is yes.
All right, thank you very much.
My pleasure.
If there is no more further questions.
All right. One last one, really quick
microphone three.
I don't know, is it
on?
OK, expanding the last question.
What's the profit on that?
What's the what? The profit performance
on which one?
The last question when you get
because that sounds expensive to me,
it actually depends.
We only look at the pointers and
luckily a lot of the
stuff is pushed onto the stack.
And so we don't pay anything on the
stack. We do protect.
We do get a performance hit on some of
the the stuff on a heap,
and especially for for C++.
As I said, we might end up
protecting a whole bunch of the
of the
pointers as to
objects with virtual functions.
Depends on the program,
how frequent these these operations are,
there they are, there's a full list of
of individual benchmarks in the paper.
Um, the
most of the benchmarks are
below five percent.
Some of them are around 10, very few
around 20.
And the highest overhead we've seen was
roughly 80 percent.
Thank you.
So there.
And four to one with 80 percent.
There's definitely room for future
optimization where you can you can kind
of at
or reduce the number of
of checks by by streamlining
and grouping and reducing the total
total amount of it.
Thank you.
All right.
Thank you very much. Again,
that's what's going on, Taggerty.