Hello everyone, my name is Andrea, and today I'm going to tell you about what I think
about fuzzing in Brazil.
This presentation is not about fuzzing itself, but rather how we've failed at it.
So before I start with the big pose of fuzzing, I will tell you a bit about fuzzing itself.
I hope some of you already know about it, I don't have a lot of time.
So fuzzing is basically an automated testing technique.
The idea is to just send random input to a program to see how it behaves in that case,
and how it works is that you use typically a tool, like a buzzer, that is going to generate random input for you,
and then you're going to close some functions with that random input,
and the buzzer is going to report some findings, and if it finds any interesting input files,
it's going to write them to a course.
Findings in this case can have crashes, can be hands, but can also be timeouts.
So for fuzzing, when you first do it, you typically start from an empty corpus,
but as you run fuzzing, you're going to generate some interesting inputs,
which is helpful because in the next ones, you can just reuse those inputs and start from scratch.
This helps with finding interesting things faster.
So in this VMM, we implemented fuzzing for VM RTIO.
We have three fast targets, one for the RTIO queue, one for the serialization of the RTIO queue,
and one for the RTIO WISO in the Rescue Memo project.
We only have implementation for the packet, so that's what we fuzzed.
During fuzzing, we discovered three crashes,
and only one of them is triggerable by any quotation malicious driver,
and what we have now is that we are able to run fuzzing
for every request that you're submitting to Rescue Memo to the VM RTIO repository.
The fuzzing is apparently using link fuzzer,
and besides the fuzzing that is happening in Rescue Memo itself,
the folks from Cloud Hypervisor are also running fuzzing,
and we also discovered a timeout theme in the package.
So this actually brings me to our first report.
So what is it you want? It should actually be...
It's a people, and that is me.
The first people is that you actually have to run the timeout,
just in the code field for what you're in fuzzing for.
Because the default, for example, for the fuzzing that we were using,
is actually 20 minutes, and since we are just working with what I always would have used,
and there's nothing that can possibly take 20 minutes to process,
so we have to adjust the timeout to 60 seconds in our case,
and this is something that was recommended by the folks from Cloud Hypervisor.
Now, how we're running fuzzing in Rescue Memo is at the library level.
The advantage of this is that it's easier to set up.
So it's really important that it's easy to set up.
It is a good thing.
People are like, oh, but you're running fuzzing at the library level,
so you don't have to have the kernel that's like so easy, so simple.
So they're like, yeah, it's great, right?
I mean, like, this is a good thing.
And yeah, it's a good thing because you can also run on almost any host.
You just have to have a fuzzing install in the repository,
and then you just run fuzzing.
And it also runs in a user space.
There's also disadvantages, of course.
The first one being that you cannot cover the whole repair setup,
so that means that you're going to have some things that are great to be fuzzed.
And then because you are fuzzing in user space,
we need to do some more things for the driver,
and this tends to be a bit complicated.
And also you can find false positives.
With the false positives, the idea is that you will find crashes
that otherwise would not be triggered by a driver,
because maybe you have some other chase in place.
I would say that it's still important to fix these ones as well,
because you never know how you're going to change your code
and how it might end up actually triggering those findings in the future.
And for the mocking of the driver, how it works,
we've already simplified here,
but the idea is that the driver is writing something in memory,
and then the device reads what the driver wrote in memory,
and it does stuff with the data.
We want to fuzze in the human, and part of the piece we're doing in the human
is this side of the device,
and then what we need to mock is actually the driver's side of the communication.
And in fuzzing the human, what we did is that we started this mocking
of the driver from the beginning,
so we needed it anyway to run some unit tests,
we needed it for other kind of testing as well.
So we had an initial mock interface from the beginning,
and when we wanted to do fuzzing, we just evolved the mock driver
in order to support that as well.
Okay, so at the high level, how it happens right now in Rasmussen,
is that we parsed the random bytes,
we initialized the mock driver with the data that was parsed by fuzzer.
At the high level, it ends up with some descriptors
and some key functions that have some random input
that they need to process,
and then we create the queue,
and we call these random functions with random input.
And yeah, the second before is that if you are trying to do fuzzing
and you just start when the project is already mature,
what is going to happen is that it's going to be a bit difficult,
you might find it very complicated to retrofit it.
So instead, I know that it's not necessarily viable to start fuzzing
when you start the project,
but what you can do instead is that you can keep fuzzing
in the back of your head,
and then when you create some mock objects or some unit tests,
you can think about how you can actually reuse them in fuzzing as well.
Which is what we did but not very well.
So one of the crashes that we actually found
was that the mock driver was crashing on invalid input.
So we had to adapt these actually to return errors,
even though it was just one test,
we couldn't just crash on invalid input anymore.
So the idea is to return errors at the level where you want to do fuzzing,
that can be processed at higher levels and so the fuzzing can crash.
And now for structural interfuzzing.
So with those structural interfuzzing,
how it works is that the fuzzer is going to generate some random bytes,
and then you have to interpret these as the bytes that you have to use
for your library.
So with structural interfuzzing, it's really nice because there are some tools
that are just going to basically interpret the random bytes as a structure
that you actually need.
So it's super nice what it does is that it significantly reduces the code
that you need to write,
and even the rest of the information is arbitrary.
Now, we had to change it unfortunately,
before we knew that we had only 270 lines of code,
and now we have around 740 lines of code for the fuzzer.
And unfortunately, it came with some problems,
so that's why we have to actually basically...
The most important part is that it's not really produceable.
So you can't really use the corpus that you had in previous runs,
which was a big problem for us,
because basically what happens is that arbitrary is introducing some...
some randomness in one terminal so that it goes right into the input.
And that basically means that you cannot use the corpus from previous runs.
The thing for here is that we would like that we can do incremental improvements
for the fuzzer,
and we didn't check that what we want to increment
can actually be implemented through N.
So, yeah, instead, a better point would be to make sure
that we can actually reuse the corpus that we generated.
Okay, and now about when fuzzing actually fails.
So, we had a PR in U.S. Human,
at this point we were already running fuzzing for cool requests,
and there was a PR that was introducing actually an overflow.
So here the overflow is that the packet header size addition to the packet length
can actually overflow,
because the packet length is set up by the joint.
This bug, well, I actually found it during code review,
so it was a bit unexpected because I was hoping that the fuzzer was going to find it,
which was not the case.
So after some dive deep,
I realized that running fuzzing for just 15 minutes might not actually be enough,
because this bug was triggered with fuzzing about 40 minutes instead.
So, how we fixed that is that we added a fuzzing session that is optional
and that fuzzed for 24 hours.
This one is to be started manually by the U.S. Human Maintainers,
and should only be started when there are cool requests
that actually impact the fuzzing relation.
This is because we're also consuming a lot of resources
when doing fuzzing, and also you don't want to block all the cool requests for 24 hours.
So typically the last one on the slide is a page that quickly needs to be executed,
so blocking it for one day might not be reasonable for all the cool requests.
So the people here was not trying to fuzze for long enough,
and, yeah, instead we had to work our way to find a way to not block cool requests,
but at the same time to provide a way to fuzze.
Fourth coverage for rust.
So in rust you can actually get coverage information by running LLVM.
In rust you only get light coverage.
So basically this was the starting point of the presentation.
I was thinking I would come here and I'm going to show you how great it is to run fuzzing for
15 minutes and then more minutes and then the coverage and all these really extravagant things.
And so we ended up with fuzzing for 15 minutes generating 10 for these regions
and the coverage of around 82%.
So it's like, well, it's okay, that's good.
So then let's just run with some minimal coverage as well.
So this is some coverage that we generated from unit tests.
Let's just feed through the fuzzer and see how this changes.
There was no change, actually.
So I was like, okay, it's not bad, not bad.
Let's just run for two weeks.
So what do you think this might happen now?
So actually, it's working.
At this point I was like, you press, I have to change my presentation, so it's not what I expected.
But instead I learned something, right?
So you can't actually use coverage to decide where to stop fuzzing.
So instead what you can do is that you can use coverage information to see when,
what the parts of your code are not actually covered.
And yeah, well, that's about it, actually.
We see the summary of the people that we read into.
And I think now we have a lot of different questions.
Did you look at how the fuzzer works and then what areas were not covered
and try to figure out why it wasn't found in those areas?
Yeah, so the question was if we looked at how the fuzzer works and what areas were not covered.
Yes, we did, and I have a slide for that.
Thanks for the question.
Okay, so actually, I have two slides for that.
There were some functions that we were not calling on purpose.
So because on the virtual queue, for example, we have some functions
that are just iterating over the script chain and then they're doing something with the data.
And at the virtual queue level, you can't do something with the data.
So it's like, okay, this needs to be fast at the high level, like at the device implementation level.
So it's like, okay, we're not going to call these functions, which is a bit hilarious
because that's where Proc Hypervisor actually found the timeout problem,
which we were not able to reproduce with the virtual queue, but still.
And we actually did this one function that shouldn't be called during fuzzing.
And then I rerun the fuzzing and, yeah, it's a bit better, but it's still not great.
And then I looked into what, well, actually, you can't see very well.
That's unfortunate.
Yeah, so I looked into what actually is not covered and you're not seeing there, so you have to trust me.
These are actually errors.
So the printing of errors to files.
So since in the fuzzing we're not actually initializing a logger, these things cannot be triggered by fuzzing.
So there's lots of error in this printing to a file that's not happening through fuzzing.
Yeah.
What's subsequently taken to actually make sure it covers everywhere which needs to be covered?
And so discovering certain areas which clearly aren't covered.
I didn't understand the question.
Which areas of view?
Well, what's subsequently taken to make sure the areas which weren't covered in the fuzzing are going to be covered in the future?
Oh, okay, so the question was what measures are we taking?
In order to make sure that all that was covered before is going to be covered in the next generations?
Yeah.
None?
So right now we're not doing anything.
This whole coverage thing is just something that I need for the presentation and it's not automatic in any way.
But this is actually a good point for future investment to make sure that we're covering code because what we help with as well is that we make sure that new functions that we are adding to the code are also covered.
So it's a great point to make that way.
We're talking about the structure of our fuzzing.
Yeah.
And you mentioned that we cannot reuse the code to explain a bit more about that.
Okay, so the question was how structured are we fuzzing and just that we cannot reuse the code.
Let me see if I actually have a question here.
No.
Okay, so the idea is that what we were using, which is arbitrary, when it was taking the uniformed fuzzer was also adding some randomness to it.
So because it was random, basically, every time it was writing the purpose to the file, it was introducing some randomness to it.
So when the same people get to read again, then it would not have been the same.
So where does the randomness come from?
Where does the randomness come from?
This is just how arbitrary it decided to implement it. There's actually an issue in arbitrary that they are aware of the problem with their not actually...
It doesn't seem like they are just fixing it for some reason.
So what we ended up doing is that we ended up doing some custom serialization with info, which is also a very well-known Rust package.
It's not much more digital than it is arbitrary. And it doesn't matter.
When you discover a bug with this puzzle, does it transform into a unit that's not the one?
The question is, when we discover a bug, does it try to...
Yeah, the way that we are fixing this kind of problem is that we are always adding a regression guess for them just to make sure that they don't...
I was wondering about the computation requirements. So how many cores are you using?
How many cores we are using?
So when we read for two weeks, we actually used 96 cores.
In uniqueness, I do not... So when you are running on coding, because I don't know exactly how many...
One? Maybe? I don't know. But I think we have been running on 96 cores as well.
That was another one. Can I see that guy?
One minute.
We found a color case that we are trying to shrink the crazy smaller color steps.
Oh, this is... I don't know. Let's shut up afterwards.
Thanks.