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/942 Thanks!
So C, C++
programmers kind of use typecasting, like
we just go for a for a beer at a bar
and they don't really seem to enforce,
you know, the types very, very,
you know, carefully at least a compilers
don't. And memory access is also a
bit of a problem.
This leads to all sorts of issues
like this, specifically
type confusion and other vulnerabilities.
Matteus Problem has come
to us all the way from Purdue University,
the other places, but just recently from
Purdue University, to
tell us all about a compiler base
extension that he's been working on that
should be able to detect and invert some
of these bugs. And with that, it's
over to Matyas.
Pretty introduction.
And different forms of type safety
vulnerabilities that can be exploited
by our attackers
to gain some form of code execution,
and we've worked for quite a bit on
looking at the type hierarchy
of C++ and different forms of
vulnerabilities that can arise that can
then be exploited in different forms.
And it's as if
just just quick show of hands.
Who here is a C++ programmer?
Wow, pretty much every one of you who has
written more than, let's say, 10000 lines
of C++ code.
A lot of you. OK, so that's a good
setting and a C++ programmers,
you are likely used to the different
forms of typecasting that you have.
You have because you have dynamic costs.
And if a bunch of other different costs
and amazingly, C++ has no
form of type safety whatsoever.
So this talk could also be
described as cloudy with a chance
of calculators, as we'll see
in a bit.
There's a lot of opportunity that an
attacker can get to exploit these
different forms of, uh,
of type confusion and
type confusion leads to remote code
execution, which an adversary
can abuse the different forms of type
confusion and the different settings in a
type hierarchy to pretty much execute
arbitrary code on your on your system.
And, um, especially
browsers and hypervisor
and colonels are great targets to
find different forms of type confusion
and then exploit them, as was recently
shown on the, uh,
point to own competition.
And we've seen a couple of these, um,
type confusions that are used to
spawn calculators all over the place,
which are fun.
So Calculator pretty much shows you that
you get arbitrary code execution if you
can spawn that.
And as we see if you think about
it, um, the attack surface
that we face on our systems is pretty
much huge.
We are no longer working with
our systems of a couple of thousand lines
of code, but with millions and millions
of lines of code, the abstraction is
immense. And if you if you look at Google
Chrome, for example, um, we have
more than one hundred million lines of
code, which is an immense
code base that we are facing.
And it's very hard to protect against
vulnerabilities in his large code base.
And even though the, uh,
folks at Google are doing an awesome job
at code reviews, figuring out, uh,
test test cases, checking all the
different conditions, there are still a
large opportunity for different forms of
type confusion in this large
source space. And it's not just Google
Chrome, the 76 million lines of code,
but also a bunch of other systems.
On top of that, there's your,
uh, window managers, there's
your, uh, standard library,
there's the Linux kernel, there's a
hypervisor and so on.
And there's easily clocks into more than
one hundred million lines of code.
And there's easy easily
there's a lot of opportunities for
different forms of type confusion.
Um, we will explore
these different opportunities of type
confusion. We will see how we can find
time, confusion, vulnerabilities, how
we can automate the search for type
confusional abilities, and also will
discuss what kind of
capabilities an adversary can get through
different forms of type query.
So how can you exploited?
What is the underlying attack vector?
What are the attack primitives?
How can you build attack primitives?
And then in the end, how can how you can
automated to figure out, uh,
exploit the ability.
Now the attacker model is as follows.
We start off with, uh, an
external user, for example, without any
form of code execution capabilities.
Um, you have to imagine is, uh, on on
one end, you have, uh, a program that is
answering to different forms of requests.
You're sending in a request.
You're getting a reply.
This defines a some form of
computational capabilities that you
get on the other end.
Right. So you can send in a request, you
get a reply, and it
conforms to some form of, uh,
computation that you are allowed to
execute your you are
severely limited in what kind of
computation you can execute.
So, for example, if you're interacting
with a Web server, you're sending in a
request and you're supposed to get an
HMO file as a response that you can
then render and and look
at. And an adversary tries to craft
a request that is being sent in so that
instead of an HTML,
uh, document, you get a shell in
return. And among several steps,
you are extending your capabilities
from an external user that is issuing
requests to to retrieve
documents to a local user and
then to an administrator account.
And these.
These steps are being followed to extend
the capabilities of an adversary step by
step, and
the nice thing is that an
external adversary can easily trigger
these these attacks by,
uh, through very simple means by,
uh, a simple, uh, simple request
that is being sent on on current
software.
We're mostly focusing on contraflow, high
tech attacks and as,
uh, software security community, over
the last 20 plus years, we
have worked on a large amount of
mitigations, different forms of
mitigations that try to detect
and exploit condition or some form of
vulnerability that is being used.
So given the attack
first that I've shown before, it is
highly likely that the large
amount of code that we have will contain
vulnerabilities.
So we are working on mechanisms
that protect the integrity and
availability of our systems, even in the
presence of vulnerabilities.
So it's a very first step.
We have to accept the fact that there
will be bugs in our code.
So all the defenses that we have, all the
mitigations that we have focused on
detecting this exploit condition and
stopping an adversary from actually
running a full exploit.
And over the last 20 plus years,
we've developed a set of different
mitigations and defenses that make it
harder and harder for adversaries
to gain full code execution capabilities.
So if you want to hijack the
contraflow on a current system, you need
to jump through a set of hoops to
actually get code execution, which
allows you to spawn a calculator or
execute arbitrary order commands.
So for a contraflow hatcheck attack,
uh, what an adversary does is it
influences the address
space of an application
of a process to readjust
the different forms of, um,
of code pointers, pointers and the data
of the application.
So that executes something different.
So you mentioned originally the code is
just the Web server, but instead
of serving a Web document, you wanted
to open a shell for you.
That gives you full computational
capabilities on that system so that you
can interact with the system and then
further escalate your privileges to an
administrator.
Now, with all the different mitigations
that we have in our systems, we've
severely restricted the set of
capabilities that an adversary can
have, even if there are vulnerabilities,
memory safety vulnerabilities or type
safety vulnerabilities in the code.
And the slide here shows
the outer space of the program in an
abstract form.
So we see that the code section
is read only and
executed only.
So an
adversary and this is the only
section that is readable and executable
so an adversary can no longer inject new
code. And this is one of the defenses we
came up with.
In addition to that, there's, uh, there's
a heap and the stack which are readable
and readable, but not executable.
So the only way an adversary can
influence the program is by modifying
the data and then reusing the existing
code.
So what we have here is we
have a large amount of code pointers
on the heap and on a stack, which then
points to the code which to get
code execution or to hijack
the contraflow, an adversary can simply
override these code pointers and redirect
them to some alternate location.
And this is precisely what is being done
for contraflow, hatcheck attack.
And this can either be for direct code
pointers that point from the heap or to
stack into the code, which in itself or
we can also go through the tables, which
is a way for C++
to handle, um,
inheritance and virtual functions.
So if you have a virtual function, you
are, uh, allowing it to
be overwritten depending on the class
hierarchy. And if you are dispatching
based on a virtual function, you're
following the code point, a code
pointer to a specific class
based implementation.
And we can influence all these different
pointers as an attacker and then redirect
and stitch together the existing code
parts and alternate ways.
As the show of hands showed before,
all of you are already C++
programmers but are still quickly goes
through the different forms of casting
behavior. And even
if you're if you've been using C++ for a
while, you may not be aware of how
the different casting operators
actually boil down to the underlying
code, how they are being compiled down
into an actual application.
So there's two main casting operations
that we have to study costs and their
dynamic costs.
A static cost allows you to
cost the an object
or a pointer to an object into a
different cost.
The advantages a static
cost is very, very fast.
The disadvantage is it doesn't do
anything except for doing a feasibility
check at compile time.
So what a static cost actually is.
It's it tells the compiler, please
check if there is a pass from
the current type to the other
type to the target type.
And if there is any pause in the class
hierarchy that, uh,
that goes from the source type to the
target type, then the cost is actually
allowed. And, uh,
it doesn't need any runtime information.
It doesn't introduce any overhead,
uh, which is which is great
for, uh, performance,
but it doesn't give you any security
guarantees.
A dynamic cost, on the other hand,
executes an actual runtime check.
So the cost is somewhat comparable to
a cost in another, uh, programing
language, like, for example, in Java or
so on, when you're doing a type cost,
it is actually enforced that the type
that you're costing into the object,
that you're costing a different type
actually is of that order type so
that the cost is allowed.
And in C++, the dynamic cost
leads to a runtime check.
Now, to actually execute a runtime
check, you need the runtime type
information to be able to decide, hey,
what is the actual type of the
object? What is the type of the memory
area that we are looking at?
You need to identify the underlying
type of the memory object to in some
form, and this
is where we see some of the drawbacks
of C++.
C++ is an extension
of C pretty much an in C,
everything boils down to an typed memory.
Everything boils down to bytes in memory.
Might you have some, Khaira that
can be interpreted in different ways
and without an actual identification
of the underlying Carrere, you don't know
what the type is and what the dynamic
cost. You are using a unique identifier
for an object to actually decide
what type it is.
And this is where the V
table pointer is actually being used.
This allows you to have a unique
identifier for the actual object
that allows you to decide, hey, this is
the runtime type of this object.
So it's a unique way to identify
different or to distinguish between
a different object types.
So let's look at the different casting,
uh, behavior and a little bit more
detail.
So if you have a static cast,
we cast an object B
into, uh, pointer,
Greta.
And this is being compiled down into
a lot of the pointer,
B, into
the Aurora X register and
then a store into this other
target area.
So there's no real type
check happening there.
And the compiler only does
a feasibility check at compile time.
And it goes from, uh, from B to A
to make sure that is of the right type.
Now, if you have a dynamic cast, if you
compile it was O0 without optimization,
we see that there's actually a lot of
code being generated.
Again, we load the pointer, we do not
check.
And in addition to that, we load the
pointer to the greater
class and we load the pointer to the base
class and then we execute a full dynamic
because this allows us to do this this
actual check and make sure that the
type of the runtime object that we have
conforms to, the actual type that we
expect.
So we we do a full runtime
enforcement check. Now, if we optimize
this, um, we
do a dynamic cost.
We load the two pointers, the two based
pointers. We check what the the current
base pointer is of the current object
and depending on the
result of the cost of to allow it or we
terminate the program at runtime as a
type safety violation.
Let's look what a static cost is
optimized.
It ends up in zero instructions
because we refused to register,
so esthetic cost does not ensure
any runtime overhead and does not in
any runtime check uses
as a as a take home message.
Static costs do not
result in any instructions being
executed at runtime.
So no performance
overhead.
And, uh.
No security guarantees,
so now with this knowledge,
what actually is type, confusion,
type confusion arises through illegal
down costs.
Assume you have the following type
hierarchy. We have a parent
class and two dependent classes,
child one and child two.
Now, if we allocate an object
or child of type, child
one and we store it and in the three
pointer we can cast it to
a parent type.
Right, so we can cast from the child
object child one object to parent object.
And as these two classes are dependent,
this is a valid cost and we
can store the pointer to P uh,
in, uh.
In the in the pointer, and we can
use the parent objects or the fields of
the parent object now
as a second step, we can consider
a parent object into a child object.
And if the child to object or
if the underlying object has been
allocated as a child to object, then this
cost would be allowed.
But the steady course does not
do any checks, right.
So at runtime, this would lead to
to type confusion.
And this is exactly where the exploitable
behavior comes in.
So with this static cost,
which is not being checked, the
static cost could be abused
to reinterpret the underlying
memory as a different type.
Let me get you a little bit
more detail and background on that.
On the type confusion we have to parent
class and we have the child and I'll
break it down to just parent and child
to make it a little bit easier.
Now to parent object only has a single
variable, uh, inside it
called, uh, often
type. And the child class has a second
type and a virtual function called
print.
Now if we allocate a P object,
we only allocate the four point four
bytes are being used for the integer.
If we allocate a C type object,
we have the V table pointer that points
to the actual, um.
Actual location, it contains all the
code pointers, we have the X integer
and we have to Y integer that can all be
used.
Now, let's assume we allocate, uh,
uh, a object, the parent object, and we
have a pointer to it, if you do
a static cast into a C,
uh, C pointer,
the C pointer ends up pointing above
the actual object.
And, um, the underlying
object or the underlying data that is at
that location would be reinterpreted
as a V table pointer along
with the Y object or Y
integer that could then be read
and written, which would expose the
underlying memory.
So this leads to a memory safety
violation and contraflow, high checking
after type confusion.
And if you look at the, uh, at the chain
of violations, the type
Confucianist, the first thing that
happens that violates the integrity
of the underlying application.
And this is the initial
entry vector for an attacker to
abuse this underlying block,
this type confusion block.
And this can then be used as a memory
safety confusion or memory safety
violation or then for contraflow,
high checking.
Now, how do we use this
vulnerability type to build
and exploit primitive.
So imagine that,
um, when you're using type confusional,
when you're exploiting type confusion
in in your programs, you're
trying to control two pointers
of different type that both
points to the same memory area.
But the two pointers of different type
allow you to in reinterprets
the different fields of the object in two
different ways.
So you have you have a certain memory
area that is of of one
original type or has been allocated as
one original type.
But you have two pointers of different
types. That's a memory area.
And for example, and in the first type,
the parameter is
the first entry is interpreted as a
visible pointer, while in the second type
it is interpreted as a long right.
And if you if you use a setter for
it is long value, you can use it
to override the veto pointer
in the interview.
So you mentioned that you are using the
first view to set the
table pointer and then you're using the
second pointer that you control as well
of different type to dispatch on
that pointer.
As a simple example, um,
just to show you the the power
of this exploit, primitive, uh, I
can imagine that we have a base class
that just implements some basic
functionality and we have two subclasses
of it to dissent in classes.
We have a greater class that just says
hello and we have a
great executer as a service.
So both of those are implemented
as virtual function because we may want
to build our fancy framework on top
of that with additional functionality.
So we want to be able to override these
functionalities.
So the executer service implements
one virtual function called exec that
takes a string that is then being passed
to system to execute it as a, uh,
as an additional service.
And the greater function just prints
the string to standard out.
So that sounds pretty reasonable, right?
There's no way that an, uh, a programmer
would confuse exec and say high
because the functions have different
names and there's no way for us to
confuse it. Right.
Now.
If we allocate,
uh, to base objects, be
one and be two of type,
the first one of type creature and the
second type, uh, the second object of
type, we can actually
dispatch, uh.
Dispatch on those objects,
so we allocate these two objects,
one object of type Grétar, the second
object of type exec, and then
we cast the first object BE1
into Grétar and we call Graeter's say
high and then Graeter's says high.
And then this was the second object
we again casted into, Greta.
So from the base class to Grétar class
and the compiler does a runtime,
a compile time check and says, oh yes,
the, uh, the Grétar.
Type is dependent or a descendant
of the beast type synesthetic cost is
actually allowed, and then we can call
hi with this weird string user
being x click and it works perfectly
fine. The compiler doesn't complain.
This is actually really fun.
If you look into this.
We see this is exactly the code that
I've just shown.
We've got the the study cost
into Grieder here and we've got a study
cost Grieder here and we call
CIHI twice,
so we make.
Object the allocate
to objects of type, base
of type, creature and type exec,
but we both recall the same
high method two times.
And if you executed.
We once the first call to say
hi, this Grieder says hi and the second
call to say hi opens
a calculator, which
is not what we want.
Um, if you look at how this is actually
implemented, so why does this
happen? First off, the initial
bar or the underlying bug, is that the
type hierarchy or the compiler
cannot, uh,
stop us from casting a base class
into a greater class, even though it is
an exact class.
We've got these two tables from
B1 and B2 and the first V table
points to the V table of the of the
Grieder type and the second, uh,
base V two
pointer points to the exact type,
um, and we
can easily cost between the two of you
without the type system in C plus
actually complaining against it.
And if you look at it at the actual
implementation, if you drill down in the
source code, what it actually ends up
with as we dereference the first field
of the, uh, of the greater
class, which is the V table pointer,
and then we dereference the
first V table pointer.
So even though we are executing C high
or we have written C high in the source
code, it boils down to executing
the exec
function in the,
um, in the exec
class instead of the Grieder class,
leading to the actual type confusion.
Now, this is a fun, uh, fun way
to exploit software. Now the hard
question is, how do we find these types
of vulnerabilities?
How can we find such issues
in our software?
And the classic approach that people have
been using for
the last couple of years, especially to
find vulnerabilities in
large browsers, has been fussing
and fussing is great.
Right?
But what it ended up
being is your fuzzing
and you're trying to find these type
Confucian vulnerabilities.
But as I've just shown, it's really hard
to find or to actually trigger
the type confusion vulnerabilities
because there's no way for you to,
um, enforce the
actual check.
Right. So the only way that you will
discover that something is amiss is,
is if you run into a memory
corruption, if you run into segmentation
fault, if you don't run into segmentation
fault, there is no way for you to detect
the actual type confusion.
And you may be missing a large amount
of time. Confucians right to
your only. If you're running a father,
you're only detecting the subset of type
confusion that results in a direct memory
corruption, the segmentation fault.
There may be a large amount of type
confusion that could be abused that
you're missing.
And what we wanted to look at is can we
discover the missing set of type
confusion?
So can we bring type safety
to C++ or at least some
form of type system and typing so
that we can be aware of when an illegal
cost is being happening?
Right. So the underlying
problem that we have here is in C++,
a static cost is checked only
at compile time, which is fast,
but does not give us any form of runtime
guarantees.
On the other hand, we have dynamic costs
that are checks at runtime which result
in high overhead and are limited to
polymorphic classes.
Polymorphic classes are classes that have
virtual functions in them.
Why are dynamic costs limited to
polymorph classes?
Well, we need to have some a
way to identify individual
objects or the type of an individual
objects. And the visible pointer is such
an identifying field.
And this goes back to the design of
C++.
And in C++, a struct
is a class and a class is a struct.
And if you allocate a structure and C,
you have no idea what the underlying
type is right there.
There's no way that she remembers that
you are allocated a few struct.
It could be any arbitrary type only if
you have an identifying field, a type
idea, only then you can actually
identify the underlying type.
Um, safe.
Object oriented languages like Java,
C, Sharp and so on, whenever
you allocate an object, they have an
object ID, an object type idea
that clearly identifies the underlying
type. This is missing in C++.
This is why we cannot explicitly check
all the costs between any objects, but
only for polymorphic objects as virtual
classes.
So what we figure is this, is
there something missing here?
We need to be able to do
an actual type check for
any of these objects.
So according to the model
of the S4 3C,
we figured we were to bat and
bring TIFE safety to C++.
And under our underlying idea
is that.
We would check every single typecasts,
so we do a dynamic check for every single
type cast and then aggressively
remove as many casts that
we can as part of our design
and as part of our implementation.
So we are making type checks explicit.
So we enforce an explicit runtime
check at all, at all cost
sites for dynamic costs,
static costs, reinterpret costs
and also see costing.
This sounds like a contradiction, right?
I've just told you that this is not
possible for in the existing
framework that C++ has
because we have no way to identify
the underlying type of an object.
How do we solve this problem?
Whenever you allocate an object,
whenever you execute it constructors,
or if you simply go through the
allocator?
We remember that this memory
area over here is of that particular
type and we keep some form of of metadata
table somewhere in the background that
allows us to query and look up for
any byte in memory.
What type does this this
piece of memory have?
And we can then use this information
in any of the costs so we can replace
a static cost with an actual runtime
check and make sure that we detect
a type confusion problem right when it
happens right at the site and
not much later than an actual
memory corruption happens.
So we build a global type hierarchy
during the compilation of the software
and we keep track of the allocation
type of each object to the instrument,
all forms of allocation, and
we keep this in our disjoined metadata
table. And then in a second
step, we can execute for every single
type cast that happens at one time.
We can execute this this check and make
sure that it actually matches.
So we've built this large system, uh,
based on LVM,
where the instrument
source code on top of Klang with
additional explicit type checks
during the compilation.
We do object tracing, uh, as
part of additional 11 percent and
track the type hierarchy.
And then at runtime you can check if
something fails or not.
And then we have a a hardened binary that
does all the explicit type checks.
Compared to some prior work, you may know
you, BISAN, which does the checking for
polymorphic types only, this allows
us to check every single
typecasts that is out there for static,
uh, for static costs and for the net
cost to do this, this fine-grained
checking.
We cover new object allocations, we
cover placement new we cover
reinterpret cost and a bunch of other
things. Right. So we we've worked
very hard to compile real software,
including Chrome, Firefox and
other systems.
Now, the problem is, as soon
as you enforce full type
checks for every single cost, you
run into impressive overheads.
I understood our main task was to
reduce the overhead to make it more
useful.
So on one hand, we limit tracing to
unsafe objects if an object is
only used in a safe context.
So, for example, if it is only if it is
never being used for costing, we don't
need to instrument. If you don't need to
remember the type of the underlying
object. If an object is never
used in cost and we don't need to worry
about it. Right.
So we can remove tracing for types that
are never cast in the program.
We limit checking to unsafe cost.
So we do some static
verification inside the
scope of a function to figure out what
parts of the code are actually
used in a safe way.
And this also allows us to remove
some of the, uh, of the costs.
Um, we also replace all the dynamic
costs with our special form of costing.
As it turns out, our cost
that we have developed using our metadata
information is much faster than
any cost done through the
RTI information that
the original C++ dynamic cost
us. Um, as it turns out that Emmi
cost has never been optimized.
People don't really use dynamic costs due
to the performance overhead.
Therefore it's not been optimized,
therefore it's not being used.
It's is this endless circle.
If you replace all the time it costs with
our type, uh, type costs, we can
actually improve the performance a little
bit.
Interestingly, by just doing this based
system, we already found four new
vulnerabilities in Apache Circus's,
which is a large XML processing
library.
Um, in
there very there were costs
from a dorm text implementation node
to DOM Element implementation node,
which allowed us to reinterpret these
these types in different ways.
And we've also found type confusion
in the Kutty based library
going from the node base to the to the
mapped, uh, map node itself.
And those were easy, low hanging
fruit that we found by simply compiling
software and running it in a day to day
use. So by simply
compiling your C++ software with
our type, uh, type checker,
you can already find vulnerabilities
and bugs in the software by just
running them in your day to day settings.
This was step one and we found
a bunch of, uh, of different
vulnerabilities, but we wanted to go
further.
So a couple of weeks before to Congress,
we started to fuzz all the things.
As it turns out, um,
you can combine our.
Type safety mechanism,
whereas AFL, so you can compile
the any arbitrary C++ software
with our hex type album
based instrumentation and you then run
the software on top of LVM and you
fuzzed to find different forms
of type confusion.
You simply let AFL do its magic
and you have to invest some time into
triaging all the type confusion reports
and you'll figure out different forms of
vulnerabilities.
Um, and at this point in time, I would
like to, um, give
a huge shout out to the students that
actually did all the work and invested
a lot of time into developing these
systems and, um,
triaging the vulnerabilities, building
to the system
and playing with it for for such
a time.
So we spent some time fuzzing on
our Geto fuzzing cluster
that we have under the
desk of one of the students.
So you see, this is a very low power
power setting. We only have five machines
that, um, that were running
different pieces of software.
But nevertheless, we found quite
a couple of of interesting cases.
After two weeks of fuzzing, uh,
we found two new type confusion,
bugs in Kutty core, uh, unfortunately
not exploitable, but, uh,
they've already been fixed and
acknowledged by the developers.
We found one more bug in success
and we found, um, seven
issues or reports and Lipsius,
where we're still looking if
they are exploitable or not.
Um.
As it turns out.
Pretty much every software you throw at
it will generate a couple of reports
and there's.
Part of some of these reports
are due to the
underlying problems with C++
as there's no explicit type
information, um.
Developers are abusing the type system
in many odd ways, which
leads to some spurious reports.
So actually triaging
and figuring out if it is an actual bug
or not adds additional overacted.
So you have to spend some, uh,
some time to look into it.
Um, as, for example, this Lipsius.
Now, we focused most of our time
on small software to test the scalability
of our approach and to find some some
reasonable bugs, but we also looked at
Firefox for a while, also to test
our, uh, the performance overhead,
for example.
So these are the
results for Firefox that we currently
have. And
they are fairly impressive.
Right. So based on a specific
set of, uh, of benchmarks
we found.
Let's just say some type confusion
reports, and we are
still figuring out on how we can
handle these large amount of
time confusion reports, many of them will
be duplicates and even more
of them will be false positives.
And we are working hard on triaging and
trying to reduce them to a smaller
set of of actual
bugs that we can then report to the
Firefox Firefox people.
The big problem that we are facing for
Firefox and also for Chrome is
but much more so for Firefox, that
the code is really, really messy.
A heart problem that we have here is
that, uh, Firefox has several
allocators.
So there's different forms, are
different locations in the code that
handle different parts of the heap's.
There are different types.
Did that move data back and
forth that share data?
And there's very odd allocators
that are missing with different parts.
So there is not
a seven billion type confusion,
bugs and Firefox or at least we hope so.
We would guess that the number will be
much lower.
But it's a it's a first step.
And we are working on, uh, on reducing
them. So Firefox is ongoing
work and we'll see how we can,
uh, how we can get there and make it make
it more, more useful.
Um, if you if you end
up after five
or six days of fussing with seven
billion reports, that's clearly too many.
So we have to figure out how to, uh,
how to reduce them to to see which
ones can be.
Interesting.
So what
in as concluding remark remarks,
what did we do? What are what are steps
forwards? How can we improve from here?
On one hand, we want to fund
all the things.
So we want to go deeper.
We want to find more software.
We want to find better test cases,
better, better fuzzing inputs and get
cheaper coverage for the overall systems.
And especially looking at
Firefox.
One thing we want to do is want to do
selective fuzzing instead of just blindly
fuzzing.
Uh, large software system,
which may result in a large amount
of false positives due to
the way that the software is architected.
Think about the Firefox example again.
You allocate an object, it may
be reused in different times without
being freed.
This would so or
let me take a step back.
Right. So one of the problems we've seen
with Firefox that led to a large amount
of reports is that
you often allocate an object.
You return this object to a pool,
you know, the developer knows that there
is no more life reference to that object,
but is then being reinterpreted
and reused as a different type of object,
which leads to type confusion report.
But this is not an actual exploitable
bug. It's just a quirk of the
lack of a type system that C++
has.
So we want to move towards
a more selective form of funding
where we can say, hey, we're only
interested in this subset of the type
hierarchy, so we want to do explicit type
checks for this subset of the type
hierarchy, but we are not interested in
anything else.
So focusing on, for example, just
the dumb or just a Chavez script object
or something like that.
In addition to that, we are also looking
into an always on check for
polymorphic objects think
think back to the contraflow hijacking
defense that I talked about in the
beginning of the talk.
One option is that you check the
type of the object whenever you do a
virtual dispatch.
So this would protect against
the type confusion from from before.
Right.
Um.
As in as an example, if you are
looking at the.
At the code here before
I, uh.
When I compiled it, it made two versions
that you may have observed, the second
version is W uh, was a
type safety.
Mechanism.
And if I run it with this type safety
protection instead of, um,
opening up, uh, calculator,
it actually reports, uh,
type safety or type confusion.
So we want to extend this into
our.
A bigger and larger system so that we can
we can run it partially, we can build it
on top of of Firefox's in a selective,
selective part, but you can also use it
for your software to specifically
protect against
these dispatch vulnerabilities,
as we just saw here, as to see how
I wanted to dispatch, we stopped
the execution and terminated the program.
So to actually conclude type
confusion is fundamental in today's
exploit.
There's a set of existing solutions that
are incomplete, partial and slow and
make it very hard for us to
protect these systems.
And especially in large software systems
like Chrome, Firefox and other
large, uh, large mechanisms.
We need to develop new ways to protect
and enforce type safety at runtime.
Um, I presented a hex
type, which is an LVM based
extension that allows you to trap upon
type confusion so you can compile your
software, uh, with
these type confusion protection, which
allows you to track the true type of
every object to allocate and then upon
typecasts or dispatches allows
you to do an actual type check
so we can trap at the type
confusion and not at the later
memory safety violation.
I showed you one application of this
approach where we've combined our
hex type mechanism that does the type
checking with, uh, fuzzing approach.
And we found a nice set of
of bugs, uh, that
are now being fixed or were fixed
overall. This has a reasonable overhead.
So for Firefox, depending on
the benchmark, we have between zero
and 50 percent.
Uh.
Overhead and
you can integrated with EFL for broad
bug discovery and as always,
with our research, it's all open source.
So you can download to the system, you
can play with it.
It takes about 15 minutes to build it on
on your machine.
And you can then compile your software is
LVM and full type checking.
And we said I would like to thank you for
your attention and I'm happy to take any
questions.
Thanks.
Awesome, awesome.
So we have four microphones
here, one, two, three, four, where
you can ask questions that and
just to be clear, a question is like one
or two sentences with a question mark
behind it.
And with that, I'm going to go to
microphone to thank
you for the presentation.
Oops. This is not
what is also be possible to have a
compiler plugin that
prevents you from misusing static cost
at compile time.
I could we could you build something
that only allows you
to use does not allow
you to use that because combined with
dynamic dispatch.
Um,
let me think about your question, would
you want to detect the type confusion
statically or would you just forbid
the programmer from using, uh,
static cast for any object
that has a virtual function?
The second one, I just want to prevent
books like usually in C++, we try to
load as much checking as possible and
compile time so we do not have their own
time overhead. So it would be nice to
disallow the cost for
anything that is
virtual something.
Right. This allows telecast.
Yeah. Yeah.
So you BISAN followed a similar
approach. They convert
and make all the static costs for
polymorphic objects into dynamic costs
and to simply replace them.
Unfortunately, as the
the Grétar example showed, the base class
is not necessarily polymorphic.
So you run into VR
runtime behavior with
AWT, like the base class is
not polymorphic. And if you turn a
certain cost into that because you fail,
right.
There's C++ code is really,
really messy and it's very hard for
you to actually simply replace them.
You can report it as a warning,
uh, as part of the compiler
process.
But in the end, you need to you need to
support non polymorphic
based classes, which are
surprisingly frequent, especially
for, uh, for browsers, as we found
that there are several base classes that
are non polymorphic.
Thank you.
Microphone three, thank
you for your great talk.
You mentioned that in Firefox you had
the problem that some objects were freed
and then reuse.
So I was wondering, could you build on
top of temporal memory safety
analysis and take
that information into account to make
your analysis more precise?
Sure. Uh, temporal memory
safety usually clocks in.
I'd like to 3x overhead.
So this is actually more expensive than
what we are doing, but it wouldn't be an
obstacle to fuzzing, right.
Like, if you only use it for fuzzing, not
in production, right, um.
Sure. Well, ideally, you
would combine it with additional
sanitizers.
You would you would use our our type
sanitizer combined with a
business like, um,
spatial and temporal memory safety
sanitizer as well. So you can you can use
it as as well.
In addition to that, um.
I you asked about
if the additional
data that you have from the type safety.
System, sorry, from the memory safety
system would be useful in our analysis,
and I would answer you that the
temporal memory safety sanitizer
will run into the same problem.
I said this, this is C,
B or C plus, plus we have a
lot of untapped memory and
Firefox simply reuses the memory,
even though there are still references to
it, and then just changes to type A.
And this is allowed, according to C++,
Symantec. So they are not doing something
illegal. It's just that it's really,
really messy and we'll have to work
around these these quirks that they have
there. Thanks.
Thanks. A microphone for.
To be frank, I'm
a bit puzzled
by your terminology because,
um.
Just because the type system is not
checked, it doesn't mean that it doesn't
exist. So wouldn't it be
better to have some
static solution like
preventing,
forbidding?
Down costs that are static
and forcing developers
to use dynamic down costs only.
It would be much faster
than having a father fuzzing
the entire application because the
result and the problem
is at compile time and
not.
Throwing some fuzzing step.
Sure, this would be a great solution,
unfortunately, it doesn't scale.
I try to do this for 75 million lines
of code where you have two hundred
thousand violations.
Right. So rewriting the whole
software stack is always a solution,
you may run into time constraints.
By the way, this is just a source
base that we have to live with.
It's a great solution.
So your approach would actually work
really well to
protect against these down costs or
illegal down costs or confused down
costs. Um,
you may have a hard time rewriting all
the software, and there
are non polymorphic.
Objects where you cannot enforce
then may costs, so you can only do
dynamic costs for polymorphic objects
and you may have an illegal down cost for
non polymorphic objects as well.
You can take you can take it offline, it
sounds a little like complicated
microphone to thank you
for the talk while you gave an example
counter to it at the very beginning, you
made a claim that Static's don't
introduce any code.
This isn't exactly correct, as everything
is implementation defined at this point.
But you can it can shift,
especially when you're going from a type
that has multiple inheritance and it
needs to shift around to get to the
correct thunks.
This introduces a very specific type of
type confusion bug where if one
casts to avoid pointer and
then from the void pointer to
the different type in the chain,
it won't do the shifting properly.
It also is a very specific one
that might be hard for you to catch.
How do you how do you attempt to catch
those once?
You mean for our system?
Yes.
You at one point in time you allocate
a slab of memory, you allocate a piece of
memory according to specific time.
And if you if you do a new full type,
we record this is now of type these
memories of Type four, then you can do
anything you want with your pointer.
The memory slab will still be tagged
as Taperoo.
Whenever you cast back into some other
type, we look up what is the base
type of this object?
If it is foo and you're crossing into
foo, everything is fine.
Otherwise reported an error.
And you do this when you're casting from
void pointer.
We do this when you're casting from
anything.
Well.
Thank you so much.
Which actually brings me to tonight's
topic that I didn't really talk about.
So this is a nice observation that you
had your had here.
See, style costs are one
of the ugliest features that C++
has. And
I just want to to call out this
ugly feature, which which pretty much
does so insist
on cost if you do just the parentheses
and the target type.
This is pretty much a hammer that
hammers. Does this object into
this disaster type that says
make this underlying memory
area now of this other type?
So this is pretty much the ugliest thing
you can do if you're programing in C++.
Never, ever under any circumstances
you see star cost because this
really messes up the underlying type
system.
Thanks.
Microphone three.
I was wondering if you tried your
tool with Shachar or Safari
and what happened?
We did not.
We ran it with Firefox mostly,
we tried a little bit with Chrome, um,
again, one of our future works
is to ported to more software
and larger software so far
for this presentation here we focus
mostly on smaller libraries and,
um, yeah, if anybody wants to
to offer more resources, feel free.
If you want to run it on safari, it's
open source downloaded building on your
system, run it on safari and report
the results.
Yeah, just my thought here is I think you
might have even more of those Firefox
type issues with the way that they do
testing. Um, though, I
think like the difference we found
between Firefox and Chrome.
So I don't know about Safari, but I can
tell you an anecdote between the
different about the differences between
Firefox and Chrome.
The Firefox has a very old code base,
so there's a lot of ugliness hidden in
there. So we found, uh,
things that, um, that do direct
dispatches or indirect dispatches
in assembly code.
And they are.
Doing weird stuff to table pointers
in in inline assembly, just due to the
legacy nature of Firefox
while in Chrome, Chrome is a much more
recent code pays and it uses a much more
recent C++ standard.
So it's much nicer and much less
likely to find bugs in there.
So just the age of the
code base, um,
is bad for for Firefox or
may lead to a large amount of
potential vulnerabilities.
So some refactoring is needed
like a run for um.
Yeah, well it is like super cool and so
on. But I'm just my question is
like the concept of down
casting is a code smell.
Right. And it isn't your tool
kind of owling like people to
keep code smell and keep
writing smelly code basically.
And I just even like the reason saying,
well, we already have a code base
wouldn't be better to have like something
that would help people to to write notes.
Kelly code.
Sure. Let's rewrite everything in Rust.
I'm all up for it.
Right.
Sure.
There's justice 100 plus million lines of
code that are lying around and we cannot
easily parted.
And the the job that we
try to do is we try to make it as secure
as possible and we try to find potential
vulnerabilities in the existing code
base. If you have unlimited resources,
let's just stop right now and rewrite
everything we have and in a safe
language, sure, I'm all up for
it.
It's just the fact that we have this
large amount of code that is out there
and we are using it right.
So we have to do the best that we can
to bump up the protection
for this code as much as possible.
Microphone to.
Thanks for a great work,
do you have any similar idea for C-code?
Is this work or work
already done? Can I read something about
it? Some of it is in progress.
We can talk offline about it.
Yeah, cool.
OK, microphone three.
Thank you for the talk.
I would like to know why.
Why did you take the
time during a vacation and not
inside your contractor, which
would feel more natural to me.
And it would also be able to
solve the problem of false positives.
Oh, not every type has a constructor.
So what we do pretty much is when
you when you allocate a new type, we
don't run it in the Allocator, but it's
part of Klang.
We know Verdie allocators are
and we can then target and adds the
metadata as additional code.
So as part of our Klank Pass, we detect
wherever data is being allocated
or where the individual costs
allocators are and we target and then
instrumented in a later step.
This allows us to tag all the locations
and not just the ones that have
constructor's.
So it allows us to extend the coverage
further to not just, uh,
classes with constructor's, but all the
the object allocations.
Even so, imagine
and this is something we found in, um,
some older software as well.
You allocate uh you
call mallock on a struct.
And then you use it as a class,
right? You would never be able to
detect that as part of a
instrumenting or a constructor, and it
actually happens in software like Firefox
and other older code bases you
call mallock instead of new and
use a struct instead of a class.
So I set the code is really, really ugly.
And here you see the the similarity
between C and C++ in the end of class
is just a struct.
And if you allocate objects
as struct, you may end up
missing a large amount of objects.
Shouldn't custo.
Is that because then in
compile time if you cast a
struggle to class.
It's like
a class, a class can
be a struct, right?
So they are the equivalent.
If it's the same type.
You can use this struct as a base type
name and then you can have a class that
is a descendant of that struct.
Oh, right, I understand.
I think you write C++
is ugly.
Tell me about it.
Was that we're at the end of her
questions.
I'd like to thank our Speaker Matteus
again for this wonderful talk and
contribution to the C++ mess.
We are trying to fix it.
OK, thank you very much.
Thanks.