LibAFL icon indicating copy to clipboard operation
LibAFL copied to clipboard

Timeout for LLMP Clients

Open domenukk opened this issue 4 years ago • 27 comments

LLMP, short for LowLevel Message Passing, has the concept of multiple clients, which are all connected to one broker. The broker broadcasts each new message it receives from a client (over an individual shared) to all other clients via one ...shared... shared map. So far, LLMP Clients never get unregistered. It would be good to keep track of the last time a client sent a message, and unregister them accordingly. This could be done in the once method of the broker (which is called in a loop in the broker) https://github.com/AFLplusplus/LibAFL/blob/6e4c6a9b6b0db909853a19a68ba769bb2516f1bf/libafl/src/bolts/llmp.rs#L1349

On top, we may want to support a good-bye/unregister message from clients when they know they are done. This could be called inside a Drop crate automatically.

domenukk avatar Mar 20 '21 01:03 domenukk

So far, LLMP Clients never get unregistered. It would be good to keep track of the last time a client sent a message, and unregister them accordingly.

what do you mean by unregister and what point should they be removed? is it like removing client from llmp_client vec? will last_message_offset work to keep track or should I use last_message_sent.

aga7hokakological avatar Mar 25 '21 13:03 aga7hokakological

Hey @aga7hokakological , yes they should be removed from the broker's vec after the broker did not receive a new message from them for n seconds. We'll need to keep the last recv time for each client. handle_new_msgs could return the amount of new messages handled, for example.

domenukk avatar Mar 25 '21 16:03 domenukk

Okay got it. I will try to implement.

aga7hokakological avatar Mar 26 '21 05:03 aga7hokakological

I am bit confused here. say if I am adding some time function in LlmpMsg struct to get that time instance and then in once method I am calculating if broker received message or not and then removing it from vec. That's how it should be right? or maybe changing register_client function to get time of last_msg_recvd variable and then comparing it in once function that it haven't received message might work. just want to know what you think.

aga7hokakological avatar Mar 28 '21 10:03 aga7hokakological

The timing should be done locally in the broker IMHO, it should just keep track of when it received the last new message from this client. Less likely to break things, too

domenukk avatar Mar 28 '21 11:03 domenukk

For example, you can have a last_msg_time_for_client: Vec<Duration> vec, and after this line: https://github.com/AFLplusplus/LibAFL/blob/6e4c6a9b6b0db909853a19a68ba769bb2516f1bf/libafl/src/bolts/llmp.rs#L1356 check if you got new messages, then update the current client's time, else check if it timed out.

domenukk avatar Mar 28 '21 11:03 domenukk

Well the problem here is that I am only able to see 2 clients on my machines. Also whenever I am receiving new test cases they are only from client 0. In broker only client is there so the message is passed to broker and then to the other clients. Also I guess I need to use std::time because only core::time doesn't work as I need to have instace there to calculate the elapsed time. But I am not getting how to check for new message as it is a struct and not any other data type such as bool.

aga7hokakological avatar Apr 07 '21 17:04 aga7hokakological

You get as many clients as you spawn; if you run more than two clients, you should see more than two clients (?) else we have a bug somewhere... Yes, the time can only be std

domenukk avatar Apr 07 '21 19:04 domenukk

How should I spawn more than 2 clients? In any real-world scenario, you should use taskset to pin each client to an empty CPU core, the lib does not pick an empty core automatically (yet). as this line states that it should be done manually. But here, RUST_BACKTRACE=full taskset -c 1 ./.libfuzzer_test.elf 2>/dev/null taskset -c 1is binding one CPU. and I see no other option to bind cpu. If there is another way plz tell.

aga7hokakological avatar Apr 08 '21 12:04 aga7hokakological

But I am not getting how to check for new message as it is a struct and not any other data type such as bool.

Also this is quite confusing as it cannot be used directly and I think I'll have to write whole code again in the functions to check new message. or is using message_id fine of the llmp_msg cause then I can keep eye on message with the sender so that way it can be done.

aga7hokakological avatar Apr 08 '21 13:04 aga7hokakological

-c 1 is binding to CPU 1. Just use -c 2, -c 3, ... for other CPUs. The test script is just there for some quick tests, don't be afraid to run the binary manually. So, build the fuzzer and then run it from the target directory multiple times.

