LibAFL icon indicating copy to clipboard operation
LibAFL copied to clipboard

fork() is not safe with tokio

Open wtdcode opened this issue 2 years ago • 5 comments

The launcher uses fork by default to start new children, however tokio is known to not working well with fork. I'm getting hangs for using Runtime created before fork() while the Runtime created after fork() seems working well.

https://github.com/AFLplusplus/LibAFL/blob/3625e881a3d7a838021046948fe710eab44ece69/libafl/src/events/launcher.rs#L189

Therefore fork() looks not that "general safe" in rust.

I want to open a discussion here for possible solutions while I haven't tried all:

  • Disable fork feature, meaning starting processes via command line: This works a bit, but only for fuzzers without initial setup because the code before fork will be executed multiple times.
  • Make sure Runtime are created after fork(): My current workaround, but seems no document.
  • Restrict the threads of Runtime to 1: Promissing workaround, no ducment either.

More context:

  • https://github.com/tokio-rs/tokio/issues/1541
  • https://stackoverflow.com/questions/60686516/why-cant-i-communicate-with-a-forked-child-process-using-tokio-unixstream

Note I have no idea about the safety with reqwest but from the documents reqwest seems maintaining a connection pool and internally uses tokio.

wtdcode avatar Sep 19 '23 16:09 wtdcode

First of all, the other threads die, and hence it deadlocks - but AFAIK these deadlocks are still considered safe in rust(?)

I don't know if using a single thread as tokio runtime will work, it may also spawn one second thread that would not survive forking.

In general I fear there is nothing we can do? It's a target-specific (or usage-specific) problem in this case, right? A lot of targets won't work with fork - for these you need to disable the fork feature, which is supported by LibAFL already.

Do you suggest to add documentation somewhere?

domenukk avatar Sep 20 '23 09:09 domenukk

I suggest either adding more documents or propagating unsafe to the launcher API like launch() instead of simply marking fork() as safe.

The root cause is that tokio don’t have handy function like gevent.reinit which setups everything again in a child. I will report this to tokio later.

wtdcode avatar Sep 20 '23 09:09 wtdcode

It's not unsafe - it's safe. It just deadlocks after fork, right? No memory corruption or similar

/edit: see https://doc.rust-lang.org/reference/behavior-not-considered-unsafe.html?highlight=deadloc#behavior-not-considered-unsafe

domenukk avatar Sep 20 '23 09:09 domenukk

It's not unsafe - it's safe. It just deadlocks after fork, right? No memory corruption or similar

/edit: see https://doc.rust-lang.org/reference/behavior-not-considered-unsafe.html?highlight=deadloc#behavior-not-considered-unsafe

Okay, make sense. I will report this to tokio.

Regarding documents, could you just add the link of this issue in launcher.rs before fork is called? I think that's pretty enough for users to understand the possible side effects.

wtdcode avatar Sep 20 '23 10:09 wtdcode

Feel free to open a docs PR :P It's an issue for all things that fork, like browsers, etc. It's kinda documented in 4) here: https://github.com/AFLplusplus/LibAFL/blob/7f0a4f1d7eb382c8e06ed14a5123a6eaab24bfa0/docs/src/message_passing/spawn_instances.md#automated-with-launcher

I agree it's pretty hidden, feel free to add more docu and send a PR along :)

domenukk avatar Sep 20 '23 10:09 domenukk

There is not much we can do here, closing for now.

domenukk avatar Mar 12 '24 23:03 domenukk