Skip to content

libafl_frida: Add tests for ASan for Unix platforms #1781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 11, 2024

Conversation

mkravchik
Copy link
Contributor

The PR contains a basic structure for running multiple tests and a few harness functions with different errors to be caught by ASan.
Apart from that, the draft fixes several errors found in Asan itself. Also, I disabled the stack increasing as is caused a crash on my Linux (the adjacent memory was in use). The code contains a workaround for a bug in frida-rust, that is fixed now, but the fix has not been released yet.

This is a WIP, as:

  1. Destroying Gum causes a segmentation fault; thus, a single test (e.g., heap-uaf-write) is supported using a static Gum object. Ideally, this should be fixed, and a new Gum instance would be created for each test.
  2. 70 identical errors are reported by Asan instead of a single one.

Your comments are welcome.

…1) destroying Gum causes segmentation fault and thus a single test is supported by using a static Gum object. Ideally, this should be fixed and a new Gum instance would be created for each test. 2) 70 identical errors are reported by Asan instead of a a single one. Apart from that, the draft fixes a number of errors found in Asan
#include <string>

extern "C" int heap_uaf_read(const uint8_t *_data, size_t _size) {
int *array = new int[100];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you purposely using c++ allocations instead of raw mallocs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing special; more tests should be added that will cover C. I just started from code present in some other example, and it used C++.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. So we should make sure to include versions of the tests which use the C api.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, are you certain your tests aren't being optimized out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added several malloc-based tests and -O0 to the compilation options to make sure the code is not optimized out. We still need a way to run all the tests. Until AsanRuntime cleans up when dropped this is not possible

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally tests should be run by cargo test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you run cargo test this one will run. It is already like that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW we can also add external tests in a Makefile.toml and run these with cargo make test (and CI supports it too).
Of course it's nice like it's now, but not everything may be possible that way, so mentioning it as well

Copy link
Contributor Author

@mkravchik mkravchik Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a brief look at proper AsanRuntime uninitialization and it indeed should be done in a separate PR. I prefer to complete this one to prevent more merges as time passes. So, can we resolve it as it is now? @s1341 I'll need your help with how to shut things down properly. I initially thought of keeping the helper around and creating a new executor for each test. This does not compile due to some dependencies between the lifetime of the harness and of the helper (which I can't figure out yet).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've figured this out, and now multiple tests are running. Please review

@domenukk
Copy link
Member

domenukk commented Jan 7, 2024

Left some comments about new dependencies - we try to keep these low. Feel free to ignore this until the PR is ready, then we can still clean it up :)
I also convert this to Draft for now (right?)

@domenukk domenukk marked this pull request as draft January 7, 2024 17:03
@mkravchik
Copy link
Contributor Author

Left some comments about new dependencies - we try to keep these low. Feel free to ignore this until the PR is ready, then we can still clean it up :) I also convert this to Draft for now (right?)

Where are these comments?

Copy link
Member

@domenukk domenukk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops forgot to publish the comments

#[test]
#[cfg(unix)]
fn run_test_asan() {
env_logger::init();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libafl_bolts has a SimpleStdoutLogger that you can use to get around this dependency here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ability to specify the trace level at runtime is very helpful. Does SimpleStdoutLogger support this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to dev-dependencies

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but for a test you don't usually see the logs anyway? For development purposes it's a different story, but I'd still prefer to not add crates to the library that are not needed in the normal case

Copy link
Member

@domenukk domenukk Jan 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I usually use something like this to enable trace in debug builds, but you could also make that read an env manually

    if cfg!(debug_assertions) {
        log::set_max_level(log::LevelFilter::Trace);
    } else {
        log::set_max_level(log::LevelFilter::Info);
    }
    SimpleStdoutLogger.set_logger().unwrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I indeed use this while developing the test, because it is quite complicated. I can remove this altogether or switch to SimpleStdoutLogger if it is important. However, I don't see it used anywhere as of now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've written the code using SimpleStdoutLogger, but the prints don't appear even though writing to stdout stream works...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's somewhat concerning, you did set the LeveFilter accordingly? Do rust tests close stdout or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the problem. In cargo test, the output of successfull tests is suppressed by default, both those sent to stdout and stderr. To see the output, run cargo test -- --nocapture. Pushed the code

// Build the test harness
// clang++ -shared -fPIC -o harness.so harness.cpp
#[cfg(unix)]
std::process::Command::new("clang++")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should potentially use the cc crate to have (some) support for other environments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find a way to build a shared library with cc. If you have an idea - please let me know

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc::build::new()
.flag("-shared")
.flag("-fPIC")

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this does not work. It produces a .a file and ignores the -shared. There is no linker running as well.
This is what I run:
cc::Build::new() .flag("-v") .flag("-shared") .flag("-fPIC") .flag("-O0") .file("test_harness.cpp") .compile("test_harness.so");
This is what it resulted in:
file /home/me/LibAFL/target/release/build/libafl_frida-3e26f9fa0e88b5c2/out/libtest_harness.so.a /home/me/LibAFL/target/release/build/libafl_frida-3e26f9fa0e88b5c2/out/libtest_harness.so.a: current ar archive

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, sad...

Alternatively maybe we can reuse the compiler detection from libafl_cc's build.rs here?

clangcpp = bindir_path.join("clang++");

Assuming clang is in the path may or may not work, I fear.
Or worst case ignore the testcase if clang wasn't found?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could copy the code over, but am not sure it is worth the effort for the current initial state where a single platform is supported by Asan (and therefore for its test). The compiler detection in libafl_cc is quite complicated, and for UNIX eventually just checks if llvm-config is in the path and then runs it. I guess that if llvm-config is in the path, clang++ will be there as well. I can start with checking for clang presence and skip compilation if not found, then the test will be skipped if the harness is not found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added checking for clang and failing the test if the harness not found

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit unfortunate cargo test can fail because of the PATH now, maybe we should make this optional somehow? Also, on MacOS the file extension would usually be dylib, right?
But anyway we can merge and then fix these things later

@domenukk
Copy link
Member

domenukk commented Jan 8, 2024

Sorry for nagging, this PR is very much appreciated btw :)