WRT new messages, inside handle_new_msgs, you know which client you are dealing with, and in this line: https://github.com/AFLplusplus/LibAFL/blob/6e4c6a9b6b0db909853a19a68ba769bb2516f1bf/libafl/src/bolts/llmp.rs#L1588 you know that a new message arrived, just add the current time to some datastructure if this is the case. Or prune the client, if None was returned. Hope this helps :)

domenukk avatar Apr 08 '21 13:04 domenukk

-c 1 is binding to CPU 1. Just use -c 2, -c 3, ... for other CPUs. The test script is just there for some quick tests, don't be afraid to run the binary manually.

I tried doing this and building but the clients number remain the same.

aga7hokakological avatar Apr 09 '21 14:04 aga7hokakological

Are you sure? Works for me. Maybe add some debug prints?

domenukk avatar Apr 10 '21 10:04 domenukk

Okay got it I had to run it multiple times.

aga7hokakological avatar Apr 10 '21 18:04 aga7hokakological

Can I get pull request access?

aga7hokakological avatar Apr 13 '21 15:04 aga7hokakological

@aga7hokakological gave you access, please push to a branch and open a pr to dev :)

domenukk avatar Apr 13 '21 15:04 domenukk

Hey @domenukk, sorry for disturbing again but target/release/example does not work for me. I have tried on arch-linux as well as on ubuntu. I was able to run once but then it is not working. I tried the way you told me to run it but It just freezes at this.

./libfuzzer_libpng Workdir: "/home/myname/Desktop/LibAFL/target/release/examples" [libafl/src/bolts/llmp.rs:409] "We're the broker" = "We're the broker" Doing broker things. Run this tool again to start fuzzing in a client.

Though I have written code but without testing I cannot be able to make a pull request. And with ./test.sh from fuzzer/libfuzzer_libpng it works but because it just spawns 2 clients I am not able to test it. I am trying to resolve it from my side if I am missing anything plz tell.

aga7hokakological avatar Apr 13 '21 19:04 aga7hokakological

Have you tried running it a second time, or multiple times? The first instance of the tool is the broker, the next ones then do the actual fuzzing.

domenukk avatar Apr 13 '21 19:04 domenukk

Yes, I have tried running it multiple times. Almost 10-15 times I tried running but it just freezes.

aga7hokakological avatar Apr 13 '21 19:04 aga7hokakological

It was giving problem because of this panic.

panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client!");

I solved it. Now working fine and clients are spawning perfectly.

aga7hokakological avatar Apr 14 '21 08:04 aga7hokakological

WRT new messages, inside handle_new_msgs, you know which client you are dealing with, and in this line:

https://github.com/AFLplusplus/LibAFL/blob/6e4c6a9b6b0db909853a19a68ba769bb2516f1bf/libafl/src/bolts/llmp.rs#L1588

you know that a new message arrived, just add the current time to some datastructure if this is the case. Or prune the client, if None was returned.

Here the function handle_new_msgs returns result. So I tried this code but because of the timeout is not perfectly working so it is unable to remove clients.

let mut last_time_msg_sent_for_client = HashMap::new(); compiler_fence(Ordering::SeqCst); for i in 0..self.llmp_clients.len() { let time = Instant::now(); if let Ok(_) = self.handle_new_msgs(i as u32, on_new_msg) { if time.elapsed().as_millis() < duration::new(0, 1).as_millis() { last_time_msg_sent_for_client.insert( self.llmp_out.id, SystemTime::now(), ); } } else { //remove the client if message not sent self.llmp_clients.retain(|client| client.id != i as u32); } }

maybe this line is not necessary

if time.elapsed().as_millis() < duration::new(0, 1).as_millis()

aga7hokakological avatar Apr 14 '21 08:04 aga7hokakological

It was giving problem because of this panic.

panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client!");

I solved it. Now working fine and clients are spawning perfectly.

How did you solve this?

s1341 avatar Apr 15 '21 06:04 s1341

Hey @s1341

How did you solve this?

This was the full error:

[libafl/src/bolts/llmp.rs:416] "We're the client" = "We're the client" [libafl/src/bolts/llmp.rs:416] e = Os { code: 98, kind: AddrInUse, message: "Address already in use", } Connected to port 1337 [libafl/src/events/llmp.rs:553] "Spawning next client (id {})" = "Spawning next client (id {})" [libafl/src/events/llmp.rs:553] ctr = 0 We're a client, let's fuzz :) First run. Let's set it all up We're a client, let's fuzz :) thread 'main' panicked at 'Failed to load initial corpus at ["./corpus"]: File(Os { code: 2, kind: Not Found, message: "No such file or directory" })', fuzzers/libfuzzer_libpng/./src/fuzzer.rs:176:14 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace thread 'main' panicked at 'Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client!', /home/aga7hokakological/Saurabh/LibAFL/libafl/src/events/llmp.rs:56 8:21 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

It was because I was running it from wrong directory, also there was error where it wasn't able to find libpng.tar.xz so I downloaded it manually and put it in the folder.

aga7hokakological avatar Apr 15 '21 08:04 aga7hokakological

WRT new messages, inside handle_new_msgs, you know which client you are dealing with, and in this line: https://github.com/AFLplusplus/LibAFL/blob/6e4c6a9b6b0db909853a19a68ba769bb2516f1bf/libafl/src/bolts/llmp.rs#L1588

you know that a new message arrived, just add the current time to some datastructure if this is the case. Or prune the client, if None was returned.

Here the function handle_new_msgs returns result. So I tried this code but because of the timeout is not perfectly working so it is unable to remove clients.

let mut last_time_msg_sent_for_client = HashMap::new(); compiler_fence(Ordering::SeqCst); for i in 0..self.llmp_clients.len() { let time = Instant::now(); if let Ok(_) = self.handle_new_msgs(i as u32, on_new_msg) { if time.elapsed().as_millis() < duration::new(0, 1).as_millis() { last_time_msg_sent_for_client.insert( self.llmp_out.id, SystemTime::now(), ); } } else { //remove the client if message not sent self.llmp_clients.retain(|client| client.id != i as u32); } }

maybe this line is not necessary

if time.elapsed().as_millis() < duration::new(0, 1).as_millis()

a) there is no need for a hashmap here, all clients are stored in a vec with fixed ids, so a vec will work here, too. b) you create a new map each time this function gets called. Instead, store the state in self so that it survives across calls.

domenukk avatar Apr 15 '21 08:04 domenukk

a) there is no need for a hashmap here, all clients are stored in a vec with fixed ids, so a vec will work here, too. b) you create a new map each time this function gets called. Instead, store the state in self so that it survives across calls.

I did but even if handle_new_msg is result the client.recv() is Option as I need some(msg) => msg but I cannot access it outside the function. But to say it contains Ok(()) of the handle_new_msg it means it has been handled so it should be pruned. My code ends as soon as I spawn clients.

aga7hokakological avatar Apr 19 '21 16:04 aga7hokakological

Sorry, I don't really understand what you try to do here? Is it roughly what I described in https://github.com/AFLplusplus/LibAFL/issues/36#issuecomment-808882592 ?

domenukk avatar Apr 19 '21 20:04 domenukk

I have declared another should_unregister variable in broker as bool. Also added this code with some changes (just checking if self.should_unregister is true or false) to above code to check if it is some or none in handle_new_msg below msg and it works but when it is none means message have arrived it means it should add time to the vec rather it removes the client. that's the only problem remaining. I mean it works but in opposite way.

if msg.as_ref().is_none() { self.should_unregister = true; } else { self.should_unregister = false; }

if I interchange the true/false then it removes when msg is none but that should not be the case right?

aga7hokakological avatar Apr 21 '21 18:04 aga7hokakological

Hello. I have made few changes to LlmpBroker struct to add Vec<Duration> so that the data is stored in self itself. For testing the changes, i built the libfuzzer_libpng example as mentioned. But the following make command is throwing an error. make CC="$(pwd)/../target/release/libafl_cc" CXX="$(pwd)/../target/release/libafl_cxx" -j ``nproc `

The error message is thread 'main' panicked at 'Failed to run the wrapped compiler: Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })', src/bin/libafl_cc.rs:29:14

I found that "$(pwd)/../target/release/libafl_cc" is a binary but not a directory. The exact path of binary is - fuzzers/libfuzzer_libpng/target/release/libafl_cc. As per the instructions, I think I am building it in the correct directory. - fuzzers/libfuzzer_libpng/libpng-1.6.37. Can you help me regarding this?

Srg213 avatar Jan 21 '23 19:01 Srg213

You can run cargo make run to get it working

tokatoka avatar Jan 26 '23 00:01 tokatoka

Actually I used that command first. Then to check where the build breaks, I followed instructions from README.md Yet I tried it again today. It is still showing the same error.

Srg213 avatar Jan 26 '23 08:01 Srg213