Good afternoon, everyone. I'm Mamta. I work as a software engineer at Leica Geosystems
and I work mostly in embedded domain. So this is the outline of my talk. So just to keep
everyone on the same page, I'll be giving a brief introduction about LLVM and Clank
and so that the terminologies next I'm using are clear. Then I'll talk about compiler RT
sanitizers, what they are, how you can build them and how exactly they work and then my
final thoughts about sanitizers. So this is a typical compiler pipeline which most of you
are aware of and they call it as a textbook diagram as well. So whenever you write a source
or a code in any language to produce a binary executable for your machine it has to go through
multiple stages. So mostly the most important ones are the front end, middle end and the
back end and the front end actually does all the lexical analysis, semantic analysis, it
checks the syntax and then it generates an intermediate representation. Then it is passed
to the middle end which does optimizations independent of the target and then it is passed
to the back end which generates executable and it has more optimizations depending on
the target you are building it for and sometimes if there are multiple object files then comes
in the picture linker.
So how does LLVM fits in our pipeline? So basically the same, it is a modular and useable
compiler framework and it provides front end, back end and LLVM core which is the LLVM optimizer.
So if we map it to our compiling pipeline, so for depending on the language you are trying
to build or use this compiling tool chain you have compile sorry you have LLVM front end
that is the clang for C languages and Rust, Rust C for Rust and then depending upon the
target it will use the target as x86, 32 bit or 64 and the main part which is most reusable
from the LLVM perspective it is the LLVM optimizer and the core.
So if I have to develop a new compiler tool chain I can easily reuse LLVM I just have
to write my own front end and if there is a new hardware back end maybe. So in short
I can say LLVM is like a Lego of compiler tool chains. Next focusing a bit more on one
of the LLVM front end clang which is for targeting mostly C, C++ and C like languages.
And here it whenever you provide your source code it performs some lexical analysis generates
tokens for parsing then it does semantic analysis and generates abstract syntax tree and the
end goal should be LLVM intermediary presentation. So just to summarize LLVM is a collection
of all modular and reusable compiler technologies and there is much more to it because it provides
now with static analyzers, sanitizers and more libraries so it comes under an umbrella
project as LLVM.org and clang is a compiler front end which is mostly for C, C++ and
C like languages but when we say clang executable it is more than a front end. So when you build
LLVM you have clang executable as well and it is like a compiler driver.
So for example if you have a static compiler and we have to tell it which directives to
use or to use this hyphen i option where is the standard library path for example for
C, C++. So clang as a driver does most of the housekeeping for all this task and it
helps this compiling pipeline to tell you where to look for the libraries and also provide
some OS related features and how your OS is. So that was just a brief introduction about
clang and LLVM.
So now I will talk about compiler RT sanitizers it is one of the sub projects in LLVM but
before that I will highlight about the runtimes. So LLVM comes with compiler RT runtimes it
is pretty much equivalent of libGCC for LLVM pipeline and it provides target specific support
for some low level functionalities which hardware itself cannot do. So it consists of three
main components built-ins, sanitizer runtimes and profilers. So built-ins provide implementation
for target specific hooks which hardware itself cannot do.
So just to simplify it a bit more for example if the 32 bit system cannot do 64 bit division
so here you can see a code snippet and if I try to compile it using clang. So first
we do a normal compilation on x86 machine which is a 64 bit and here on left you can
see it directly calls this diff cube which means it performs the division itself but
when I use hyphen m32 which is I am forcing it to compile for 32 bit machine it depends
on another call it actually calls this udiff i3 which is basically a built-in implementation
in compiler RT. So this is an overall picture of compiler RT runtimes.
So talking about the sanitizers, sanitizers are like runtime checks which it is like adding
a code probe in your code to verify if there are any memory bugs or to sanitize the code
or to find any security flaws. So in case of LLVM it is provided by compiler RT and called
as compiler RT sanitizers and there are multiple kind of sanitizers available. First is address
sanitizer which you can use to detect use after free buffer overflow and memory leaks
as well. Then you have undefined behavior sanitizers, memory sanitizers to identify
if there is some uninitialized memory and threat sanitizer to detect some race conditions
and dead locks. So here it is just an example of how sanitizer
looks. So here is a very simple code where I am allocating some memory in heap and then
I am trying to I am freeing it and then I am trying to access it after free. So if you
build this code with your sanitizer flag on and try to run it immediately it will complain
that you are trying to access some memory after free. So that is how it looks like when
it is built with address sanitizer and I will talk in more detail about how what goes behind
the scene. But before that how to build compiler RT sanitizers. So there are lot of documentation
around as well and it is very easy to follow as well but sometimes it works, sometimes
it does not. So you can build first compiler RT with LLVM it is easy you can directly
enable it with LLVM enable projects when you are building your complete LLVM tool chain
and you can do a separate build as well if you have your LLVM config and use generator
of your choice. To enable the sanitizers so if I am doing a build along with the complete
LLVM tool chain I just have to use this flag compiler RT build sanitizers to on and when
you do this and here I am using Ninja as a generator you can see we get a config out
of CMake and you can see it is enabling different sanitizers as address sanitizer, leak sanitizers,
memory sanitizer, thread and undefined behavior.
And same you can do for and after sorry after the installation and build you can get these
set of libraries. You can do the same for the standalone build as well with the same
similar flag and this is the config generated when I am doing a compiler RT standalone build
and then it is also possible to cross compile RT sanitizers you have to provide lot of flags
and you need to have your arms this route as well and I personally do not prefer this
way but there is a talk today in the end from Peter about building embedded tool chain
using LLVM. So, to make it a bit easier for all the embedded
developers there is a in Yachto project there is an open embedded layer called Meta Clang.
It makes it bit easy because it provides everything for building your tool chain and you just
have to include this layer if people are aware of Yachto builds. And just few configurations
are needed like you have to enable the SDK, you have to use LLVM runtimes and then either
you can write in your package groups or in local conf to include compiler RT and compiler
RT sanitizers. And this actually generates a SDK and it is very easy to distribute this
SDK to other developers or like in our case I used to send it to the application team
so that they can use this and the people who are developing C++ code they can run their
code they can compile their code and run sanitizers on it.
So this was actually while contributing to Meta Clang I came to became more aware about
compiler RT sanitizers and also now it is available for arm 32 bit and arm 64 bit and x86 of course
and you can easily test it also on KMU arm as well just specifying your sys route and
running your test code to see how it behaves on your actual target.
So that was all about what is compiler RT sanitizers, how we build it but what exactly
goes behind the scene. So here I am using an example of address sanitizer to use and
here is a very basic code where we are taking some arguments and converting into integer
but here you can see we are using argument counter and the value for this can be very
large as well. So first time when I try to compile it with Clang it compiles and when
you run it after like as you can see easily and it is very easy to spot here I have defined
the size of buffer to be just 2 and if I provide input more than 2 it should fail actually
that is the case but it is very the crux here is like it is difficult for some machine it
will fail for 3 and for some machine it will fail with the 4 input and leads to the segmentation
fault and we do not know what happened exactly behind the scene. Second when you try to build
it with address sanitizer enabled or link it with compiler RT and with your address sanitizer
flag as well and the output is actually a bit large this is the first part of it.
So it can easily spot that there was a stack overflow stack buffer overflow and it also
points out if you build it with hyphen g option it also points out at work line it is failing
but what exactly led to generation of this kind of error. So in very simple term when
address sanitizers adding sanitizer is like adding an additional code to your actual code
to just check when it is going to fail and report the error. So here it looks very easy
okay if my buffer size is more than 2 or sorry if the input size for the buffer is more than
2 just notify it is an error but behind the scene for address sanitizer it is much more
and it can adopt multiple strategies to implement it. So here address sanitizer uses memory
mapping so memory that should not be accessed is called as poisoned memory.
So behind the scene it is implemented like to check whether this is getting poisoned
or not so here poison means either it is referring to some deallocated memory or some already
allocated memory or there is getting some overflow or not and just report the error
but there is much more how this memory is mapped. So for any code built with any application
that you build the virtual address space is divided into two kind of memories one is
shadow memory and one is application memory. So for address sanitizer it is more important
to implement this is poisoned and report error in a very compact and fast way then talking
about shadow memory and the application memory.
So application memory is the main memory of the code and shadow memory is a copy of application
memory but here 8 bytes of your application map memory is mapped as 1 byte in shadow
memory and when the sanitizer checks whether this memory is poisoned or not it is mapped
to either 0 or 1. So here it is a small portion of shadow memory of our example and you can
see the memory which is accessible and which is in good condition is marked as 0.
But here when we are allocating we are checking the buffer here you can see after it is marked
as so the memory which is like more when we are trying to access more than the allocated
buffer size it is marked as 1 and these and here you can see in the square brackets it
is it is marking that hello this here you are supposed not to access this it is getting
out of the range of the buffer. So that is how this application memory and shadow memory
is used and then you can see here we restrict it as the red zones.
So if you have anything greater than these than the allocated one so that one gets marked
as 1 and you can see f 1 f 3 around and so that is how the address sanitizer works by
using this shadow memory and application memory and knowing about your shadow memory and whenever
it spots it is 1 then it says okay there is a problem. So that is all about the address
sanitizer and how it works.
So here are my final thoughts about using sanitizers it is a very great tool to find
bugs and memory issues and in run time for complex applications. So by using sanitizers
you can improve your development and you can spot the errors very quickly and since we
had a very simple example but when the code gets more complex it is more difficult to
find what is the problem. And though with the sanitizers it is more like a tool to check
not to be used in your production it increases the code size but the sanitizers are comparatively
more faster than Valgrind the existing one and still there are not all the architectures
are supported uniformly like for example for arm 32 we do not have threat sanitizer completely
implemented so I hope we see a better implementation later and it supports threat sanitization
on 32 bit machines as well questions. Yes it works sorry the question here is like
I am using the optimization flag for building with address sanitizer enabled yes it can
be enabled to up to 3 as well and you get similar kind of log.
So the question is where I am placing the instrumentation to identify whether my buffer
is overflowing or not to use assertion here I am not sure I can answer that well but the
strategy of this one is using more with this memory mapping one and also here since this
buffer size is very small so may be assertion might work here.
This I am also not much aware of so the question is like if I can sanitize it for a very specific
portion of the code right yeah you can no I am sorry I am not much aware of it you can
reverse it you can prevent some specific code from being instrumented either by annotating
the code or by using a blacklist. Yeah there is this blacklist address sanitizers
options as well yeah. Have you you say that sanitizers are primarily
a development tool and yes they mostly are but have you ever looked into using some of
them for actual production executables for hardening for example UV sun has a fairly
minimal overhead so it can be used in production especially if you make a strap or if you make
it if you use the minimal runtime or also there is GWP ASAN which is like a lightweight
sampling version of other sanitizers which can be used in runtime I think Android is
using it actually. Okay so does it support some of the embedded
platforms as well like ARM32 or 64? It supports I believe it does well UV sun works pretty
much everywhere almost everywhere GWP ASAN works I think almost everywhere but it is
also dependent on like external function for unwinding so it might not be great in all
scenarios it really depends. I was wondering if you can use this for kernel ring zero code
as well. Yes you can but like there's like. But there's a. While there are no if you don't
have like the runtime it's not in the NPM it's actually offered by the kernel and you
infer the checks but the hooks themselves are implemented in the kernel. Well some sanitizers
don't require any runtime. So for ASAN you need instrumentation. Yeah you can use kernel
address sanitizer for it. But like ASAN also needs like the ASAN best point like the IRF.
I think it's just a level implementation of the stuff. There's other sanitizers in kernel
which are implemented this way. I think that it's GACFI. Yeah. Yes. Do you know where the
code lives currently that does the hooking to malloc and free like the C library? Like
how does the interaction with the C library work do you know because like I could have
that for example have a muscle instead of the C library and I guess the code has to be
adapted somehow or there must be some book infrastructure. So do you know how that works
and where the code lives that books malloc and free for example. It's in compiler RT
instance if I'm right. So if you change the version of library you have. Yeah the F on
runtime has to be for malloc and free. Okay. So outside of the C library. Yeah. For ASAN
yes but for example for ASAN for leak sanitizer which is enabled by ASAN you need to be aware
of things like PLS layout and so on. But the runtime in LVM does implement both. Yes. Are
the sanitizers shared between GCC and LLVM or does each have its own implementation?
They are shared. Yes. I noticed that one shadow byte corresponds to eight actual bytes. However
considering that there are special values for it. Does it mean that there could be situations
where there could be some bytes which are not protected necessarily? Yeah there can be
some cases where you can call it as a false positive kind of thing or sometimes it is
checking very much in the end. So it was a very small example but it can happen that
you get these square brackets at the very end and it's an overrun case. So that can
happen. Yes. Could you get a false negative in some sense in the sense that if you have
two almost adjacent blocks of advocated application memory and you access a pointer from one to
the other could you get a false negative in this sense? So because that would not be
a point and it would still be part of the application but for example what an address
could you get? Yeah it can be. It can be a false positive case maybe. Yes. Okay. I think
it was already partially answered before but I am not sure. So I am just going to ask it.
So for example I am also working on an embedded scenario and we don't use, I think we don't
use glpc or muscle like we have our own set of routines for malloc and free. Yes. Would
it also work in that case or is that then something that you would need to, are there
more requirements before you can use these sanitizers on those devices? For my experience
I have been just using this meta-clang layer included and for testing with like or for
just doing on the SDKs and emulator it was perfectly fine. So not on the actual devices
because it is just meant for testing.
All right. Thank you Manta. Thank you.
Thank you.