@mkravchik
Copy link
Contributor Author

If possible, I'd prefer to merge this without adding more features or placeholders for more platforms, as Asan doesn't support anything but Unix as of now. Postponing a merge of this just increases the size of the code to merge

@tokatoka
Copy link
Member

tokatoka commented Jan 9, 2024

can you run fix the clippy and run ./script/fmt_all.sh

@mkravchik
Copy link
Contributor Author

I ran fmt and merged main. But clippy has nothing to do with my changes; it complains about dependencies in libafl_bolts and libafl. As I wrote above, I'm unsure how that main is in such a state.

@mkravchik
Copy link
Contributor Author

mkravchik commented Jan 9, 2024

I ran fmt and merged main. But clippy has nothing to do with my changes; it complains about dependencies in libafl_bolts and libafl. As I wrote above, I'm unsure how comes that main is in such a state.

@tokatoka
Copy link
Member

tokatoka commented Jan 9, 2024

because clippy updates bring new lints.

@tokatoka
Copy link
Member

tokatoka commented Jan 9, 2024

can you fix it as suggested by clippy?

error: this expression creates a reference which is immediately dereferenced by the compiler
   --> libafl_frida/src/lib.rs:400:37
    |
400 |         let asan = AsanRuntime::new(&options);
    |                                     ^^^^^^^^ help: change this to: `options`

Also it's not formatted yet. you need to clang-format your test-harness.cpp

@mkravchik
Copy link
Contributor Author

I ran clippy many times, both nightly and stable, and I don't get this error. I also looked up the GitHub builds and don't see it. What command do you run to get it?

@tokatoka
Copy link
Member

tokatoka commented Jan 9, 2024

@tokatoka
Copy link
Member

tokatoka commented Jan 9, 2024

@mkravchik
Copy link
Contributor Author

Oh, crap. I ran scripts/clippy locally with the latest toolchain and hope this time it is ok

@domenukk
Copy link
Member

Aweoms, it's green!
So is this ready then?

@domenukk
Copy link
Member

All of the commented-out stuff is no longer required? Or will you bring it back?

@mkravchik
Copy link
Contributor Author

mkravchik commented Jan 10, 2024

I left it because it seemed that it might be useful for macos/ios when someone gets ASan working there.
From my POV - it is good to go. I also suggest the tests run as a mandatory part of CI.

@domenukk
Copy link
Member

How do you mean get asan working there? It was working IIRC? cc @fabianfreyer

@tokatoka tokatoka marked this pull request as ready for review January 10, 2024 13:52
@tokatoka
Copy link
Member

I also suggest the tests run as a mandatory part of CI.

cargo test already is a part of the CI steps

@domenukk domenukk changed the title First draft of a Asan tests. As of now, unix-only. This is a WIP. LibAFL_frida: Add tests for ASan for Unix platforms Jan 10, 2024
@domenukk domenukk changed the title LibAFL_frida: Add tests for ASan for Unix platforms libafl_frida: Add tests for ASan for Unix platforms Jan 10, 2024
@mkravchik
Copy link
Contributor Author

How do you mean get asan working there? It was working IIRC? cc @fabianfreyer

Maybe it's my misunderstanding, I indeed see some indications of this support, but have no way of testing

@mkravchik
Copy link
Contributor Author

So, will this be merged?

let shadow_mapping_start = map_to_shadow!(self, start);

let shadow_start = self.round_down_to_page(shadow_mapping_start);
// I'm not sure this works as planned. The same address appearing as start and end is mapped to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not about the code you did but do you know why create a new mappings here in this function?

after we find the suitable bit in init() then the shadow region is mapped and then we are good and ready to start fuzzing right?
I don't get the purpose of this function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok i understand now it's like a primitive malloc

@tokatoka tokatoka merged commit 6a72f8a into AFLplusplus:main Jan 11, 2024
@mkravchik mkravchik deleted the pr/libafl-frida-test branch January 11, 2024 12:27
@domenukk
Copy link
Member

I'm still uneasy about the removed parts for MacOS..

@domenukk
Copy link
Member

It definitely used to work. Can we make sure it still works? @tokatoka did you look at the comments?
mkravchik@c879a0a

@domenukk
Copy link
Member

Any functional reasons you removed the code or just refactoring?

@mkravchik
Copy link
Contributor Author

Yes - the code was crashing on linux. It tries to extend the stack beyond the originally allocated region. But in all cases I tested there was another allocated region adjacent to it so it was not possible just to stretch the stack over. Maybe on Mac it makes some sense, but I haven't work on Mac for some years and don't have one at hand to test. There were no comments to explain why this was necessary in the first place and it worked correctly with the original stack so I commented it out hoping that whoever wrote this will react.

@domenukk
Copy link
Member

Ok then, I guess we'll keep it like this and have to hope someone fixes MacOS again (don't have time myself)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants