diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ae73618122..621f174256 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -1,52 +1,46 @@ name: Build and Test -on: [push] +on: + push: + branches: [ main ] + pull_requests: + branches: [ main, dev ] env: CARGO_TERM_COLOR: always jobs: - default: + ubuntu: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Build + - name: Default Build run: cargo build --verbose - - name: Test + - name: Default Test run: cargo test --verbose - all-features: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - name: Build all features run: cd libafl && cargo build --all-features --verbose - name: Test all features run: cd libafl && cargo test --all-features --verbose - no-std: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - name: Build no_std run: cd libafl && cargo build --no-default-features --verbose - name: Test no_std run: cd libafl && cargo test --no-default-features --verbose - examples: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - name: Build examples run: cargo build --examples --verbose - fmt: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Format run: cargo fmt -- --check - docs: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Build Docs run: cargo doc - name: Test Docs - run: cargo test --doc \ No newline at end of file + run: cargo test --doc + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Windows Build + run: cargo build --verbose + - name: Windows Test + run: cargo test --verbose diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5357f69183..0000000000 --- a/LICENSE +++ /dev/null @@ -1,166 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000..f49a4e16e6 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000000..468cd79a8f --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 05acde12d3..0ff353378b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ Advanced Fuzzing Library - Slot your own fuzzers together and extend their featu LibAFL is written and maintained by Andrea Fioraldi and Dominik Maier . -It is released as Free and Open Source Software under the GNU Lesser General Public License V3. - - +It is released as Open Source Software under the [Apache v2](LICENSE-APACHE) or [MIT](LICENSE-MIT) licenses. ## Example usages diff --git a/TODO.md b/TODO.md index ac96a219af..c5a59e1a49 100644 --- a/TODO.md +++ b/TODO.md @@ -15,6 +15,7 @@ - [ ] Restart Count in Fuzzing Loop - [ ] LAIN / structured fuzzing example - [ ] Errors in the Fuzzer should exit the fuzz run +- [ ] More informative outpus, deeper introspection (stats, what mutation did x, etc.) - [ ] Timeouts for executors - [ ] Timeout handling for llmp clients (no ping for n seconds -> treat as disconnected) - [ ] LLMP Cross Machine Link (2 brokers connected via TCP) diff --git a/fuzzers/libfuzzer_libmozjpeg/build.rs b/fuzzers/libfuzzer_libmozjpeg/build.rs index ee0062f3e3..96f09a9db0 100644 --- a/fuzzers/libfuzzer_libmozjpeg/build.rs +++ b/fuzzers/libfuzzer_libmozjpeg/build.rs @@ -1,12 +1,19 @@ // build.rs -use std::env; -use std::path::Path; -use std::process::Command; +use std::{ + env, + path::Path, + process::{exit, Command}, +}; const LIBMOZJPEG_URL: &str = "https://github.com/mozilla/mozjpeg/archive/v4.0.3.tar.gz"; fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libmozjpeg example on Windows"); + exit(0); + } + let out_dir = env::var_os("OUT_DIR").unwrap(); let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let out_dir = out_dir.to_string_lossy().to_string(); diff --git a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs index 58ec07983a..ffbbcdfa78 100644 --- a/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libmozjpeg/src/fuzzer.rs @@ -3,6 +3,7 @@ use std::{env, path::PathBuf}; +#[cfg(unix)] use libafl::{ bolts::{shmem::UnixShMem, tuples::tuple_list}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler}, @@ -22,6 +23,7 @@ use libafl::{ }; /// We will interact with a C++ target, so use external c functionality +#[cfg(unix)] extern "C" { /// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32; @@ -35,6 +37,7 @@ extern "C" { } /// The wrapped harness function, calling out to the LLVM-style harness +#[cfg(unix)] fn harness(_executor: &E, buf: &[u8]) -> ExitKind where E: Executor, @@ -65,7 +68,14 @@ pub fn main() { .expect("An error occurred while fuzzing"); } +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz(_corpus_dirs: Vec, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + /// The actual fuzzer +#[cfg(unix)] fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted let stats = SimpleStats::new(|s| println!("{}", s)); @@ -76,10 +86,9 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> .expect("Failed to setup the restarter".into()); // Create an observation channel using the coverage map - let edges_observer = - StdMapObserver::new_from_ptr("edges", unsafe { __lafl_edges_map }, unsafe { - __lafl_max_edges_size as usize - }); + let edges_observer = unsafe { + StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize) + }; // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -119,7 +128,7 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> tuple_list!(edges_observer), &mut state, &mut restarting_mgr, - ); + )?; // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. diff --git a/fuzzers/libfuzzer_libpng/build.rs b/fuzzers/libfuzzer_libpng/build.rs index 1b270dbd71..49f3cfba94 100644 --- a/fuzzers/libfuzzer_libpng/build.rs +++ b/fuzzers/libfuzzer_libpng/build.rs @@ -1,13 +1,20 @@ // build.rs -use std::env; -use std::path::Path; -use std::process::Command; +use std::{ + env, + path::Path, + process::{exit, Command}, +}; const LIBPNG_URL: &str = "https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz"; fn main() { + if cfg!(windows) { + println!("cargo:warning=Skipping libpng example on Windows"); + exit(0); + } + let out_dir = env::var_os("OUT_DIR").unwrap(); let cwd = env::current_dir().unwrap().to_string_lossy().to_string(); let out_dir = out_dir.to_string_lossy().to_string(); diff --git a/fuzzers/libfuzzer_libpng/harness.cc b/fuzzers/libfuzzer_libpng/harness.cc index 15c204dc9f..65faff685d 100644 --- a/fuzzers/libfuzzer_libpng/harness.cc +++ b/fuzzers/libfuzzer_libpng/harness.cc @@ -158,7 +158,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (width && height > 100000000 / width) { PNG_CLEANUP #ifdef HAS_DUMMY_CRASH - asm("ud2"); #ifdef __aarch64__ asm volatile (".word 0xf7f0a000\n"); #else diff --git a/fuzzers/libfuzzer_libpng/src/fuzzer.rs b/fuzzers/libfuzzer_libpng/src/fuzzer.rs index 62845e7799..4640433c40 100644 --- a/fuzzers/libfuzzer_libpng/src/fuzzer.rs +++ b/fuzzers/libfuzzer_libpng/src/fuzzer.rs @@ -3,6 +3,7 @@ use std::{env, path::PathBuf}; +#[cfg(unix)] use libafl::{ bolts::{shmem::UnixShMem, tuples::tuple_list}, corpus::{ @@ -11,11 +12,11 @@ use libafl::{ }, events::setup_restarting_mgr, executors::{inprocess::InProcessExecutor, Executor, ExitKind}, - feedbacks::{CrashFeedback, MaxMapFeedback}, + feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, inputs::Input, mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, - observers::{HitcountsMapObserver, StdMapObserver}, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::mutational::StdMutationalStage, state::{HasCorpus, HasMetadata, State}, stats::SimpleStats, @@ -24,6 +25,7 @@ use libafl::{ }; /// We will interact with a C++ target, so use external c functionality +#[cfg(unix)] extern "C" { /// int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> i32; @@ -37,6 +39,7 @@ extern "C" { } /// The wrapped harness function, calling out to the LLVM-style harness +#[cfg(unix)] fn harness(_executor: &E, buf: &[u8]) -> ExitKind where E: Executor, @@ -67,22 +70,36 @@ pub fn main() { .expect("An error occurred while fuzzing"); } +/// Not supported on windows right now +#[cfg(windows)] +fn fuzz(_corpus_dirs: Vec, _objective_dir: PathBuf, _broker_port: u16) -> Result<(), ()> { + todo!("Example not supported on Windows"); +} + /// The actual fuzzer +#[cfg(unix)] fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> Result<(), Error> { // 'While the stats are state, they are usually used in the broker - which is likely never restarted let stats = SimpleStats::new(|s| println!("{}", s)); // The restarting state will spawn the same process again as child, then restarted it each time it crashes. let (state, mut restarting_mgr) = - setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) - .expect("Failed to setup the restarter".into()); + match setup_restarting_mgr::<_, _, UnixShMem, _>(stats, broker_port) { + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; // Create an observation channel using the coverage map - let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( - "edges", - unsafe { __lafl_edges_map }, - unsafe { __lafl_max_edges_size as usize }, - )); + let edges_observer = HitcountsMapObserver::new(unsafe { + StdMapObserver::new_from_ptr("edges", __lafl_edges_map, __lafl_max_edges_size as usize) + }); // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -92,11 +109,10 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> // Corpus that will be evolved, we keep it in memory for performance InMemoryCorpus::new(), // Feedbacks to rate the interestingness of an input - tuple_list!(MaxMapFeedback::new_with_observer_track( - &edges_observer, - true, - false - )), + tuple_list!( + MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), + TimeFeedback::new() + ), // Corpus in which we store solutions (crashes in this example), // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(objective_dir).unwrap(), @@ -130,10 +146,10 @@ fn fuzz(corpus_dirs: Vec, objective_dir: PathBuf, broker_port: u16) -> let mut executor = InProcessExecutor::new( "in-process(edges)", harness, - tuple_list!(edges_observer), + tuple_list!(edges_observer, TimeObserver::new("time")), &mut state, &mut restarting_mgr, - ); + )?; // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. diff --git a/fuzzers/libfuzzer_runtime/rt.c b/fuzzers/libfuzzer_runtime/rt.c index 90f4fea89a..ec90be0d7e 100644 --- a/fuzzers/libfuzzer_runtime/rt.c +++ b/fuzzers/libfuzzer_runtime/rt.c @@ -1,5 +1,7 @@ #include #include +#include +#include #define MAP_SIZE 65536 @@ -8,9 +10,11 @@ char **orig_argv; char **orig_envp; uint8_t __lafl_dummy_map[MAP_SIZE]; +size_t __lafl_dummy_map_usize[MAP_SIZE]; uint8_t *__lafl_edges_map = __lafl_dummy_map; uint8_t *__lafl_cmp_map = __lafl_dummy_map; +size_t *__lafl_alloc_map = __lafl_dummy_map_usize; uint32_t __lafl_max_edges_size = 0; @@ -127,6 +131,32 @@ void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) { } +void *malloc(size_t size) { + + uintptr_t k = (uintptr_t)__builtin_return_address(0); + k = (k >> 4) ^ (k << 8); + k &= MAP_SIZE - 1; + __lafl_alloc_map[k] = MAX(__lafl_alloc_map[k], size); + + return realloc(NULL, size); + +} + +void *calloc(size_t nmemb, size_t size) { + + size *= nmemb; + + uintptr_t k = (uintptr_t)__builtin_return_address(0); + k = (k >> 4) ^ (k << 8); + k &= MAP_SIZE - 1; + __lafl_alloc_map[k] = MAX(__lafl_alloc_map[k], size); + + void *result = realloc(NULL, size); + memset(result, 0, size); + return result; + +} + static void afl_libfuzzer_copy_args(int argc, char** argv, char** envp) { orig_argc = argc; orig_argv = argv; diff --git a/fuzzers/qemufuzzer/src/lib.rs b/fuzzers/qemufuzzer/src/lib.rs index 8ed917e4b0..4413e0f67e 100644 --- a/fuzzers/qemufuzzer/src/lib.rs +++ b/fuzzers/qemufuzzer/src/lib.rs @@ -72,7 +72,7 @@ pub extern "C" fn fuzz_main_loop() { }); let edges_feedback = MaxMapFeedback::new_with_observer(&NAME_COV_MAP, &edges_observer); - let executor = InProcessExecutor::new("QEMUFuzzer", harness, tuple_list!(edges_observer)); + let executor = InProcessExecutor::new("QEMUFuzzer", harness, tuple_list!(edges_observer))?; let mut state = State::new(tuple_list!(edges_feedback)); let mut engine = Engine::new(executor); diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 7c7f9f5e00..01a78fc319 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -54,9 +54,11 @@ ctor = "*" libafl_derive = { version = "*", optional = true, path = "../libafl_derive" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } # an easy way to debug print SerdeAnyMap #TODO: for llmp brotli = { version = "3.3.0", default-features = false } # brotli compression +num_enum = "0.5.1" [target.'cfg(unix)'.dependencies] libc = "0.2" # For (*nix) libc +nix = "0.20.0" [target.'cfg(windows)'.dependencies] windows = "0.3.1" diff --git a/libafl/examples/llmp_test/main.rs b/libafl/examples/llmp_test/main.rs index 139030a833..4dadc2ebf4 100644 --- a/libafl/examples/llmp_test/main.rs +++ b/libafl/examples/llmp_test/main.rs @@ -3,17 +3,21 @@ This shows how llmp can be used directly, without libafl abstractions */ extern crate alloc; +#[cfg(all(unix, feature = "std"))] use core::{convert::TryInto, time::Duration}; +#[cfg(all(unix, feature = "std"))] use std::{thread, time}; +#[cfg(all(unix, feature = "std"))] use libafl::{ bolts::{llmp, shmem::UnixShMem}, Error, }; -const TAG_SIMPLE_U32_V1: u32 = 0x51300321; -const TAG_MATH_RESULT_V1: u32 = 0x77474331; +const _TAG_SIMPLE_U32_V1: u32 = 0x51300321; +const _TAG_MATH_RESULT_V1: u32 = 0x77474331; +#[cfg(all(unix, feature = "std"))] fn adder_loop(port: u16) -> ! { let mut client = llmp::LlmpClient::::create_attach_to_tcp(port).unwrap(); let mut last_result: u32 = 0; @@ -27,7 +31,7 @@ fn adder_loop(port: u16) -> ! { }; msg_counter += 1; match tag { - TAG_SIMPLE_U32_V1 => { + _TAG_SIMPLE_U32_V1 => { current_result = current_result.wrapping_add(u32::from_le_bytes(buf.try_into().unwrap())); } @@ -42,7 +46,7 @@ fn adder_loop(port: u16) -> ! { ); client - .send_buf(TAG_MATH_RESULT_V1, ¤t_result.to_le_bytes()) + .send_buf(_TAG_MATH_RESULT_V1, ¤t_result.to_le_bytes()) .unwrap(); last_result = current_result; } @@ -51,13 +55,14 @@ fn adder_loop(port: u16) -> ! { } } +#[cfg(all(unix, feature = "std"))] fn broker_message_hook( client_id: u32, tag: llmp::Tag, message: &[u8], ) -> Result { match tag { - TAG_SIMPLE_U32_V1 => { + _TAG_SIMPLE_U32_V1 => { println!( "Client {:?} sent message: {:?}", client_id, @@ -65,7 +70,7 @@ fn broker_message_hook( ); Ok(llmp::LlmpMsgHookResult::ForwardToClients) } - TAG_MATH_RESULT_V1 => { + _TAG_MATH_RESULT_V1 => { println!( "Adder Client has this current result: {:?}", u32::from_le_bytes(message.try_into().unwrap()) @@ -79,6 +84,12 @@ fn broker_message_hook( } } +#[cfg(not(unix))] +fn main() { + todo!("LLMP is not yet supported on this platform."); +} + +#[cfg(unix)] fn main() { /* The main node has a broker, and a few worker threads */ @@ -96,9 +107,9 @@ fn main() { "broker" => { let mut broker = llmp::LlmpBroker::::new().unwrap(); broker - .launch_tcp_listener( + .launch_listener(llmp::Listener::Tcp( std::net::TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(), - ) + )) .unwrap(); broker.loop_forever(&mut broker_message_hook, Some(Duration::from_millis(5))) } @@ -108,7 +119,7 @@ fn main() { loop { counter = counter.wrapping_add(1); client - .send_buf(TAG_SIMPLE_U32_V1, &counter.to_le_bytes()) + .send_buf(_TAG_SIMPLE_U32_V1, &counter.to_le_bytes()) .unwrap(); println!("CTR Client writing {}", counter); thread::sleep(Duration::from_secs(1)) diff --git a/libafl/src/bolts/bindings.rs b/libafl/src/bolts/bindings.rs index 02f7aa456c..a3fa41413e 100644 --- a/libafl/src/bolts/bindings.rs +++ b/libafl/src/bolts/bindings.rs @@ -1,2 +1,2 @@ -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] ::windows::include_bindings!(); diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index 0055354f10..96cee0bbc6 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -52,7 +52,7 @@ Then register some clientloops using llmp_broker_register_threaded_clientloop */ -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::{ cmp::max, fmt::Debug, @@ -64,14 +64,42 @@ use core::{ use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::{ - env, + env, fs, io::{Read, Write}, - net::{TcpListener, TcpStream}, + net::{SocketAddr, TcpListener, TcpStream}, thread, }; -use super::shmem::{ShMem, ShMemDescription}; -use crate::Error; +#[cfg(all(feature = "std", unix))] +use nix::{ + cmsg_space, + sys::{ + socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags}, + uio::IoVec, + }, +}; + +#[cfg(all(feature = "std", unix))] +use std::{ + ffi::CStr, + os::unix::{ + self, + net::{UnixListener, UnixStream}, + {io::AsRawFd, prelude::RawFd}, + }, +}; + +#[cfg(all(feature = "std", unix))] +use libc::c_char; + +#[cfg(unix)] +use crate::bolts::os::unix_signals::{c_void, setup_signal_handler, siginfo_t, Handler, Signal}; +use crate::{ + bolts::shmem::{ShMem, ShMemDescription}, + Error, +}; + +use super::shmem::HasFd; /// We'll start off with 256 megabyte maps per fuzzer client const LLMP_PREF_INITIAL_MAP_SIZE: usize = 1 << 28; @@ -101,9 +129,54 @@ const EOP_MSG_SIZE: usize = /// The header length of a llmp page in a shared map (until messages start) const LLMP_PAGE_HEADER_LEN: usize = size_of::(); +/// The llmp broker registers a signal handler for cleanups on `SIGINT`. +#[cfg(unix)] +static mut GLOBAL_SIGHANDLER_STATE: LlmpBrokerSignalHandler = LlmpBrokerSignalHandler { + shutting_down: false, +}; + /// TAGs used thorughout llmp pub type Tag = u32; +/// Abstraction for listeners +#[cfg(feature = "std")] +pub enum Listener { + Tcp(TcpListener), + #[cfg(unix)] + Unix(UnixListener), +} + +#[cfg(feature = "std")] +pub enum ListenerStream { + Tcp(TcpStream, SocketAddr), + #[cfg(unix)] + Unix(UnixStream, unix::net::SocketAddr), + Empty(), +} + +#[cfg(feature = "std")] +impl Listener { + fn accept(&self) -> ListenerStream { + match self { + Listener::Tcp(inner) => match inner.accept() { + Ok(res) => ListenerStream::Tcp(res.0, res.1), + Err(err) => { + dbg!("Ignoring failed accept", err); + ListenerStream::Empty() + } + }, + #[cfg(unix)] + Listener::Unix(inner) => match inner.accept() { + Ok(res) => ListenerStream::Unix(res.0, res.1), + Err(err) => { + dbg!("Ignoring failed accept", err); + ListenerStream::Empty() + } + }, + } + } +} + /// Get sharedmem from a page #[inline] unsafe fn shmem2page_mut(afl_shmem: &mut SH) -> *mut LlmpPage { @@ -120,8 +193,8 @@ unsafe fn shmem2page(afl_shmem: &SH) -> *const LlmpPage { #[inline] unsafe fn llmp_msg_in_page(page: *const LlmpPage, msg: *const LlmpMsg) -> bool { /* DBG("llmp_msg_in_page %p within %p-%p\n", msg, page, page + page->size_total); */ - return (page as *const u8) < msg as *const u8 - && (page as *const u8).offset((*page).size_total as isize) > msg as *const u8; + (page as *const u8) < msg as *const u8 + && (page as *const u8).add((*page).size_total) > msg as *const u8 } /// allign to LLMP_PREF_ALIGNNMENT=64 bytes @@ -198,9 +271,9 @@ unsafe fn llmp_next_msg_ptr_checked( ) -> Result<*mut LlmpMsg, Error> { let page = map.page_mut(); let map_size = map.shmem.map().len(); - let msg_begin_min = (page as *const u8).offset(size_of::() as isize); + let msg_begin_min = (page as *const u8).add(size_of::()); // We still need space for this msg (alloc_size). - let msg_begin_max = (page as *const u8).offset((map_size - alloc_size) as isize); + let msg_begin_max = (page as *const u8).add(map_size - alloc_size); let next = _llmp_next_msg_ptr(last_msg); let next_ptr = next as *const u8; if next_ptr >= msg_begin_min && next_ptr <= msg_begin_max { @@ -217,9 +290,9 @@ unsafe fn llmp_next_msg_ptr_checked( #[inline] unsafe fn _llmp_next_msg_ptr(last_msg: *const LlmpMsg) -> *mut LlmpMsg { /* DBG("_llmp_next_msg_ptr %p %lu + %lu\n", last_msg, last_msg->buf_len_padded, sizeof(llmp_message)); */ - return (last_msg as *mut u8) - .offset(size_of::() as isize) - .offset((*last_msg).buf_len_padded as isize) as *mut LlmpMsg; + (last_msg as *mut u8) + .add(size_of::()) + .add((*last_msg).buf_len_padded as usize) as *mut LlmpMsg } /// Description of a shared map. @@ -262,6 +335,7 @@ pub struct LlmpMsg { /// The message we receive impl LlmpMsg { /// Gets the buffer from this message as slice, with the corrent length. + /// # Safety /// This is unsafe if somebody has access to shared mem pages on the system. pub unsafe fn as_slice_unsafe(&self) -> &[u8] { slice::from_raw_parts(self.buf.as_ptr(), self.buf_len as usize) @@ -285,14 +359,13 @@ impl LlmpMsg { unsafe { let map_size = map.shmem.map().len(); let buf_ptr = self.buf.as_ptr(); - if buf_ptr > (map.page_mut() as *const u8).offset(size_of::() as isize) + if buf_ptr > (map.page_mut() as *const u8).add(size_of::()) && buf_ptr - <= (map.page_mut() as *const u8) - .offset((map_size - size_of::() as usize) as isize) + <= (map.page_mut() as *const u8).add(map_size - size_of::() as usize) { // The message header is in the page. Continue with checking the body. let len = self.buf_len_padded as usize + size_of::(); - buf_ptr <= (map.page_mut() as *const u8).offset((map_size - len) as isize) + buf_ptr <= (map.page_mut() as *const u8).add(map_size - len) } else { false } @@ -324,7 +397,7 @@ where // We got the port. We are the broker! :) dbg!("We're the broker"); let mut broker = LlmpBroker::new()?; - let _listener_thread = broker.launch_tcp_listener(listener)?; + let _listener_thread = broker.launch_listener(Listener::Tcp(listener))?; Ok(LlmpConnection::IsBroker { broker }) } Err(e) => { @@ -368,6 +441,36 @@ where } } +impl LlmpConnection +where + SH: ShMem + HasFd, +{ + #[cfg(all(feature = "std", unix))] + pub fn on_domain_socket(filename: &str) -> Result { + match UnixListener::bind(filename) { + Ok(listener) => { + dbg!("We're the broker"); + let mut broker = LlmpBroker::new()?; + broker.socket_name = Some(filename.to_string()); + let _listener_thread = broker.launch_listener(Listener::Unix(listener))?; + Ok(LlmpConnection::IsBroker { broker }) + } + Err(e) => { + match e.kind() { + std::io::ErrorKind::AddrInUse => { + // We are the client :) + dbg!("We're the client", e); + Ok(LlmpConnection::IsClient { + client: LlmpClient::create_attach_to_unix(filename)?, + }) + } + _ => Err(Error::File(e)), + } + } + } + } +} + /// Contents of the share mem pages, used by llmp internally #[derive(Copy, Clone, Debug)] #[repr(C, packed)] @@ -448,6 +551,8 @@ where /// Completely reset the current sender map. /// Afterwards, no receiver should read from it at a different location. /// This is only useful if all connected llmp parties start over, for example after a crash. + /// # Safety + /// Only safe if you really really restart the page on everything connected pub unsafe fn reset(&mut self) { _llmp_page_init(&mut self.out_maps.last_mut().unwrap().shmem, self.id, true); self.last_msg_sent = ptr::null_mut(); @@ -640,7 +745,7 @@ where panic!("Allocated new message without calling send() inbetween. ret: {:?}, page: {:?}, complete_msg_size: {:?}, size_used: {:?}, last_msg: {:?}", ret, page, buf_len_padded, (*page).size_used, last_msg); } - (*page).size_used = (*page).size_used + complete_msg_size; + (*page).size_used += complete_msg_size; (*ret).buf_len_padded = buf_len_padded as u64; (*ret).buf_len = buf_len as u64; /* DBG("Returning new message at %p with len %ld, TAG was %x", ret, ret->buf_len_padded, ret->tag); */ @@ -714,16 +819,17 @@ where } /// Allocates the next space on this sender page - pub unsafe fn alloc_next(&mut self, buf_len: usize) -> Result<*mut LlmpMsg, Error> { - match self.alloc_next_if_space(buf_len) { - Some(msg) => return Ok(msg), - _ => (), + pub fn alloc_next(&mut self, buf_len: usize) -> Result<*mut LlmpMsg, Error> { + if let Some(msg) = unsafe { self.alloc_next_if_space(buf_len) } { + return Ok(msg); }; /* no more space left! We'll have to start a new page */ - self.handle_out_eop()?; + unsafe { + self.handle_out_eop()?; + } - match self.alloc_next_if_space(buf_len) { + match unsafe { self.alloc_next_if_space(buf_len) } { Some(msg) => Ok(msg), None => Err(Error::Unknown(format!( "Error allocating {} bytes in shmap", @@ -733,6 +839,8 @@ where } /// Cancel send of the next message, this allows us to allocate a new message without sending this one. + /// # Safety + /// They msg pointer may no longer be used after `cancel_send` pub unsafe fn cancel_send(&mut self, msg: *mut LlmpMsg) { /* DBG("Client %d cancels send of msg at %p with tag 0x%X and size %ld", client->id, msg, msg->tag, * msg->buf_len_padded); */ @@ -769,7 +877,7 @@ where let last_message_offset = if self.last_msg_sent.is_null() { None } else { - Some(map.msg_to_offset(self.last_msg_sent)?) + Some(unsafe { map.msg_to_offset(self.last_msg_sent) }?) }; Ok(LlmpDescription { shmem: map.shmem.description(), @@ -872,61 +980,59 @@ where }; // Let's see what we go here. - match ret { - Some(msg) => { - if !(*msg).in_map(&mut self.current_recv_map) { - return Err(Error::IllegalState("Unexpected message in map (out of map bounds) - bugy client or tampered shared map detedted!".into())); - } - // Handle special, LLMP internal, messages. - match (*msg).tag { - LLMP_TAG_UNSET => panic!("BUG: Read unallocated msg"), - LLMP_TAG_END_OF_PAGE => { - #[cfg(feature = "std")] - dbg!("Got end of page, allocing next"); - // Handle end of page - if (*msg).buf_len < size_of::() as u64 { - panic!( - "Illegal message length for EOP (is {}, expected {})", - (*msg).buf_len_padded, - size_of::() - ); - } - let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; - - /* We can reuse the map mem space, no need to free and calloc. - However, the pageinfo points to the map we're about to unmap. - Clone the contents first to be safe (probably fine in rust eitner way). */ - let pageinfo_cpy = (*pageinfo).clone(); - - // Mark the old page save to unmap, in case we didn't so earlier. - ptr::write_volatile(&mut (*page).save_to_unmap, 1); - // Map the new page. The old one should be unmapped by Drop - self.current_recv_map = - LlmpSharedMap::existing(SH::existing_from_shm_slice( - &pageinfo_cpy.shm_str, - pageinfo_cpy.map_size, - )?); - // Mark the new page save to unmap also (it's mapped by us, the broker now) - ptr::write_volatile(&mut (*page).save_to_unmap, 1); - - #[cfg(feature = "std")] - dbg!("Got a new recv map", self.current_recv_map.shmem.shm_str()); - // After we mapped the new page, return the next message, if available - return self.recv(); - } - _ => (), - } - - // Store the last msg for next time - self.last_msg_recvd = msg; + if let Some(msg) = ret { + if !(*msg).in_map(&mut self.current_recv_map) { + return Err(Error::IllegalState("Unexpected message in map (out of map bounds) - bugy client or tampered shared map detedted!".into())); } - _ => (), + // Handle special, LLMP internal, messages. + match (*msg).tag { + LLMP_TAG_UNSET => panic!("BUG: Read unallocated msg"), + LLMP_TAG_END_OF_PAGE => { + #[cfg(feature = "std")] + dbg!("Got end of page, allocing next"); + // Handle end of page + if (*msg).buf_len < size_of::() as u64 { + panic!( + "Illegal message length for EOP (is {}, expected {})", + (*msg).buf_len_padded, + size_of::() + ); + } + let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; + + /* We can reuse the map mem space, no need to free and calloc. + However, the pageinfo points to the map we're about to unmap. + Clone the contents first to be safe (probably fine in rust eitner way). */ + let pageinfo_cpy = (*pageinfo).clone(); + + // Mark the old page save to unmap, in case we didn't so earlier. + ptr::write_volatile(&mut (*page).save_to_unmap, 1); + // Map the new page. The old one should be unmapped by Drop + self.current_recv_map = LlmpSharedMap::existing(SH::existing_from_shm_slice( + &pageinfo_cpy.shm_str, + pageinfo_cpy.map_size, + )?); + // Mark the new page save to unmap also (it's mapped by us, the broker now) + ptr::write_volatile(&mut (*page).save_to_unmap, 1); + + #[cfg(feature = "std")] + dbg!("Got a new recv map", self.current_recv_map.shmem.shm_str()); + // After we mapped the new page, return the next message, if available + return self.recv(); + } + _ => (), + } + + // Store the last msg for next time + self.last_msg_recvd = msg; }; Ok(ret) } /// Blocks/spins until the next message gets posted to the page, /// then returns that message. + /// # Safety + /// Returns a raw ptr, on the recv map. Should be safe in general pub unsafe fn recv_blocking(&mut self) -> Result<*mut LlmpMsg, Error> { let mut current_msg_id = 0; let page = self.current_recv_map.page_mut(); @@ -982,7 +1088,7 @@ where let last_message_offset = if self.last_msg_recvd.is_null() { None } else { - Some(map.msg_to_offset(self.last_msg_recvd)?) + Some(unsafe { map.msg_to_offset(self.last_msg_recvd) }?) }; Ok(LlmpDescription { shmem: map.shmem.description(), @@ -1047,29 +1153,33 @@ where } /// Get the unsafe ptr to this page, situated on the shared map + /// # Safety + /// The unsafe page pointer is obviously unsafe. pub unsafe fn page_mut(&mut self) -> *mut LlmpPage { shmem2page_mut(&mut self.shmem) } /// Get the unsafe ptr to this page, situated on the shared map + /// # Safety + /// The unsafe page pointer is obviously unsafe. pub unsafe fn page(&self) -> *const LlmpPage { shmem2page(&self.shmem) } /// Gets the offset of a message on this here page. /// Will return IllegalArgument error if msg is not on page. - pub fn msg_to_offset(&self, msg: *const LlmpMsg) -> Result { - unsafe { - let page = self.page(); - if llmp_msg_in_page(page, msg) { - // Cast both sides to u8 arrays, get the offset, then cast the return isize to u64 - Ok((msg as *const u8).offset_from((*page).messages.as_ptr() as *const u8) as u64) - } else { - Err(Error::IllegalArgument(format!( - "Message (0x{:X}) not in page (0x{:X})", - page as u64, msg as u64 - ))) - } + /// # Safety + /// This dereferences msg, make sure to pass a proper pointer to it. + pub unsafe fn msg_to_offset(&self, msg: *const LlmpMsg) -> Result { + let page = self.page(); + if llmp_msg_in_page(page, msg) { + // Cast both sides to u8 arrays, get the offset, then cast the return isize to u64 + Ok((msg as *const u8).offset_from((*page).messages.as_ptr() as *const u8) as u64) + } else { + Err(Error::IllegalArgument(format!( + "Message (0x{:X}) not in page (0x{:X})", + page as u64, msg as u64 + ))) } } @@ -1092,7 +1202,7 @@ where } else { env::set_var( &format!("{}_OFFSET", map_env_name), - format!("{}", self.msg_to_offset(msg)?), + format!("{}", unsafe { self.msg_to_offset(msg) }?), ) }; Ok(()) @@ -1131,6 +1241,27 @@ where /// This allows us to intercept messages right in the broker /// This keeps the out map clean. pub llmp_clients: Vec>, + /// This is the socket name, when unix domain sockets are used. + socket_name: Option, + /// This flag is used to indicate that shutdown has been requested by the SIGINT and SIGTERM + /// handlers + shutting_down: bool, +} + +#[cfg(unix)] +pub struct LlmpBrokerSignalHandler { + shutting_down: bool, +} + +#[cfg(all(unix))] +impl Handler for LlmpBrokerSignalHandler { + fn handle(&mut self, _signal: Signal, _info: siginfo_t, _void: c_void) { + unsafe { ptr::write_volatile(&mut self.shutting_down, true) }; + } + + fn signals(&self) -> Vec { + vec![Signal::SigTerm, Signal::SigInterrupt, Signal::SigQuit] + } } /// The broker forwards all messages to its own bus-like broadcast map. @@ -1151,6 +1282,8 @@ where keep_pages_forever: true, }, llmp_clients: vec![], + socket_name: None, + shutting_down: false, }; Ok(broker) @@ -1210,23 +1343,43 @@ where Ok(()) } + /// Internal function, returns true when shuttdown is requested by a `SIGINT` signal + #[inline] + #[cfg(unix)] + fn is_shutting_down(&self) -> bool { + unsafe { ptr::read_volatile(&GLOBAL_SIGHANDLER_STATE.shutting_down) } + } + + /// Always returns true on platforms, where no shutdown signal handlers are supported + #[inline] + #[cfg(not(unix))] + fn is_shutting_down(&self) -> bool { + false + } + /// Loops infinitely, forwarding and handling all incoming messages from clients. /// Never returns. Panics on error. /// 5 millis of sleep can't hurt to keep busywait not at 100% - pub fn loop_forever(&mut self, on_new_msg: &mut F, sleep_time: Option) -> ! + pub fn loop_forever(&mut self, on_new_msg: &mut F, sleep_time: Option) where F: FnMut(u32, Tag, &[u8]) -> Result, { - loop { + #[cfg(unix)] + if let Err(_e) = unsafe { setup_signal_handler(&mut GLOBAL_SIGHANDLER_STATE) } { + // We can live without a proper ctrl+c signal handler. Print and ignore. + #[cfg(feature = "std")] + println!("Failed to setup signal handlers: {}", _e); + } + + while !self.is_shutting_down() { compiler_fence(Ordering::SeqCst); self.once(on_new_msg) .expect("An error occurred when brokering. Exiting."); #[cfg(feature = "std")] - match sleep_time { - Some(time) => thread::sleep(time), - None => (), - } + if let Some(time) = sleep_time { + thread::sleep(time) + }; #[cfg(not(feature = "std"))] match sleep_time { @@ -1250,15 +1403,12 @@ where let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?; // accept connections and process them, spawning a new thread for each one println!("Server listening on port {}", port); - return self.launch_tcp_listener(listener); + self.launch_listener(Listener::Tcp(listener)) } #[cfg(feature = "std")] - /// Launches a thread using a tcp listener socket, on which new clients may connect to this broker - pub fn launch_tcp_listener( - &mut self, - listener: TcpListener, - ) -> Result, Error> { + /// Launches a thread using a listener socket, on which new clients may connect to this broker + pub fn launch_listener(&mut self, listener: Listener) -> Result, Error> { // Later in the execution, after the initial map filled up, // the current broacast map will will point to a different map. // However, the original map is (as of now) never freed, new clients will start @@ -1290,43 +1440,109 @@ where }; loop { - let (mut stream, addr) = match listener.accept() { - Ok(res) => res, - Err(e) => { - dbg!("Ignoring failed accept", e); - continue; + match listener.accept() { + ListenerStream::Tcp(mut stream, addr) => { + dbg!("New connection", addr, stream.peer_addr().unwrap()); + match stream.write(&broadcast_str_initial) { + Ok(_) => {} // fire & forget + Err(e) => { + dbg!("Could not send to shmap to client", e); + continue; + } + }; + let mut new_client_map_str: [u8; 20] = Default::default(); + match stream.read_exact(&mut new_client_map_str) { + Ok(()) => (), + Err(e) => { + dbg!("Ignoring failed read from client", e); + continue; + } + }; + unsafe { + let msg = new_client_sender + .alloc_next(size_of::()) + .expect("Could not allocate a new message in shared map."); + (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; + let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; + (*pageinfo).shm_str = new_client_map_str; + (*pageinfo).map_size = LLMP_PREF_INITIAL_MAP_SIZE; + match new_client_sender.send(msg) { + Ok(()) => (), + Err(e) => println!("Error forwarding client on map: {:?}", e), + }; + } } - }; - dbg!("New connection", addr, stream.peer_addr().unwrap()); - match stream.write(&broadcast_str_initial) { - Ok(_) => {} // fire & forget - Err(e) => { - dbg!("Could not send to shmap to client", e); - continue; - } - }; - let mut new_client_map_str: [u8; 20] = Default::default(); - match stream.read_exact(&mut new_client_map_str) { - Ok(()) => (), - Err(e) => { - dbg!("Ignoring failed read from client", e); - continue; - } - }; + #[cfg(unix)] + ListenerStream::Unix(stream, addr) => unsafe { + dbg!("New connection", addr); - unsafe { - let msg = new_client_sender - .alloc_next(size_of::()) - .expect("Could not allocate a new message in shared map."); - (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; - let pageinfo = (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; - (*pageinfo).shm_str = new_client_map_str; - (*pageinfo).map_size = LLMP_PREF_INITIAL_MAP_SIZE; - match new_client_sender.send(msg) { - Ok(()) => (), - Err(e) => println!("Error forwarding client on map: {:?}", e), - }; - } + let broadcast_fd_initial: i32 = + CStr::from_ptr(broadcast_str_initial.as_ptr() as *const c_char) + .to_string_lossy() + .into_owned() + .parse() + .expect(&format!( + "ShmId is not a valid int file descriptor: {:?}", + broadcast_str_initial + )); + + match sendmsg( + stream.as_raw_fd(), + &[IoVec::from_slice(b"\x00")], + &[ControlMessage::ScmRights(&[broadcast_fd_initial])], + MsgFlags::empty(), + None, + ) { + Ok(_) => {} + Err(err) => { + dbg!("Error sending fd over stream: {}", err); + continue; + } + }; + + let mut buf = [0u8; 5]; + let mut cmsgspace = cmsg_space!([RawFd; 1]); + let msg = recvmsg( + stream.as_raw_fd(), + &[IoVec::from_mut_slice(&mut buf[..])], + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); + + for cmsg in msg.cmsgs() { + if let ControlMessageOwned::ScmRights(fds) = cmsg { + for fd in fds { + let mut fdstr = [0u8; 20]; + match write!(&mut fdstr[..], "{}", fd) { + Ok(_) => {} + Err(_) => { + dbg!("error converting fd to string"); + } + } + + let msg = new_client_sender + .alloc_next(size_of::()) + .expect("Could not allocate a new message in shared map."); + (*msg).tag = LLMP_TAG_NEW_SHM_CLIENT; + let pageinfo = + (*msg).buf.as_mut_ptr() as *mut LlmpPayloadSharedMapInfo; + (*pageinfo).shm_str = fdstr; + (*pageinfo).map_size = LLMP_PREF_INITIAL_MAP_SIZE; + match new_client_sender.send(msg) { + Ok(()) => (), + Err(e) => { + println!("Error forwarding client on map: {:?}", e) + } + }; + } + } + } + }, + ListenerStream::Empty() => { + continue; + } + }; } })) } @@ -1398,10 +1614,9 @@ where let map = &mut self.llmp_clients[client_id as usize].current_recv_map; let msg_buf = (*msg).as_slice(map)?; - match (on_new_msg)(client_id, (*msg).tag, msg_buf)? { - LlmpMsgHookResult::Handled => should_forward_msg = false, - _ => (), - } + if let LlmpMsgHookResult::Handled = (on_new_msg)(client_id, (*msg).tag, msg_buf)? { + should_forward_msg = false + }; if should_forward_msg { self.forward_msg(msg)?; } @@ -1410,6 +1625,24 @@ where } } +#[cfg(feature = "std")] +impl Drop for LlmpBroker +where + SH: ShMem, +{ + fn drop(&mut self) { + match &self.socket_name { + Some(name) => match fs::remove_file(&name) { + Ok(_) => {} + Err(err) => { + dbg!("failed to close socket: {}", err); + } + }, + None => {} + } + } +} + /// A restorable client description #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct LlmpClientDescription { @@ -1521,6 +1754,8 @@ where } /// Commits a msg to the client's out map + /// # Safety + /// Needs to be called with a proper msg pointer pub unsafe fn send(&mut self, msg: *mut LlmpMsg) -> Result<(), Error> { self.sender.send(msg) } @@ -1551,6 +1786,8 @@ where /// A client receives a broadcast message. /// Returns null if no message is availiable + /// # Safety + /// Should be save, unless the internal state is corrupt. Returns raw ptr. #[inline] pub unsafe fn recv(&mut self) -> Result, Error> { self.receiver.recv() @@ -1558,6 +1795,8 @@ where /// A client blocks/spins until the next message gets posted to the page, /// then returns that message. + /// # Safety + /// Should be save, unless the internal state is corrupt. Returns raw ptr. #[inline] pub unsafe fn recv_blocking(&mut self) -> Result<*mut LlmpMsg, Error> { self.receiver.recv_blocking() @@ -1565,6 +1804,8 @@ where /// The current page could have changed in recv (EOP) /// Alloc the next message, internally handling end of page by allocating a new one. + /// # Safety + /// Should be safe, but returns an unsafe ptr #[inline] pub unsafe fn alloc_next(&mut self, buf_len: usize) -> Result<*mut LlmpMsg, Error> { self.sender.alloc_next(buf_len) @@ -1602,28 +1843,93 @@ where LLMP_PREF_INITIAL_MAP_SIZE, )?))?; - stream.write(ret.sender.out_maps.first().unwrap().shmem.shm_slice())?; + stream.write_all(ret.sender.out_maps.first().unwrap().shmem.shm_slice())?; Ok(ret) } } +/// `n` clients connect to a broker. They share an outgoing map with the broker, +/// and get incoming messages from the shared broker bus +/// If the Shm has a fd, we can attach to it. +impl LlmpClient +where + SH: ShMem + HasFd, +{ + #[cfg(all(unix, feature = "std"))] + /// Create a LlmpClient, getting the ID from a given filename + pub fn create_attach_to_unix(filename: &str) -> Result { + let stream = UnixStream::connect(filename)?; + println!("Connected to socket {}", filename); + + let mut buf = [0u8; 5]; + let mut cmsgspace = cmsg_space!([RawFd; 1]); + let msg = recvmsg( + stream.as_raw_fd(), + &[IoVec::from_mut_slice(&mut buf[..])], + Some(&mut cmsgspace), + MsgFlags::empty(), + ) + .unwrap(); + + for cmsg in msg.cmsgs() { + if let ControlMessageOwned::ScmRights(fds) = cmsg { + for fd in fds { + let mut fdstr = [0u8; 20]; + match write!(&mut fdstr[..], "{}", fd) { + Ok(_) => {} + Err(_) => { + dbg!("error converting fd to string"); + } + } + + let ret = Self::new(LlmpSharedMap::existing(SH::existing_from_shm_slice( + &fdstr, + LLMP_PREF_INITIAL_MAP_SIZE, + )?))?; + + match sendmsg( + stream.as_raw_fd(), + &[IoVec::from_slice(b"\x00")], + &[ControlMessage::ScmRights(&[ret + .sender + .out_maps + .first() + .unwrap() + .shmem + .shm_id()])], + MsgFlags::empty(), + None, + ) { + Ok(_) => {} + Err(err) => { + dbg!("Error sending fd over stream {}", err); + continue; + } + }; + return Ok(ret); + } + } + } + + panic!("Didn't receive a file descriptor from the broker!"); + } +} + #[cfg(test)] +#[cfg(all(unix, feature = "std"))] mod tests { - #[cfg(feature = "std")] use std::{thread::sleep, time::Duration}; - #[cfg(feature = "std")] use super::{ LlmpClient, LlmpConnection::{self, IsBroker, IsClient}, LlmpMsgHookResult::ForwardToClients, Tag, }; - #[cfg(feature = "std")] + use crate::bolts::shmem::UnixShMem; - #[cfg(feature = "std")] #[test] pub fn llmp_connection() { let mut broker = match LlmpConnection::::on_port(1337).unwrap() { diff --git a/libafl/src/bolts/mod.rs b/libafl/src/bolts/mod.rs index f73268ce7c..86c761710b 100644 --- a/libafl/src/bolts/mod.rs +++ b/libafl/src/bolts/mod.rs @@ -2,6 +2,7 @@ pub mod bindings; pub mod llmp; +pub mod os; pub mod ownedref; pub mod serdeany; pub mod shmem; diff --git a/libafl/src/bolts/os/mod.rs b/libafl/src/bolts/os/mod.rs new file mode 100644 index 0000000000..0a1b95601f --- /dev/null +++ b/libafl/src/bolts/os/mod.rs @@ -0,0 +1,2 @@ +#[cfg(unix)] +pub mod unix_signals; diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs new file mode 100644 index 0000000000..737cf533fa --- /dev/null +++ b/libafl/src/bolts/os/unix_signals.rs @@ -0,0 +1,175 @@ +use alloc::vec::Vec; +use core::{ + cell::UnsafeCell, + convert::TryFrom, + fmt::{self, Display, Formatter}, + mem, ptr, + ptr::write_volatile, + sync::atomic::{compiler_fence, Ordering}, +}; + +#[cfg(feature = "std")] +use std::ffi::CString; + +use libc::{ + c_int, malloc, sigaction, sigaltstack, sigemptyset, stack_t, SA_NODEFER, SA_ONSTACK, + SA_SIGINFO, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, + SIGQUIT, SIGSEGV, SIGTERM, SIGUSR2, +}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +use crate::Error; + +pub use libc::{c_void, siginfo_t}; + +#[derive(IntoPrimitive, TryFromPrimitive, Hash, Clone, Copy)] +#[repr(i32)] +pub enum Signal { + SigAbort = SIGABRT, + SigBus = SIGBUS, + SigFloatingPointException = SIGFPE, + SigIllegalInstruction = SIGILL, + SigPipe = SIGPIPE, + SigSegmentationFault = SIGSEGV, + SigUser2 = SIGUSR2, + SigAlarm = SIGALRM, + SigHangUp = SIGHUP, + SigKill = SIGKILL, + SigQuit = SIGQUIT, + SigTerm = SIGTERM, + SigInterrupt = SIGINT, +} + +pub static CRASH_SIGNALS: &[Signal] = &[ + Signal::SigAbort, + Signal::SigBus, + Signal::SigFloatingPointException, + Signal::SigIllegalInstruction, + Signal::SigPipe, + Signal::SigSegmentationFault, +]; + +impl PartialEq for Signal { + fn eq(&self, other: &Self) -> bool { + *self as i32 == *other as i32 + } +} + +impl Eq for Signal {} + +unsafe impl Sync for Signal {} + +impl Display for Signal { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + Signal::SigAbort => write!(f, "SIGABRT")?, + Signal::SigBus => write!(f, "SIGBUS")?, + Signal::SigFloatingPointException => write!(f, "SIGFPE")?, + Signal::SigIllegalInstruction => write!(f, "SIGILL")?, + Signal::SigPipe => write!(f, "SIGPIPE")?, + Signal::SigSegmentationFault => write!(f, "SIGSEGV")?, + Signal::SigUser2 => write!(f, "SIGUSR2")?, + Signal::SigAlarm => write!(f, "SIGALRM")?, + Signal::SigHangUp => write!(f, "SIGHUP")?, + Signal::SigKill => write!(f, "SIGKILL")?, + Signal::SigQuit => write!(f, "SIGQUIT")?, + Signal::SigTerm => write!(f, "SIGTERM")?, + Signal::SigInterrupt => write!(f, "SIGINT")?, + }; + + Ok(()) + } +} + +pub trait Handler { + /// Handle a signal + fn handle(&mut self, signal: Signal, info: siginfo_t, _void: c_void); + /// Return a list of signals to handle + fn signals(&self) -> Vec; +} + +struct HandlerHolder { + handler: UnsafeCell<*mut dyn Handler>, +} + +unsafe impl Send for HandlerHolder {} + +/// Let's get 8 mb for now. +const SIGNAL_STACK_SIZE: usize = 2 << 22; +/// To be able to handle SIGSEGV when the stack is exhausted, we need our own little stack space. +static mut SIGNAL_STACK_PTR: *mut c_void = ptr::null_mut(); + +/// Keep track of which handler is registered for which signal +static mut SIGNAL_HANDLERS: [Option; 32] = [ + // We cannot use [None; 32] because it requires Copy. Ugly, but I don't think there's an + // alternative. + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, +]; + +/// Internal function that is being called whenever a signal we are registered for arrives. +/// # Safety +/// This should be somewhat safe to call for signals previously registered, +/// unless the signal handlers registered using [setup_signal_handler] are broken. +unsafe fn handle_signal(sig: c_int, info: siginfo_t, void: c_void) { + let signal = &Signal::try_from(sig).unwrap(); + let handler = { + match &SIGNAL_HANDLERS[*signal as usize] { + Some(handler_holder) => &mut **handler_holder.handler.get(), + None => return, + } + }; + handler.handle(*signal, info, void); +} + +/// Setup signal handlers in a somewhat rusty way. +/// This will allocate a signal stack and set the signal handlers accordingly. +/// It is, for example, used in the [crate::executors::InProcessExecutor] to restart the fuzzer in case of a crash, +/// or to handle `SIGINT` in the broker process. +/// # Safety +/// The signal handlers will be called on any signal. They should (tm) be async safe. +/// A lot can go south in signal handling. Be sure you know what you are doing. +pub unsafe fn setup_signal_handler(handler: &mut T) -> Result<(), Error> { + // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) + if SIGNAL_STACK_PTR.is_null() { + SIGNAL_STACK_PTR = malloc(SIGNAL_STACK_SIZE); + + if SIGNAL_STACK_PTR.is_null() { + // Rust always panics on OOM, so we will, too. + panic!( + "Failed to allocate signal stack with {} bytes!", + SIGNAL_STACK_SIZE + ); + } + } + let mut ss: stack_t = mem::zeroed(); + ss.ss_size = SIGNAL_STACK_SIZE; + ss.ss_sp = SIGNAL_STACK_PTR; + sigaltstack(&mut ss as *mut stack_t, ptr::null_mut() as _); + + let mut sa: sigaction = mem::zeroed(); + sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); + sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sa.sa_sigaction = handle_signal as usize; + let signals = handler.signals(); + for sig in signals { + write_volatile( + &mut SIGNAL_HANDLERS[sig as usize], + Some(HandlerHolder { + handler: UnsafeCell::new(handler as *mut dyn Handler), + }), + ); + + if sigaction(sig as i32, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { + #[cfg(feature = "std")] + { + let err_str = CString::new(format!("Failed to setup {} handler", sig)).unwrap(); + libc::perror(err_str.as_ptr()); + } + return Err(Error::Unknown(format!("Could not set up {} handler", sig))); + } + } + compiler_fence(Ordering::SeqCst); + + Ok(()) +} diff --git a/libafl/src/bolts/ownedref.rs b/libafl/src/bolts/ownedref.rs index 82b6bef9ab..968b7bc4ce 100644 --- a/libafl/src/bolts/ownedref.rs +++ b/libafl/src/bolts/ownedref.rs @@ -31,8 +31,8 @@ where } } -impl<'a, T: Sized> Ptr<'a, T> { - pub fn as_ref(&self) -> &T { +impl<'a, T: Sized> AsRef for Ptr<'a, T> { + fn as_ref(&self) -> &T { match self { Ptr::Ref(r) => r, Ptr::Owned(v) => v.as_ref(), @@ -69,15 +69,17 @@ where } } -impl<'a, T: Sized> PtrMut<'a, T> { - pub fn as_ref(&self) -> &T { +impl<'a, T: Sized> AsRef for PtrMut<'a, T> { + fn as_ref(&self) -> &T { match self { PtrMut::Ref(r) => r, PtrMut::Owned(v) => v.as_ref(), } } +} - pub fn as_mut(&mut self) -> &T { +impl<'a, T: Sized> AsMut for PtrMut<'a, T> { + fn as_mut(&mut self) -> &mut T { match self { PtrMut::Ref(r) => r, PtrMut::Owned(v) => v.as_mut(), @@ -195,8 +197,8 @@ where } } -impl Cptr { - pub fn as_ref(&self) -> &T { +impl AsRef for Cptr { + fn as_ref(&self) -> &T { match self { Cptr::Cptr(p) => unsafe { p.as_ref().unwrap() }, Cptr::Owned(v) => v.as_ref(), @@ -230,15 +232,17 @@ where } } -impl CptrMut { - pub fn as_ref(&self) -> &T { +impl AsRef for CptrMut { + fn as_ref(&self) -> &T { match self { CptrMut::Cptr(p) => unsafe { p.as_ref().unwrap() }, CptrMut::Owned(b) => b.as_ref(), } } +} - pub fn as_mut(&mut self) -> &mut T { +impl AsMut for CptrMut { + fn as_mut(&mut self) -> &mut T { match self { CptrMut::Cptr(p) => unsafe { p.as_mut().unwrap() }, CptrMut::Owned(b) => b.as_mut(), diff --git a/libafl/src/bolts/serdeany.rs b/libafl/src/bolts/serdeany.rs index 1d0062b36f..01a2a2e85f 100644 --- a/libafl/src/bolts/serdeany.rs +++ b/libafl/src/bolts/serdeany.rs @@ -71,7 +71,6 @@ macro_rules! create_serde_registry_for_trait { pub mod $mod_name { use alloc::boxed::Box; - use alloc::string::String; use core::any::{Any, TypeId}; use core::fmt; use postcard; @@ -106,7 +105,7 @@ macro_rules! create_serde_registry_for_trait { .get(&id) .expect("Cannot deserialize an unregistered type") }; - let seed = DeserializeCallbackSeed:: { cb: cb }; + let seed = DeserializeCallbackSeed:: { cb }; let obj: Self::Value = visitor.next_element_seed(seed)?.unwrap(); Ok(obj) } @@ -126,8 +125,7 @@ macro_rules! create_serde_registry_for_trait { panic!("Registry is already finalized!"); } - let deserializers = - self.deserializers.get_or_insert_with(|| HashMap::default()); + let deserializers = self.deserializers.get_or_insert_with(HashMap::default); deserializers.insert(unpack_type_id(TypeId::of::()), |de| { Ok(Box::new(erased_serde::deserialize::(de)?)) }); @@ -260,7 +258,7 @@ macro_rules! create_serde_registry_for_trait { impl NamedSerdeAnyMap { #[inline] - pub fn get(&self, name: &String) -> Option<&T> + pub fn get(&self, name: &str) -> Option<&T> where T: Any, { @@ -273,11 +271,7 @@ macro_rules! create_serde_registry_for_trait { } #[inline] - pub fn by_typeid( - &self, - name: &String, - typeid: &TypeId, - ) -> Option<&dyn $trait_name> { + pub fn by_typeid(&self, name: &str, typeid: &TypeId) -> Option<&dyn $trait_name> { match self.map.get(&unpack_type_id(*typeid)) { None => None, Some(h) => h @@ -287,7 +281,7 @@ macro_rules! create_serde_registry_for_trait { } #[inline] - pub fn get_mut(&mut self, name: &String) -> Option<&mut T> + pub fn get_mut(&mut self, name: &str) -> Option<&mut T> where T: Any, { @@ -302,7 +296,7 @@ macro_rules! create_serde_registry_for_trait { #[inline] pub fn by_typeid_mut( &mut self, - name: &String, + name: &str, typeid: &TypeId, ) -> Option<&mut dyn $trait_name> { match self.map.get_mut(&unpack_type_id(*typeid)) { @@ -423,7 +417,7 @@ macro_rules! create_serde_registry_for_trait { } #[inline] - pub fn insert(&mut self, val: Box, name: &String) { + pub fn insert(&mut self, val: Box, name: &str) { let id = unpack_type_id((*val).type_id()); if !self.map.contains_key(&id) { self.map.insert(id, HashMap::default()); @@ -448,7 +442,7 @@ macro_rules! create_serde_registry_for_trait { } #[inline] - pub fn contains(&self, name: &String) -> bool + pub fn contains(&self, name: &str) -> bool where T: Any, { diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index f6edf33ff8..4803654d43 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -1,12 +1,10 @@ //! A generic sharememory region to be used by any functions (queues or feedbacks // too.) -#[cfg(feature = "std")] -#[cfg(unix)] -pub use shmem::UnixShMem; +#[cfg(all(feature = "std", unix))] +pub use unix_shmem::UnixShMem; -#[cfg(feature = "std")] -#[cfg(windows)] +#[cfg(all(windows, feature = "std"))] pub use shmem::Win32ShMem; use alloc::string::{String, ToString}; @@ -71,7 +69,7 @@ pub trait ShMem: Sized + Debug { fn description(&self) -> ShMemDescription { ShMemDescription { size: self.map().len(), - str_bytes: self.shm_slice().clone(), + str_bytes: *self.shm_slice(), } } @@ -99,17 +97,26 @@ pub trait ShMem: Sized + Debug { } } -#[cfg(unix)] -#[cfg(feature = "std")] -pub mod shmem { +/// shared maps that have an id can use this trait +pub trait HasFd { + /// Retrieve the id of this shared map + fn shm_id(&self) -> i32; +} - use core::{mem::size_of, slice}; +#[cfg(all(unix, feature = "std"))] +pub mod unix_shmem { + + use core::{mem::size_of, ptr, slice}; use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_ushort, c_void}; + #[cfg(target_os = "android")] + use libc::{off_t, size_t, MAP_SHARED, O_RDWR, PROT_READ, PROT_WRITE}; use std::ffi::CStr; + #[cfg(target_os = "android")] + use std::ffi::CString; use crate::Error; - use super::ShMem; + use super::{HasFd, ShMem}; #[cfg(unix)] extern "C" { @@ -117,12 +124,119 @@ pub mod shmem { fn snprintf(_: *mut c_char, _: c_ulong, _: *const c_char, _: ...) -> c_int; #[cfg(feature = "std")] fn strncpy(_: *mut c_char, _: *const c_char, _: c_ulong) -> *mut c_char; - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(target_os = "android")))] fn shmctl(__shmid: c_int, __cmd: c_int, __buf: *mut shmid_ds) -> c_int; - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(target_os = "android")))] fn shmget(__key: c_int, __size: c_ulong, __shmflg: c_int) -> c_int; - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(target_os = "android")))] fn shmat(__shmid: c_int, __shmaddr: *const c_void, __shmflg: c_int) -> *mut c_void; + #[cfg(all(feature = "std", target_os = "android"))] + fn ioctl(fd: c_int, request: c_long, ...) -> c_int; + #[cfg(all(feature = "std", target_os = "android"))] + fn open(path: *const c_char, oflag: c_int, ...) -> c_int; + #[cfg(all(feature = "std", target_os = "android"))] + fn close(fd: c_int) -> c_int; + #[cfg(all(feature = "std", target_os = "android"))] + fn mmap( + addr: *mut c_void, + len: size_t, + prot: c_int, + flags: c_int, + fd: c_int, + offset: off_t, + ) -> *mut c_void; + + } + + #[cfg(target_os = "android")] + #[derive(Copy, Clone)] + #[repr(C)] + struct ashmem_pin { + pub offset: c_uint, + pub len: c_uint, + } + + #[cfg(target_os = "android")] + const ASHMEM_GET_SIZE: c_long = 0x00007704; + #[cfg(target_os = "android")] + const ASHMEM_UNPIN: c_long = 0x40087708; + #[cfg(target_os = "android")] + const ASHMEM_SET_NAME: c_long = 0x41007701; + #[cfg(target_os = "android")] + const ASHMEM_SET_SIZE: c_long = 0x40087703; + #[cfg(target_os = "android")] + const ASHMEM_DEVICE: &str = "/dev/ashmem"; + + #[cfg(target_os = "android")] + unsafe fn shmctl(__shmid: c_int, __cmd: c_int, _buf: *mut shmid_ds) -> c_int { + println!("shmctl(__shmid: {})", __shmid); + if __cmd == 0 { + let length = ioctl(__shmid, ASHMEM_GET_SIZE); + + let ap = ashmem_pin { + offset: 0, + len: length as u32, + }; + + let ret = ioctl(__shmid, ASHMEM_UNPIN, &ap); + close(__shmid); + ret + } else { + 0 + } + } + + #[cfg(target_os = "android")] + unsafe fn shmget(__key: c_int, __size: c_ulong, __shmflg: c_int) -> c_int { + let path = CString::new(ASHMEM_DEVICE).expect("CString::new failed!"); + let fd = open(path.as_ptr(), O_RDWR); + + let mut ourkey: [c_char; 20] = [0; 20]; + snprintf( + ourkey.as_mut_ptr() as *mut c_char, + size_of::<[c_char; 20]>() as c_ulong, + b"%d\x00" as *const u8 as *const c_char, + __key, + ); + + println!("ourkey: {:?}", ourkey); + if ioctl(fd, ASHMEM_SET_NAME, &ourkey) != 0 { + close(fd); + return 0; + }; + + if ioctl(fd, ASHMEM_SET_SIZE, __size) != 0 { + close(fd); + return 0; + }; + + println!("shmget returns {}", fd); + fd + } + + #[cfg(target_os = "android")] + unsafe fn shmat(__shmid: c_int, __shmaddr: *const c_void, __shmflg: c_int) -> *mut c_void { + println!("shmat(__shmid: {})", __shmid); + + let size = ioctl(__shmid, ASHMEM_GET_SIZE); + if size < 0 { + return 0 as *mut c_void; + } + + let ptr = mmap( + 0 as *mut c_void, + size as usize, + PROT_READ | PROT_WRITE, + MAP_SHARED, + __shmid, + 0, + ); + if ptr == usize::MAX as *mut c_void { + return 0 as *mut c_void; + } + + println!("shmat() = {:?}", ptr); + ptr } #[cfg(unix)] @@ -197,6 +311,12 @@ pub mod shmem { } } + impl HasFd for UnixShMem { + fn shm_id(&self) -> i32 { + self.shm_id + } + } + /// Deinit sharedmaps on drop impl Drop for UnixShMem { fn drop(&mut self) { @@ -222,7 +342,7 @@ pub mod shmem { pub fn from_str(shm_str: &CStr, map_size: usize) -> Result { let mut ret = afl_shmem_unitialized(); let map = unsafe { afl_shmem_by_str(&mut ret, shm_str, map_size) }; - if map != 0 as *mut u8 { + if !map.is_null() { Ok(ret) } else { Err(Error::Unknown(format!( @@ -235,7 +355,7 @@ pub mod shmem { pub fn new(map_size: usize) -> Result { let mut ret = afl_shmem_unitialized(); let map = unsafe { afl_shmem_init(&mut ret, map_size) }; - if map != 0 as *mut u8 { + if !map.is_null() { Ok(ret) } else { Err(Error::Unknown(format!( @@ -253,24 +373,24 @@ pub mod shmem { // Not set or not initialized; return; } - (*shm).shm_str[0 as usize] = '\u{0}' as u8; - shmctl((*shm).shm_id, 0 as c_int, 0 as *mut shmid_ds); - (*shm).map = 0 as *mut c_uchar; + (*shm).shm_str[0 as usize] = 0u8; + shmctl((*shm).shm_id, 0 as c_int, ptr::null_mut()); + (*shm).map = ptr::null_mut(); } /// Functions to create Shared memory region, for observation channels and /// opening inputs and stuff. unsafe fn afl_shmem_init(shm: *mut UnixShMem, map_size: usize) -> *mut c_uchar { (*shm).map_size = map_size; - (*shm).map = 0 as *mut c_uchar; + (*shm).map = ptr::null_mut(); (*shm).shm_id = shmget( 0 as c_int, map_size as c_ulong, 0o1000 as c_int | 0o2000 as c_int | 0o600 as c_int, ); if (*shm).shm_id < 0 as c_int { - (*shm).shm_str[0] = '\u{0}' as u8; - return 0 as *mut c_uchar; + (*shm).shm_str[0] = 0u8; + return ptr::null_mut(); } snprintf( (*shm).shm_str.as_mut_ptr() as *mut c_char, @@ -280,15 +400,15 @@ pub mod shmem { ); (*shm).shm_str [(size_of::<[c_char; 20]>() as c_ulong).wrapping_sub(1 as c_int as c_ulong) as usize] = - '\u{0}' as u8; - (*shm).map = shmat((*shm).shm_id, 0 as *const c_void, 0 as c_int) as *mut c_uchar; + 0u8; + (*shm).map = shmat((*shm).shm_id, ptr::null(), 0 as c_int) as *mut c_uchar; if (*shm).map == -(1 as c_int) as *mut c_void as *mut c_uchar || (*shm).map.is_null() { - shmctl((*shm).shm_id, 0 as c_int, 0 as *mut shmid_ds); + shmctl((*shm).shm_id, 0 as c_int, ptr::null_mut()); (*shm).shm_id = -(1 as c_int); - (*shm).shm_str[0 as c_int as usize] = '\u{0}' as u8; - return 0 as *mut c_uchar; + (*shm).shm_str[0 as c_int as usize] = 0u8; + return ptr::null_mut(); } - return (*shm).map; + (*shm).map } /// Uses a shmap id string to open a shared map @@ -297,10 +417,10 @@ pub mod shmem { shm_str: &CStr, map_size: usize, ) -> *mut c_uchar { - if shm.is_null() || shm_str.to_bytes().len() == 0 || map_size == 0 { - return 0 as *mut c_uchar; + if shm.is_null() || shm_str.to_bytes().is_empty() || map_size == 0 { + return ptr::null_mut(); } - (*shm).map = 0 as *mut c_uchar; + (*shm).map = ptr::null_mut(); (*shm).map_size = map_size; strncpy( (*shm).shm_str.as_mut_ptr() as *mut c_char, @@ -309,35 +429,30 @@ pub mod shmem { ); (*shm).shm_id = shm_str .to_str() - .expect(&format!("illegal shm_str {:?}", shm_str)) + .unwrap_or_else(|_| panic!("illegal shm_str {:?}", shm_str)) .parse::() .unwrap(); - (*shm).map = shmat((*shm).shm_id, 0 as *const c_void, 0 as c_int) as *mut c_uchar; + (*shm).map = shmat((*shm).shm_id, ptr::null(), 0 as c_int) as *mut c_uchar; if (*shm).map == -(1 as c_int) as *mut c_void as *mut c_uchar { - (*shm).map = 0 as *mut c_uchar; + (*shm).map = ptr::null_mut(); (*shm).map_size = 0; - (*shm).shm_str[0] = '\u{0}' as u8; - return 0 as *mut c_uchar; + (*shm).shm_str[0] = 0u8; + return ptr::null_mut(); } - return (*shm).map; + (*shm).map } } -#[cfg(windows)] -#[cfg(feature = "std")] +#[cfg(all(feature = "std", windows))] pub mod shmem { - use core::{mem::size_of, slice}; - use std::ffi::CStr; - - use super::ShMem; - use crate::Error; + //TODO use super::ShMem; /// The default Sharedmap impl for windows using shmctl & shmget #[derive(Clone, Debug)] pub struct Win32ShMem { pub filename: [u8; 64], - pub handle: windows::win32::system_services::HANDLE, + //TODO pub handle: windows::win32::system_services::HANDLE, pub map: *mut u8, pub map_size: usize, } @@ -348,10 +463,10 @@ pub mod shmem { #[cfg(test)] mod tests { - #[cfg(feature = "std")] + #[cfg(all(unix, feature = "std"))] use super::{ShMem, UnixShMem}; - #[cfg(feature = "std")] + #[cfg(all(unix, feature = "std"))] #[test] fn test_str_conversions() { let mut shm_str: [u8; 20] = [0; 20]; diff --git a/libafl/src/bolts/tuples.rs b/libafl/src/bolts/tuples.rs index 162bea12ce..c44f74102d 100644 --- a/libafl/src/bolts/tuples.rs +++ b/libafl/src/bolts/tuples.rs @@ -6,6 +6,9 @@ use core::any::TypeId; pub trait HasLen { fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } } impl HasLen for () { @@ -65,12 +68,8 @@ pub trait MatchType { } impl MatchType for () { - fn match_type(&self, _f: fn(t: &T)) { - () - } - fn match_type_mut(&mut self, _f: fn(t: &mut T)) { - () - } + fn match_type(&self, _f: fn(t: &T)) {} + fn match_type_mut(&mut self, _f: fn(t: &mut T)) {} } impl MatchType for (Head, Tail) @@ -179,7 +178,7 @@ where fn append(self, value: T) -> Self::AppendResult { let (head, tail) = self; - return (head, tail.append(value)); + (head, tail.append(value)) } } diff --git a/libafl/src/corpus/minimizer.rs b/libafl/src/corpus/minimizer.rs index 42c37d9d84..0520d98734 100644 --- a/libafl/src/corpus/minimizer.rs +++ b/libafl/src/corpus/minimizer.rs @@ -37,6 +37,12 @@ impl TopRatedsMetadata { } } +impl Default for TopRatedsMetadata { + fn default() -> Self { + Self::new() + } +} + pub trait FavFactor where I: Input, @@ -56,6 +62,7 @@ where I: Input + HasLen, { fn compute(entry: &mut Testcase) -> Result { + // TODO maybe enforce entry.exec_time().is_some() Ok(entry.exec_time().map_or(1, |d| d.as_millis()) as u64 * entry.cached_len()? as u64) } } @@ -208,7 +215,7 @@ where pub fn new(base: CS) -> Self { Self { - base: base, + base, skip_not_fav_prob: DEFAULT_SKIP_NOT_FAV_PROB, phantom: PhantomData, } @@ -216,8 +223,8 @@ where pub fn with_skip_prob(base: CS, skip_not_fav_prob: u64) -> Self { Self { - base: base, - skip_not_fav_prob: skip_not_fav_prob, + base, + skip_not_fav_prob, phantom: PhantomData, } } diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 6b2fc082d5..3f48ffa0c7 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -39,6 +39,11 @@ where /// Returns the number of elements fn count(&self) -> usize; + /// Returns true, if no elements are in this corpus yet + fn is_empty(&self) -> bool { + self.count() == 0 + } + /// Add an entry to the corpus and return its index fn add(&mut self, testcase: Testcase) -> Result; @@ -128,6 +133,7 @@ where I: Input, R: Rand, { + /// Create a new RandCorpusScheduler that just schedules randomly. pub fn new() -> Self { Self { phantom: PhantomData, @@ -135,4 +141,16 @@ where } } +impl Default for RandCorpusScheduler +where + S: HasCorpus + HasRand, + C: Corpus, + I: Input, + R: Rand, +{ + fn default() -> Self { + Self::new() + } +} + pub type StdCorpusScheduler = RandCorpusScheduler; diff --git a/libafl/src/corpus/ondisk.rs b/libafl/src/corpus/ondisk.rs index f597d45bdd..f9ca34d1e4 100644 --- a/libafl/src/corpus/ondisk.rs +++ b/libafl/src/corpus/ondisk.rs @@ -35,18 +35,15 @@ where /// Add an entry to the corpus and return its index #[inline] fn add(&mut self, mut testcase: Testcase) -> Result { - match testcase.filename() { - None => { - // TODO walk entry metadata to ask for pices of filename (e.g. :havoc in AFL) - let filename = self.dir_path.join(format!("id_{}", &self.entries.len())); - let filename_str = filename.to_str().expect("Invalid Path"); - testcase.set_filename(filename_str.into()); - } - _ => {} - } + if testcase.filename().is_none() { + // TODO walk entry metadata to ask for pices of filename (e.g. :havoc in AFL) + let filename = self.dir_path.join(format!("id_{}", &self.entries.len())); + let filename_str = filename.to_str().expect("Invalid Path"); + testcase.set_filename(filename_str.into()); + }; testcase .store_input() - .expect("Could not save testcase to disk".into()); + .expect("Could not save testcase to disk"); self.entries.push(RefCell::new(testcase)); Ok(self.entries.len() - 1) } @@ -101,7 +98,7 @@ where Ok(Self { entries: vec![], current: None, - dir_path: dir_path, + dir_path, }) } } diff --git a/libafl/src/corpus/queue.rs b/libafl/src/corpus/queue.rs index 5257a52d8e..3edb0f0833 100644 --- a/libafl/src/corpus/queue.rs +++ b/libafl/src/corpus/queue.rs @@ -59,48 +59,62 @@ where } } -/* +impl Default for QueueCorpusScheduler +where + S: HasCorpus, + C: Corpus, + I: Input, +{ + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] #[cfg(feature = "std")] mod tests { - use std::path::PathBuf; + use std::{fs, path::PathBuf}; use crate::{ - corpus::{Corpus, OnDiskCorpus, QueueCorpus, Testcase}, + corpus::{Corpus, CorpusScheduler, OnDiskCorpus, QueueCorpusScheduler, Testcase}, inputs::bytes::BytesInput, + state::{HasCorpus, State}, utils::StdRand, }; #[test] fn test_queuecorpus() { - let mut rand = StdRand::new(0); - let mut q = QueueCorpus::new(OnDiskCorpus::::new(PathBuf::from( - "fancy/path", - ))); - let t = Testcase::with_filename(BytesInput::new(vec![0 as u8; 4]), "fancyfile".into()); - q.add(t); - let filename = q - .next(&mut rand) + let rand = StdRand::with_seed(4); + let scheduler = QueueCorpusScheduler::new(); + + let mut q = + OnDiskCorpus::::new(PathBuf::from("target/.test/fancy/path")).unwrap(); + let t = Testcase::with_filename( + BytesInput::new(vec![0 as u8; 4]), + "target/.test/fancy/path/fancyfile".into(), + ); + q.add(t).unwrap(); + + let objective_q = + OnDiskCorpus::::new(PathBuf::from("target/.test/fancy/objective/path")) + .unwrap(); + + let mut state = State::new(rand, q, (), objective_q, ()); + + let next_idx = scheduler.next(&mut state).unwrap(); + let filename = state + .corpus() + .get(next_idx) .unwrap() - .0 .borrow() .filename() .as_ref() .unwrap() .to_owned(); - assert_eq!( - filename, - q.next(&mut rand) - .unwrap() - .0 - .borrow() - .filename() - .as_ref() - .unwrap() - .to_owned() - ); - assert_eq!(filename, "fancyfile"); + + assert_eq!(filename, "target/.test/fancy/path/fancyfile"); + + fs::remove_dir_all("target/.test/fancy").unwrap(); } } -*/ diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index 625e005ca2..eea9fd01ac 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -181,9 +181,9 @@ where #[inline] pub fn with_fitness(input: I, fitness: u32) -> Self { Testcase { - input: Some(input.into()), + input: Some(input), filename: None, - fitness: fitness, + fitness, metadata: SerdeAnyMap::new(), exec_time: None, cached_len: None, diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 102cab12d9..7219338e32 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -1,4 +1,4 @@ -use crate::bolts::llmp::LlmpSender; +use crate::bolts::{llmp::LlmpSender, shmem::HasFd}; use alloc::{string::ToString, vec::Vec}; use core::{marker::PhantomData, time::Duration}; use serde::{de::DeserializeOwned, Serialize}; @@ -6,11 +6,13 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "std")] use crate::bolts::llmp::LlmpReceiver; -#[cfg(feature = "std")] -use std::{env, process::Command}; +#[cfg(all(feature = "std", windows))] +use crate::utils::startable_self; -#[cfg(feature = "std")] -#[cfg(unix)] +#[cfg(all(feature = "std", unix))] +use crate::utils::{fork, ForkResult}; + +#[cfg(all(feature = "std", unix))] use crate::bolts::shmem::UnixShMem; use crate::{ bolts::{ @@ -167,10 +169,7 @@ where /// Returns if we are the broker pub fn is_broker(&self) -> bool { - match self.llmp { - llmp::LlmpConnection::IsBroker { broker: _ } => true, - _ => false, - } + matches!(self.llmp, llmp::LlmpConnection::IsBroker { broker: _ }) } /// Run forever in the broker @@ -194,6 +193,8 @@ where }, Some(Duration::from_millis(5)), ); + + Ok(()) } _ => Err(Error::IllegalState( "Called broker loop in the client".into(), @@ -283,14 +284,13 @@ where let observers: OT = postcard::from_bytes(&observers_buf)?; // TODO include ExitKind in NewTestcase let fitness = state.is_interesting(&input, &observers, ExitKind::Ok)?; - if fitness > 0 { - if !state + if fitness > 0 + && state .add_if_interesting(&input, fitness, scheduler)? - .is_none() - { - #[cfg(feature = "std")] - println!("Added received Testcase"); - } + .is_some() + { + #[cfg(feature = "std")] + println!("Added received Testcase"); } Ok(()) } @@ -302,6 +302,23 @@ where } } +impl LlmpEventManager +where + I: Input, + S: IfInteresting, + SH: ShMem + HasFd, + ST: Stats, +{ + #[cfg(all(feature = "std", unix))] + pub fn new_on_domain_socket(stats: ST, filename: &str) -> Result { + Ok(Self { + stats: Some(stats), + llmp: llmp::LlmpConnection::on_domain_socket(filename)?, + phantom: PhantomData, + }) + } +} + impl EventManager for LlmpEventManager where I: Input, @@ -312,12 +329,9 @@ where /// The llmp client needs to wait until a broker mapped all pages, before shutting down. /// Otherwise, the OS may already have removed the shared maps, fn await_restart_safe(&mut self) { - match &self.llmp { - llmp::LlmpConnection::IsClient { client } => { - // wait until we can drop the message safely. - client.await_save_to_unmap_blocking(); - } - _ => (), + if let llmp::LlmpConnection::IsClient { client } = &self.llmp { + // wait until we can drop the message safely. + client.await_save_to_unmap_blocking(); } } @@ -335,18 +349,15 @@ where // TODO: Get around local event copy by moving handle_in_client let mut events = vec![]; match &mut self.llmp { - llmp::LlmpConnection::IsClient { client } => loop { - match client.recv_buf()? { - Some((sender_id, tag, msg)) => { - if tag == _LLMP_TAG_EVENT_TO_BROKER { - continue; - } - let event: Event = postcard::from_bytes(msg)?; - events.push((sender_id, event)); + llmp::LlmpConnection::IsClient { client } => { + while let Some((sender_id, tag, msg)) = client.recv_buf()? { + if tag == _LLMP_TAG_EVENT_TO_BROKER { + continue; } - None => break, + let event: Event = postcard::from_bytes(msg)?; + events.push((sender_id, event)); } - }, + } _ => { #[cfg(feature = "std")] dbg!("Skipping process in broker"); @@ -498,18 +509,31 @@ pub fn setup_restarting_mgr( where I: Input, S: DeserializeOwned + IfInteresting, - SH: ShMem, + SH: ShMem + HasFd, // Todo: HasFd is only needed for Android ST: Stats, { let mut mgr; // We start ourself as child process to actually fuzz - if std::env::var(_ENV_FUZZER_SENDER).is_err() { - mgr = LlmpEventManager::::new_on_port(stats, broker_port)?; + let (sender, mut receiver) = if std::env::var(_ENV_FUZZER_SENDER).is_err() { + #[cfg(target_os = "android")] + { + let path = std::env::current_dir()?; + mgr = LlmpEventManager::::new_on_domain_socket( + stats, + &format!("{}/.llmp_socket", path.display()).to_string(), + )?; + }; + #[cfg(not(target_os = "android"))] + { + mgr = LlmpEventManager::::new_on_port(stats, broker_port)? + }; + if mgr.is_broker() { // Yep, broker. Just loop here. println!("Doing broker things. Run this tool again to start fuzzing in a client."); mgr.broker_loop()?; + return Err(Error::ShuttingDown); } else { mgr.to_env(_ENV_FUZZER_BROKER_CLIENT_INITIAL); @@ -527,22 +551,32 @@ where // Client->parent loop loop { dbg!("Spawning next client (id {})", ctr); - Command::new(env::current_exe()?) - .current_dir(env::current_dir()?) - .args(env::args()) - .status()?; + + // On Unix, we fork (todo: measure if that is actually faster.) + #[cfg(unix)] + let _ = match unsafe { fork() }? { + ForkResult::Parent(handle) => handle.status(), + ForkResult::Child => break (sender, receiver), + }; + + // On windows, we spawn ourself again + #[cfg(windows)] + startable_self()?.status()?; + ctr += 1; } } - } + } else { + // We are the newly started fuzzing instance, first, connect to our own restore map. + // A sender and a receiver for single communication + ( + LlmpSender::::on_existing_from_env(_ENV_FUZZER_SENDER)?, + LlmpReceiver::::on_existing_from_env(_ENV_FUZZER_RECEIVER)?, + ) + }; println!("We're a client, let's fuzz :)"); - // We are the fuzzing instance, first, connect to our own restore map. - // A sender and a receiver for single communication - let mut receiver = LlmpReceiver::::on_existing_from_env(_ENV_FUZZER_RECEIVER)?; - let sender = LlmpSender::::on_existing_from_env(_ENV_FUZZER_SENDER)?; - // If we're restarting, deserialize the old state. let (state, mut mgr) = match receiver.recv_buf()? { None => { diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index ec91e2c1b1..ab0d88cab3 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -43,7 +43,7 @@ where OT: ObserversTuple, { let count = self.events.len(); - while self.events.len() > 0 { + while !self.events.is_empty() { let event = self.events.pop().unwrap(); self.handle_in_client(state, event)?; } @@ -66,7 +66,7 @@ where { pub fn new(stats: ST) -> Self { Self { - stats: stats, + stats, events: vec![], phantom: PhantomData, } @@ -118,11 +118,9 @@ where // Handle arriving events in the client fn handle_in_client(&mut self, _state: &mut S, event: Event) -> Result<(), Error> { - match event { - _ => Err(Error::Unknown(format!( - "Received illegal message that message should not have arrived: {:?}.", - event - ))), - } + Err(Error::Unknown(format!( + "Received illegal message that message should not have arrived: {:?}.", + event + ))) } } diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 5c59f9df29..a890be9518 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -2,10 +2,14 @@ //! It should usually be paired with extra error-handling, such as a restarting event manager, to be effective. use core::marker::PhantomData; -#[cfg(feature = "std")] #[cfg(unix)] -use os_signals::set_oncrash_ptrs; +use core::{ + ptr::{self, write_volatile}, + sync::atomic::{compiler_fence, Ordering}, +}; +#[cfg(unix)] +use crate::bolts::os::unix_signals::{c_void, setup_signal_handler}; use crate::{ bolts::tuples::Named, corpus::Corpus, @@ -18,17 +22,6 @@ use crate::{ Error, }; -#[cfg(feature = "std")] -#[cfg(unix)] -use unix_signals as os_signals; - -#[cfg(feature = "std")] -#[cfg(unix)] -use self::os_signals::reset_oncrash_ptrs; -#[cfg(feature = "std")] -#[cfg(unix)] -use self::os_signals::setup_crash_handlers; - /// The inmem executor harness type HarnessFunction = fn(&E, &[u8]) -> ExitKind; @@ -58,27 +51,19 @@ where _state: &mut S, _event_mgr: &mut EM, _input: &I, - ) -> Result<(), Error> - where - EM: EventManager, - { + ) -> Result<(), Error> { #[cfg(unix)] - #[cfg(feature = "std")] unsafe { - set_oncrash_ptrs(_state, _event_mgr, self.observers(), _input); - } - Ok(()) - } - - #[inline] - fn post_exec(&mut self, _state: &S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error> - where - EM: EventManager, - { - #[cfg(unix)] - #[cfg(feature = "std")] - unsafe { - reset_oncrash_ptrs(); + let data = &mut unix_signal_handler::GLOBAL_STATE; + write_volatile( + &mut data.current_input_ptr, + _input as *const _ as *const c_void, + ); + // Direct raw pointers access /aliasing is pretty undefined behavior. + // Since the state and event may have moved in memory, refresh them right before the signal may happen + write_volatile(&mut data.state_ptr, _state as *mut _ as *mut c_void); + write_volatile(&mut data.event_mgr_ptr, _event_mgr as *mut _ as *mut c_void); + compiler_fence(Ordering::SeqCst); } Ok(()) } @@ -87,6 +72,14 @@ where fn run_target(&mut self, input: &I) -> Result { let bytes = input.target_bytes(); let ret = (self.harness_fn)(self, bytes.as_slice()); + #[cfg(unix)] + unsafe { + write_volatile( + &mut unix_signal_handler::GLOBAL_STATE.current_input_ptr, + ptr::null(), + ); + compiler_fence(Ordering::SeqCst); + } Ok(ret) } } @@ -124,100 +117,274 @@ where { /// Create a new in mem executor. /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, - /// depnding on different corpus or state. + /// depending on different corpus or state. /// * `name` - the name of this executor (to address it along the way) /// * `harness_fn` - the harness, executiong the function /// * `observers` - the observers observing the target during execution + /// This may return an error on unix, if signal handler setup fails pub fn new( name: &'static str, harness_fn: HarnessFunction, observers: OT, _state: &mut S, _event_mgr: &mut EM, - ) -> Self + ) -> Result where EM: EventManager, OC: Corpus, OFT: FeedbacksTuple, S: HasObjectives + HasSolutions, { - #[cfg(feature = "std")] #[cfg(unix)] unsafe { - setup_crash_handlers::(); + let data = &mut unix_signal_handler::GLOBAL_STATE; + write_volatile( + &mut data.observers_ptr, + &observers as *const _ as *const c_void, + ); + write_volatile( + &mut data.crash_handler, + unix_signal_handler::inproc_crash_handler::, + ); + write_volatile( + &mut data.timeout_handler, + unix_signal_handler::inproc_timeout_handler::, + ); + + setup_signal_handler(data)?; + compiler_fence(Ordering::SeqCst); } - Self { + Ok(Self { harness_fn, observers, name, phantom: PhantomData, - } + }) } } -#[cfg(feature = "std")] #[cfg(unix)] -pub mod unix_signals { - - extern crate libc; - - // Unhandled signals: SIGALRM, SIGHUP, SIGINT, SIGKILL, SIGQUIT, SIGTERM - use libc::{ - c_int, c_void, malloc, sigaction, sigaltstack, siginfo_t, SA_NODEFER, SA_ONSTACK, - SA_SIGINFO, SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, SIGUSR2, - }; - +mod unix_signal_handler { + use alloc::vec::Vec; + use core::ptr; + use libc::{c_void, siginfo_t}; + #[cfg(feature = "std")] use std::{ fs, io::{stdout, Write}, - mem, ptr, }; use crate::{ + bolts::os::unix_signals::{Handler, Signal}, corpus::{Corpus, Testcase}, events::{Event, EventManager}, executors::ExitKind, feedbacks::FeedbacksTuple, - inputs::Input, + inputs::{HasTargetBytes, Input}, observers::ObserversTuple, state::{HasObjectives, HasSolutions}, }; - /// Let's get 8 mb for now. - const SIGNAL_STACK_SIZE: usize = 2 << 22; - /// To be able to handle SIGSEGV when the stack is exhausted, we need our own little stack space. - static mut SIGNAL_STACK_PTR: *const c_void = ptr::null_mut(); - /// Pointers to values only needed on crash. As the program will not continue after a crash, - /// we should (tm) be okay with raw pointers here, - static mut STATE_PTR: *mut c_void = ptr::null_mut(); - static mut EVENT_MGR_PTR: *mut c_void = ptr::null_mut(); - static mut OBSERVERS_PTR: *const c_void = ptr::null(); - /// The (unsafe) pointer to the current inmem input, for the current run. - /// This is needed for certain non-rust side effects, as well as unix signal handling. - static mut CURRENT_INPUT_PTR: *const c_void = ptr::null(); + /// Signal handling on unix systems needs some nasty unsafe. + pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { + /// The state ptr for signal handling + state_ptr: ptr::null_mut(), + /// The event manager ptr for signal handling + event_mgr_ptr: ptr::null_mut(), + /// The observers ptr for signal handling + observers_ptr: ptr::null(), + /// The current input for signal handling + current_input_ptr: ptr::null(), + /// The crash handler fn + crash_handler: nop_handler, + /// The timeout handler fn + timeout_handler: nop_handler, + }; - unsafe fn inmem_handle_crash(_sig: c_int, info: siginfo_t, _void: c_void) - where + pub struct InProcessExecutorHandlerData { + pub state_ptr: *mut c_void, + pub event_mgr_ptr: *mut c_void, + pub observers_ptr: *const c_void, + pub current_input_ptr: *const c_void, + pub crash_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self), + pub timeout_handler: unsafe fn(Signal, siginfo_t, c_void, data: &mut Self), + } + + unsafe impl Send for InProcessExecutorHandlerData {} + unsafe impl Sync for InProcessExecutorHandlerData {} + + unsafe fn nop_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + _data: &mut InProcessExecutorHandlerData, + ) { + } + + #[cfg(unix)] + impl Handler for InProcessExecutorHandlerData { + fn handle(&mut self, signal: Signal, info: siginfo_t, void: c_void) { + unsafe { + let data = &mut GLOBAL_STATE; + match signal { + Signal::SigUser2 => (data.timeout_handler)(signal, info, void, data), + _ => (data.crash_handler)(signal, info, void, data), + } + } + } + + fn signals(&self) -> Vec { + vec![ + Signal::SigUser2, + Signal::SigAbort, + Signal::SigBus, + Signal::SigPipe, + Signal::SigFloatingPointException, + Signal::SigIllegalInstruction, + Signal::SigSegmentationFault, + ] + } + } + + #[cfg(unix)] + pub unsafe fn inproc_timeout_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + data: &mut InProcessExecutorHandlerData, + ) where EM: EventManager, OT: ObserversTuple, OC: Corpus, OFT: FeedbacksTuple, S: HasObjectives + HasSolutions, - I: Input, + I: Input + HasTargetBytes, { - if CURRENT_INPUT_PTR == ptr::null() { - #[cfg(target_os = "android")] - let si_addr = { ((info._pad[0] as usize) | ((info._pad[1] as usize) << 32)) as usize }; - #[cfg(not(target_os = "android"))] - let si_addr = { info.si_addr() as usize }; + let state = (data.state_ptr as *mut S).as_mut().unwrap(); + let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); + let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); - println!( + if data.current_input_ptr.is_null() { + #[cfg(feature = "std")] + dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing. Exiting"); + } else { + #[cfg(feature = "std")] + println!("Timeout in fuzz run."); + #[cfg(feature = "std")] + let _ = stdout().flush(); + + let input = (data.current_input_ptr as *const I).as_ref().unwrap(); + data.current_input_ptr = ptr::null(); + + let obj_fitness = state + .objectives_mut() + .is_interesting_all(&input, observers, ExitKind::Crash) + .expect("In timeout handler objectives failure."); + if obj_fitness > 0 { + state + .solutions_mut() + .add(Testcase::new(input.clone())) + .expect("In timeout handler solutions failure."); + event_mgr + .fire( + state, + Event::Objective { + objective_size: state.solutions().count(), + }, + ) + .expect("Could not send timeouting input"); + } + + event_mgr.on_restart(state).unwrap(); + + #[cfg(feature = "std")] + println!("Waiting for broker..."); + event_mgr.await_restart_safe(); + #[cfg(feature = "std")] + println!("Bye!"); + + event_mgr.await_restart_safe(); + + libc::_exit(1); + } + } + + pub unsafe fn inproc_crash_handler( + _signal: Signal, + _info: siginfo_t, + _void: c_void, + data: &mut InProcessExecutorHandlerData, + ) where + EM: EventManager, + OT: ObserversTuple, + OC: Corpus, + OFT: FeedbacksTuple, + S: HasObjectives + HasSolutions, + I: Input + HasTargetBytes, + { + #[cfg(feature = "std")] + println!("Crashed with {}", _signal); + if !data.current_input_ptr.is_null() { + let state = (data.state_ptr as *mut S).as_mut().unwrap(); + let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); + let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + + #[cfg(feature = "std")] + println!("Child crashed!"); + #[cfg(feature = "std")] + let _ = stdout().flush(); + + let input = (data.current_input_ptr as *const I).as_ref().unwrap(); + // Make sure we don't crash in the crash handler forever. + data.current_input_ptr = ptr::null(); + + let obj_fitness = state + .objectives_mut() + .is_interesting_all(&input, observers, ExitKind::Crash) + .expect("In crash handler objectives failure."); + if obj_fitness > 0 { + let new_input = input.clone(); + state + .solutions_mut() + .add(Testcase::new(new_input)) + .expect("In crash handler solutions failure."); + event_mgr + .fire( + state, + Event::Objective { + objective_size: state.solutions().count(), + }, + ) + .expect("Could not send crashing input"); + } + + event_mgr.on_restart(state).unwrap(); + + #[cfg(feature = "std")] + println!("Waiting for broker..."); + event_mgr.await_restart_safe(); + #[cfg(feature = "std")] + println!("Bye!"); + + libc::_exit(1); + } else { + #[cfg(feature = "std")] + { + println!("Double crash\n"); + #[cfg(target_os = "android")] + let si_addr = + { ((_info._pad[0] as usize) | ((_info._pad[1] as usize) << 32)) as usize }; + #[cfg(not(target_os = "android"))] + let si_addr = { _info.si_addr() as usize }; + + println!( "We crashed at addr 0x{:x}, but are not in the target... Bug in the fuzzer? Exiting.", si_addr - ); + ); + } // let's yolo-cat the maps for debugging, if possible. - #[cfg(target_os = "linux")] + #[cfg(all(target_os = "linux", feature = "std"))] match fs::read_to_string("/proc/self/maps") { Ok(maps) => println!("maps:\n{}", maps), Err(e) => println!("Couldn't load mappings: {:?}", e), @@ -232,167 +399,7 @@ pub mod unix_signals { } // TODO tell the parent to not restart - std::process::exit(1); - } - - #[cfg(feature = "std")] - println!("Child crashed!"); - #[cfg(feature = "std")] - let _ = stdout().flush(); - - let input = (CURRENT_INPUT_PTR as *const I).as_ref().unwrap(); - // Make sure we don't crash in the crash handler forever. - CURRENT_INPUT_PTR = ptr::null(); - let state = (STATE_PTR as *mut S).as_mut().unwrap(); - let mgr = (EVENT_MGR_PTR as *mut EM).as_mut().unwrap(); - - let observers = (OBSERVERS_PTR as *const OT).as_ref().unwrap(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, ExitKind::Crash) - .expect("In crash handler objectives failure.".into()); - if obj_fitness > 0 { - state - .solutions_mut() - .add(Testcase::new(input.clone())) - .expect("In crash handler solutions failure.".into()); - mgr.fire( - state, - Event::Objective { - objective_size: state.solutions().count(), - }, - ) - .expect("Could not send crashing input".into()); - } - - mgr.on_restart(state).unwrap(); - - println!("Waiting for broker..."); - mgr.await_restart_safe(); - println!("Bye!"); - - std::process::exit(1); - } - - unsafe fn inmem_handle_timeout( - _sig: c_int, - _info: siginfo_t, - _void: c_void, - ) where - EM: EventManager, - OT: ObserversTuple, - OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, - I: Input, - { - dbg!("TIMEOUT/SIGUSR2 received"); - if CURRENT_INPUT_PTR.is_null() { - dbg!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing. Exiting"); - return; - } - - println!("Timeout in fuzz run."); - let _ = stdout().flush(); - - let input = (CURRENT_INPUT_PTR as *const I).as_ref().unwrap(); - // Make sure we don't crash in the crash handler forever. - CURRENT_INPUT_PTR = ptr::null(); - let state = (STATE_PTR as *mut S).as_mut().unwrap(); - let mgr = (EVENT_MGR_PTR as *mut EM).as_mut().unwrap(); - - let observers = (OBSERVERS_PTR as *const OT).as_ref().unwrap(); - let obj_fitness = state - .objectives_mut() - .is_interesting_all(&input, observers, ExitKind::Crash) - .expect("In timeout handler objectives failure.".into()); - if obj_fitness > 0 { - state - .solutions_mut() - .add(Testcase::new(input.clone())) - .expect("In timeout handler solutions failure.".into()); - mgr.fire( - state, - Event::Objective { - objective_size: state.solutions().count(), - }, - ) - .expect("Could not send timeouting input".into()); - } - - mgr.on_restart(state).unwrap(); - - println!("Waiting for broker..."); - mgr.await_restart_safe(); - println!("Bye!"); - - mgr.await_restart_safe(); - - std::process::exit(1); - } - - #[inline] - pub unsafe fn set_oncrash_ptrs( - state: &mut S, - event_mgr: &mut EM, - observers: &OT, - input: &I, - ) { - CURRENT_INPUT_PTR = input as *const _ as *const c_void; - STATE_PTR = state as *mut _ as *mut c_void; - EVENT_MGR_PTR = event_mgr as *mut _ as *mut c_void; - OBSERVERS_PTR = observers as *const _ as *const c_void; - } - - #[inline] - pub unsafe fn reset_oncrash_ptrs() { - CURRENT_INPUT_PTR = ptr::null(); - STATE_PTR = ptr::null_mut(); - EVENT_MGR_PTR = ptr::null_mut(); - OBSERVERS_PTR = ptr::null(); - } - - pub unsafe fn setup_crash_handlers() - where - EM: EventManager, - OT: ObserversTuple, - OC: Corpus, - OFT: FeedbacksTuple, - S: HasObjectives + HasSolutions, - I: Input, - { - // First, set up our own stack to be used during segfault handling. (and specify `SA_ONSTACK` in `sigaction`) - if SIGNAL_STACK_PTR.is_null() { - SIGNAL_STACK_PTR = malloc(SIGNAL_STACK_SIZE); - if SIGNAL_STACK_PTR.is_null() { - panic!( - "Failed to allocate signal stack with {} bytes!", - SIGNAL_STACK_SIZE - ); - } - } - sigaltstack(SIGNAL_STACK_PTR as _, ptr::null_mut() as _); - - let mut sa: sigaction = mem::zeroed(); - libc::sigemptyset(&mut sa.sa_mask as *mut libc::sigset_t); - sa.sa_flags = SA_NODEFER | SA_SIGINFO | SA_ONSTACK; - sa.sa_sigaction = inmem_handle_crash:: as usize; - for (sig, msg) in &[ - (SIGSEGV, "segfault"), - (SIGBUS, "sigbus"), - (SIGABRT, "sigabrt"), - (SIGILL, "illegal instruction"), - (SIGFPE, "fp exception"), - (SIGPIPE, "pipe"), - ] { - if sigaction(*sig, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { - panic!("Could not set up {} handler", &msg); - } - } - - sa.sa_sigaction = inmem_handle_timeout:: as usize; - if sigaction(SIGUSR2, &mut sa as *mut sigaction, ptr::null_mut()) < 0 { - panic!("Could not set up sigusr2 handler for timeouts"); + libc::_exit(1); } } } diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index dfe2740203..b3d25bc9ff 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -9,7 +9,7 @@ use core::cmp::PartialEq; use core::marker::PhantomData; use crate::{ - bolts::tuples::{MatchNameAndType, MatchType, Named, TupleList}, + bolts::tuples::Named, events::EventManager, inputs::{HasTargetBytes, Input}, observers::ObserversTuple, @@ -59,7 +59,7 @@ where I: Input + HasTargetBytes, { fn run_target(&mut self, input: &I) -> Result { - if input.target_bytes().as_slice().len() == 0 { + if input.target_bytes().as_slice().is_empty() { Err(Error::Empty("Input Empty".into())) } else { Ok(ExitKind::Ok) @@ -78,8 +78,8 @@ pub trait Executor: Named where I: Input, { - #[inline] /// Called right before exexution starts + #[inline] fn pre_exec( &mut self, _state: &mut S, @@ -92,8 +92,8 @@ where Ok(()) } - #[inline] /// Called right after execution finished. + #[inline] fn post_exec(&mut self, _state: &S, _event_mgr: &mut EM, _input: &I) -> Result<(), Error> where EM: EventManager, @@ -105,39 +105,6 @@ where fn run_target(&mut self, input: &I) -> Result; } -pub trait ExecutorsTuple: MatchType + MatchNameAndType -where - I: Input, -{ - //fn for_each(&self, f: fn(&dyn Executor)); - //fn for_each_mut(&mut self, f: fn(&mut dyn Executor)); -} - -impl ExecutorsTuple for () -where - I: Input, -{ - //fn for_each(&self, _f: fn(&dyn Executor)) {} - //fn for_each_mut(&mut self, _f: fn(&mut dyn Executor)) {} -} - -impl ExecutorsTuple for (Head, Tail) -where - Head: Executor + 'static, - Tail: ExecutorsTuple + TupleList, - I: Input, -{ - /*fn for_each(&self, f: fn(&dyn Executor)) { - f(&self.0); - self.1.for_each(f) - } - - fn for_each_mut(&mut self, f: fn(&mut dyn Executor)) { - f(&mut self.0); - self.1.for_each_mut(f) - }*/ -} - #[cfg(test)] mod test { use core::marker::PhantomData; diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 18daa5cd5a..2f7e71fb3c 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -218,19 +218,13 @@ where } fn append_metadata(&mut self, testcase: &mut Testcase) -> Result<(), Error> { - match self.indexes.as_mut() { - Some(v) => { - let meta = MapIndexesMetadata::new(core::mem::take(v)); - testcase.add_metadata(meta); - } - None => {} + if let Some(v) = self.indexes.as_mut() { + let meta = MapIndexesMetadata::new(core::mem::take(v)); + testcase.add_metadata(meta); }; - match self.novelties.as_mut() { - Some(v) => { - let meta = MapNoveltiesMetadata::new(core::mem::take(v)); - testcase.add_metadata(meta); - } - None => {} + if let Some(v) = self.novelties.as_mut() { + let meta = MapNoveltiesMetadata::new(core::mem::take(v)); + testcase.add_metadata(meta); }; Ok(()) } @@ -329,7 +323,7 @@ where /// The map can be shared. pub fn with_history_map(name: &'static str, history_map: Vec) -> Self { Self { - history_map: history_map, + history_map, name: name.to_string(), indexes: None, novelties: None, diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 76ac3abad5..79dddaaf5d 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -11,10 +11,12 @@ use crate::{ corpus::Testcase, executors::ExitKind, inputs::Input, - observers::ObserversTuple, + observers::{ObserversTuple, TimeObserver}, Error, }; +use core::time::Duration; + /// Feedbacks evaluate the observers. /// Basically, they reduce the information provided by an observer to a value, /// indicating the "interestingness" of the last run. @@ -41,30 +43,6 @@ where fn discard_metadata(&mut self, _input: &I) -> Result<(), Error> { Ok(()) } - - /* - /// Serialize this feedback's state only, to be restored later using deserialize_state - /// As opposed to completely serializing the observer, this is only needed when the fuzzer is to be restarted - /// If no state is needed to be kept, just return an empty vec. - /// Example: - /// >> The virgin_bits map in AFL needs to be in sync with the corpus - #[inline] - fn serialize_state(&mut self) -> Result, Error> { - Ok(vec![]) - } - - /// Restore the state from a given vec, priviously stored using `serialize_state` - #[inline] - fn deserialize_state(&mut self, serialized_state: &[u8]) -> Result<(), Error> { - let _ = serialized_state; - Ok(()) - } - - // TODO: Restore_from - fn restore_from(&mut self, restore_from: Self) -> Result<(), Error> { - Ok(()) - } - */ } pub trait FeedbacksTuple: serde::Serialize + serde::de::DeserializeOwned @@ -84,12 +62,6 @@ where /// Discards metadata - the end of this input's execution fn discard_metadata_all(&mut self, input: &I) -> Result<(), Error>; - - /* - /// Restores the state from each of the containing feedbacks in a list of the same shape. - /// Used (prette exclusively) to restore the feedback states after a crash. - fn restore_state_from_all(&mut self, restore_from: &Self) -> Result<(), Error>; - */ } impl FeedbacksTuple for () @@ -115,12 +87,6 @@ where fn discard_metadata_all(&mut self, _input: &I) -> Result<(), Error> { Ok(()) } - - /* - fn restore_state_from_all(&mut self, restore_from: &Self) -> Result<(), Error> { - Ok(()) - } - */ } impl FeedbacksTuple for (Head, Tail) @@ -184,3 +150,65 @@ impl CrashFeedback { Self {} } } + +impl Default for CrashFeedback { + fn default() -> Self { + Self::new() + } +} + +/// Nop feedback that annotates execution time in the new testcase, if any +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TimeFeedback { + exec_time: Option, +} + +impl Feedback for TimeFeedback +where + I: Input, +{ + fn is_interesting( + &mut self, + _input: &I, + observers: &OT, + _exit_kind: ExitKind, + ) -> Result { + let observer = observers.match_first_type::().unwrap(); + self.exec_time = *observer.last_runtime(); + Ok(0) + } + + /// Append to the testcase the generated metadata in case of a new corpus item + #[inline] + fn append_metadata(&mut self, testcase: &mut Testcase) -> Result<(), Error> { + *testcase.exec_time_mut() = self.exec_time; + self.exec_time = None; + Ok(()) + } + + /// Discard the stored metadata in case that the testcase is not added to the corpus + #[inline] + fn discard_metadata(&mut self, _input: &I) -> Result<(), Error> { + self.exec_time = None; + Ok(()) + } +} + +impl Named for TimeFeedback { + #[inline] + fn name(&self) -> &str { + "TimeFeedback" + } +} + +impl TimeFeedback { + pub fn new() -> Self { + Self { exec_time: None } + } +} + +impl Default for TimeFeedback { + fn default() -> Self { + Self::new() + } +} diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index d4507ed9b6..b65587d205 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -142,8 +142,8 @@ where { pub fn new(scheduler: CS, stages: ST) -> Self { Self { - scheduler: scheduler, - stages: stages, + scheduler, + stages, phantom: PhantomData, } } diff --git a/libafl/src/generators/mod.rs b/libafl/src/generators/mod.rs index 07ab0bd4e6..f42a6c175a 100644 --- a/libafl/src/generators/mod.rs +++ b/libafl/src/generators/mod.rs @@ -61,7 +61,7 @@ where { pub fn new(max_size: usize) -> Self { Self { - max_size: max_size, + max_size, phantom: PhantomData, } } @@ -93,7 +93,7 @@ where /// Generates up to DUMMY_BYTES_MAX non-random dummy bytes (0) fn generate_dummy(&self) -> BytesInput { let size = min(self.max_size, DUMMY_BYTES_MAX); - BytesInput::new(vec!['0' as u8; size]) + BytesInput::new(vec![0u8; size]) } } @@ -103,7 +103,7 @@ where { pub fn new(max_size: usize) -> Self { Self { - max_size: max_size, + max_size, phantom: PhantomData, } } diff --git a/libafl/src/inputs/bytes.rs b/libafl/src/inputs/bytes.rs index 2747cb3643..5840e783be 100644 --- a/libafl/src/inputs/bytes.rs +++ b/libafl/src/inputs/bytes.rs @@ -64,7 +64,7 @@ impl From<&[u8]> for BytesInput { impl BytesInput { /// Creates a new bytes input using the given bytes pub fn new(bytes: Vec) -> Self { - Self { bytes: bytes } + Self { bytes } } } diff --git a/libafl/src/inputs/mod.rs b/libafl/src/inputs/mod.rs index 6d00b34c4e..cb409ed408 100644 --- a/libafl/src/inputs/mod.rs +++ b/libafl/src/inputs/mod.rs @@ -97,6 +97,10 @@ pub trait HasBytesVec { /// Has a length field pub trait HasLen { - /// The lenght + /// The length fn len(&self) -> usize; + + fn is_empty(&self) -> bool { + self.len() == 0 + } } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 01fb47a3f9..55170850ad 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -66,6 +66,8 @@ pub enum Error { IllegalState(String), /// The argument passed to this method or function is not valid IllegalArgument(String), + /// Shutting down, not really an error. + ShuttingDown, /// Something else happened Unknown(String), } @@ -85,6 +87,7 @@ impl fmt::Display for Error { Self::NotImplemented(s) => write!(f, "Not implemented: {0}", &s), Self::IllegalState(s) => write!(f, "Illegal state: {0}", &s), Self::IllegalArgument(s) => write!(f, "Illegal argument: {0}", &s), + Self::ShuttingDown => write!(f, "Shutting down!"), Self::Unknown(s) => write!(f, "Unknown error: {0}", &s), } } @@ -179,7 +182,8 @@ mod tests { //Box::new(|_, _, _, _, _| ()), &mut state, &mut event_manager, - ); + ) + .unwrap(); let mut mutator = StdScheduledMutator::new(); mutator.add_mutation(mutation_bitflip); diff --git a/libafl/src/mutators/mutations.rs b/libafl/src/mutators/mutations.rs index a4ac24c46d..dd8946eec9 100644 --- a/libafl/src/mutators/mutations.rs +++ b/libafl/src/mutators/mutations.rs @@ -41,32 +41,26 @@ where /// Mem move in the own vec #[inline] pub fn buffer_self_copy(data: &mut [u8], from: usize, to: usize, len: usize) { - debug_assert!(data.len() > 0); + debug_assert!(!data.is_empty()); debug_assert!(from + len <= data.len()); debug_assert!(to + len <= data.len()); if len != 0 && from != to { let ptr = data.as_mut_ptr(); - unsafe { core::ptr::copy(ptr.offset(from as isize), ptr.offset(to as isize), len) } + unsafe { core::ptr::copy(ptr.add(from), ptr.add(to), len) } } } /// Mem move between vecs #[inline] pub fn buffer_copy(dst: &mut [u8], src: &[u8], from: usize, to: usize, len: usize) { - debug_assert!(dst.len() > 0); - debug_assert!(src.len() > 0); + debug_assert!(!dst.is_empty()); + debug_assert!(!src.is_empty()); debug_assert!(from + len <= src.len()); debug_assert!(to + len <= dst.len()); let dst_ptr = dst.as_mut_ptr(); let src_ptr = src.as_ptr(); if len != 0 { - unsafe { - core::ptr::copy( - src_ptr.offset(from as isize), - dst_ptr.offset(to as isize), - len, - ) - } + unsafe { core::ptr::copy(src_ptr.add(from), dst_ptr.add(to), len) } } } @@ -124,7 +118,7 @@ where S: HasRand + HasMaxSize, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let bit = state.rand_mut().below((input.bytes().len() << 3) as u64) as usize; @@ -142,7 +136,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -160,7 +154,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -179,7 +173,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -198,7 +192,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -216,7 +210,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -234,7 +228,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -335,7 +329,7 @@ where S: HasRand, R: Rand, { - if input.bytes().len() == 0 { + if input.bytes().is_empty() { Ok(MutationResult::Skipped) } else { let idx = state.rand_mut().below(input.bytes().len() as u64) as usize; @@ -767,7 +761,7 @@ fn from_hex(hex: u8) -> Result { if hex >= 97 && hex <= 102 { return Ok(hex - 87); } - return Err(Error::IllegalArgument("".to_owned())); + Err(Error::IllegalArgument("".to_owned())) } /// Decodes a dictionary token: 'foo\x41\\and\"bar' -> 'fooA\and"bar' @@ -801,7 +795,7 @@ pub fn str_decode(item: &str) -> Result, Error> { } } - return Ok(token); + Ok(token) } #[cfg(test)] diff --git a/libafl/src/mutators/scheduled.rs b/libafl/src/mutators/scheduled.rs index 961e266b8c..2c309d46df 100644 --- a/libafl/src/mutators/scheduled.rs +++ b/libafl/src/mutators/scheduled.rs @@ -133,12 +133,23 @@ where /// Create a new StdScheduledMutator instance specifying mutations pub fn with_mutations(mutations: Vec>) -> Self { StdScheduledMutator { - mutations: mutations, + mutations, phantom: PhantomData, } } } +impl Default for StdScheduledMutator +where + I: Input, + S: HasRand, + R: Rand, +{ + fn default() -> Self { + Self::new() + } +} + /// Schedule some selected byte level mutations given a ScheduledMutator type #[derive(Clone, Debug)] pub struct HavocBytesMutator @@ -202,7 +213,7 @@ where scheduled.add_mutation(mutation_bitflip); scheduled.add_mutation(mutation_splice); Self { - scheduled: scheduled, + scheduled, phantom: PhantomData, } } @@ -253,13 +264,12 @@ where //scheduled.add_mutation(mutation_splice); HavocBytesMutator { - scheduled: scheduled, + scheduled, phantom: PhantomData, } } } -/* #[cfg(test)] mod tests { use crate::{ @@ -276,28 +286,23 @@ mod tests { #[test] fn test_mut_scheduled() { // With the current impl, seed of 1 will result in a split at pos 2. - let mut rand = XKCDRand::new(); - let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); - corpus.add(Testcase::new(vec!['a' as u8, 'b' as u8, 'c' as u8]).into()); - corpus.add(Testcase::new(vec!['d' as u8, 'e' as u8, 'f' as u8]).into()); + let mut rand = XKCDRand::with_seed(5); + let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); + corpus + .add(Testcase::new(vec!['a' as u8, 'b' as u8, 'c' as u8]).into()) + .unwrap(); + corpus + .add(Testcase::new(vec!['d' as u8, 'e' as u8, 'f' as u8]).into()) + .unwrap(); - let (testcase, _) = corpus - .next(&mut rand) - .expect("Corpus did not contain entries"); + let testcase = corpus.get(0).expect("Corpus did not contain entries"); let mut input = testcase.borrow_mut().load_input().unwrap().clone(); - let mut state = State::new(corpus, (), InMemoryCorpus::new(), ()); + let mut state = State::new(rand, corpus, (), InMemoryCorpus::new(), ()); rand.set_seed(5); - let mut mutator = StdScheduledMutator::< - InMemoryCorpus, - _, - _, - State<_, (), _, InMemoryCorpus, (), _>, - >::new(); - - mutation_splice(&mut mutator, &mut rand, &mut state, &mut input).unwrap(); + mutation_splice(&mut state, &mut input).unwrap(); #[cfg(feature = "std")] println!("{:?}", input.bytes()); @@ -310,27 +315,37 @@ mod tests { #[test] fn test_havoc() { // With the current impl, seed of 1 will result in a split at pos 2. - let mut rand = StdRand::new(0x1337); - let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); - corpus.add(Testcase::new(vec!['a' as u8, 'b' as u8, 'c' as u8]).into()); - corpus.add(Testcase::new(vec!['d' as u8, 'e' as u8, 'f' as u8]).into()); + let rand = StdRand::with_seed(0x1337); + let mut corpus: InMemoryCorpus = InMemoryCorpus::new(); + corpus + .add(Testcase::new(vec!['a' as u8, 'b' as u8, 'c' as u8]).into()) + .unwrap(); + corpus + .add(Testcase::new(vec!['d' as u8, 'e' as u8, 'f' as u8]).into()) + .unwrap(); - let (testcase, _) = corpus - .next(&mut rand) - .expect("Corpus did not contain entries"); + let testcase = corpus.get(0).expect("Corpus did not contain entries"); let mut input = testcase.borrow_mut().load_input().unwrap().clone(); let input_prior = input.clone(); - let mut state = State::new(corpus, (), InMemoryCorpus::new(), ()); + let mut state = State::new(rand, corpus, (), InMemoryCorpus::new(), ()); - let mut havoc = HavocBytesMutator::new(StdScheduledMutator::new()); + let havoc = HavocBytesMutator::new(StdScheduledMutator::new()); assert_eq!(input, input_prior); + let mut equal_in_a_row = 0; + for i in 0..42 { - havoc.mutate(&mut rand, &mut state, &mut input, i).unwrap(); - assert_ne!(input, input_prior); + havoc.mutate(&mut state, &mut input, i).unwrap(); + + // Make sure we actually mutate something, at least sometimes + equal_in_a_row = if input == input_prior { + equal_in_a_row + 1 + } else { + 0 + }; + assert_ne!(equal_in_a_row, 5); } } } -*/ diff --git a/libafl/src/mutators/token_mutations.rs b/libafl/src/mutators/token_mutations.rs index 9a43602bfb..90741b2f97 100644 --- a/libafl/src/mutators/token_mutations.rs +++ b/libafl/src/mutators/token_mutations.rs @@ -54,7 +54,7 @@ impl Tokens { return false; } self.token_vec.push(token.to_vec()); - return true; + true } /// Reads a tokens file, returning the count of new entries read @@ -75,11 +75,11 @@ impl Tokens { let line = line.trim_start().trim_end(); // we are only interested in '"..."', not prefixed 'foo = ' - let start = line.chars().nth(0); - if line.len() == 0 || start == Some('#') { + let start = line.chars().next(); + if line.is_empty() || start == Some('#') { continue; } - let pos_quote = match line.find("\"") { + let pos_quote = match line.find('\"') { Some(x) => x, _ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)), }; @@ -92,7 +92,7 @@ impl Tokens { Some(x) => x, _ => return Err(Error::IllegalArgument("Illegal line: ".to_owned() + line)), }; - if item.len() == 0 { + if item.is_empty() { continue; } @@ -117,7 +117,7 @@ impl Tokens { /// Gets the tokens stored in this db pub fn tokens(&self) -> &[Vec] { - return &self.token_vec; + &self.token_vec } } @@ -134,7 +134,7 @@ where if meta.is_none() { return Ok(MutationResult::Skipped); } - if meta.unwrap().tokens().len() == 0 { + if meta.unwrap().tokens().is_empty() { return Ok(MutationResult::Skipped); } meta.unwrap().tokens().len() @@ -180,7 +180,7 @@ where if meta.is_none() { return Ok(MutationResult::Skipped); } - if meta.unwrap().tokens().len() == 0 { + if meta.unwrap().tokens().is_empty() { return Ok(MutationResult::Skipped); } meta.unwrap().tokens().len() diff --git a/libafl/src/observers/map.rs b/libafl/src/observers/map.rs index a63155c030..141f75f02d 100644 --- a/libafl/src/observers/map.rs +++ b/libafl/src/observers/map.rs @@ -118,7 +118,7 @@ where { /// Creates a new MapObserver pub fn new(name: &'static str, map: &'static mut [T]) -> Self { - let initial = if map.len() > 0 { map[0] } else { T::default() }; + let initial = if map.is_empty() { T::default() } else { map[0] }; Self { map: ArrayMut::Cptr((map.as_mut_ptr(), map.len())), name: name.to_string(), @@ -127,14 +127,14 @@ where } /// Creates a new MapObserver from a raw pointer - pub fn new_from_ptr(name: &'static str, map_ptr: *mut T, len: usize) -> Self { - unsafe { - let initial = if len > 0 { *map_ptr } else { T::default() }; - StdMapObserver { - map: ArrayMut::Cptr((map_ptr, len)), - name: name.to_string(), - initial, - } + /// # Safety + /// Will dereference the map_ptr with up to len elements. + pub unsafe fn new_from_ptr(name: &'static str, map_ptr: *mut T, len: usize) -> Self { + let initial = if len > 0 { *map_ptr } else { T::default() }; + StdMapObserver { + map: ArrayMut::Cptr((map_ptr, len)), + name: name.to_string(), + initial, } } } @@ -213,7 +213,7 @@ where { /// Creates a new MapObserver pub fn new(name: &'static str, map: &'static mut [T], size: &usize) -> Self { - let initial = if map.len() > 0 { map[0] } else { T::default() }; + let initial = if map.is_empty() { T::default() } else { map[0] }; Self { map: ArrayMut::Cptr((map.as_mut_ptr(), map.len())), size: Cptr::Cptr(size as *const _), @@ -223,20 +223,20 @@ where } /// Creates a new MapObserver from a raw pointer - pub fn new_from_ptr( + /// # Safety + /// Dereferences map_ptr with up to max_len elements of size_ptr. + pub unsafe fn new_from_ptr( name: &'static str, map_ptr: *mut T, max_len: usize, size_ptr: *const usize, ) -> Self { - unsafe { - let initial = if max_len > 0 { *map_ptr } else { T::default() }; - VariableMapObserver { - map: ArrayMut::Cptr((map_ptr, max_len)), - size: Cptr::Cptr(size_ptr), - name: name.into(), - initial, - } + let initial = if max_len > 0 { *map_ptr } else { T::default() }; + VariableMapObserver { + map: ArrayMut::Cptr((map_ptr, max_len)), + size: Cptr::Cptr(size_ptr), + name: name.into(), + initial, } } } @@ -335,6 +335,6 @@ where { /// Creates a new MapObserver pub fn new(base: M) -> Self { - Self { base: base } + Self { base } } } diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 379566902a..7404ceeeb9 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -9,7 +9,7 @@ use core::time::Duration; use serde::{Deserialize, Serialize}; use crate::{ - bolts::tuples::{MatchNameAndType, MatchType, Named, TupleList}, + bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named, TupleList}, utils::current_time, Error, }; @@ -52,17 +52,15 @@ pub trait Observer: Named + serde::Serialize + serde::de::DeserializeOwned + 'st /// A hastkel-style tuple of observers pub trait ObserversTuple: - MatchNameAndType + MatchType + serde::Serialize + serde::de::DeserializeOwned + MatchNameAndType + MatchType + MatchFirstType + serde::Serialize + serde::de::DeserializeOwned { /// Reset all executors in the tuple /// This is called right before the next execution. fn pre_exec_all(&mut self) -> Result<(), Error>; + /// Do whatever you need to do after a run. /// This is called right after the last execution fn post_exec_all(&mut self) -> Result<(), Error>; - - //fn for_each(&self, f: fn(&dyn Observer)); - //fn for_each_mut(&mut self, f: fn(&mut dyn Observer)); } impl ObserversTuple for () { @@ -72,9 +70,6 @@ impl ObserversTuple for () { fn post_exec_all(&mut self) -> Result<(), Error> { Ok(()) } - - //fn for_each(&self, f: fn(&dyn Observer)) { } - //fn for_each_mut(&mut self, f: fn(&mut dyn Observer)) { } } impl ObserversTuple for (Head, Tail) @@ -91,16 +86,6 @@ where self.0.post_exec()?; self.1.post_exec_all() } - - /*fn for_each(&self, f: fn(&dyn Observer)) { - f(&self.0); - self.1.for_each(f) - } - - fn for_each_mut(&mut self, f: fn(&mut dyn Observer)) { - f(&mut self.0); - self.1.for_each_mut(f) - }*/ } /// A simple observer, just overlooking the runtime of the target. @@ -120,6 +105,10 @@ impl TimeObserver { last_runtime: None, } } + + pub fn last_runtime(&self) -> &Option { + &self.last_runtime + } } impl Observer for TimeObserver { diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index e50a165d0b..6d91508a58 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -1,5 +1,8 @@ pub mod mutational; -pub use mutational::StdMutationalStage; +pub use mutational::{MutationalStage, StdMutationalStage}; + +//pub mod power; +//pub use power::PowerMutationalStage; use crate::{ bolts::tuples::TupleList, events::EventManager, executors::Executor, inputs::Input, Error, diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index bf05d9c803..cd872b3462 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -157,7 +157,7 @@ where /// Creates a new default mutational stage pub fn new(mutator: M) -> Self { Self { - mutator: mutator, + mutator, phantom: PhantomData, } } diff --git a/libafl/src/stages/power.rs b/libafl/src/stages/power.rs new file mode 100644 index 0000000000..3e333b52d7 --- /dev/null +++ b/libafl/src/stages/power.rs @@ -0,0 +1,110 @@ +use core::marker::PhantomData; + +use crate::{ + corpus::{Corpus, CorpusScheduler}, + events::EventManager, + executors::{Executor, HasObservers}, + inputs::Input, + mutators::Mutator, + observers::ObserversTuple, + stages::{Stage, MutationalStage}, + state::{Evaluator, HasCorpus, HasRand}, + utils::Rand, + Error, +}; + +/// The mutational stage using power schedules +#[derive(Clone, Debug)] +pub struct PowerMutationalStage +where + M: Mutator, + I: Input, + S: HasCorpus + Evaluator + HasRand, + C: Corpus, + EM: EventManager, + E: Executor + HasObservers, + OT: ObserversTuple, + CS: CorpusScheduler, + R: Rand, +{ + mutator: M, + phantom: PhantomData<(C, CS, E, EM, I, OT, R, S)>, +} + +impl MutationalStage + for PowerMutationalStage +where + M: Mutator, + I: Input, + S: HasCorpus + Evaluator + HasRand, + C: Corpus, + EM: EventManager, + E: Executor + HasObservers, + OT: ObserversTuple, + CS: CorpusScheduler, + R: Rand, +{ + /// The mutator, added to this stage + #[inline] + fn mutator(&self) -> &M { + &self.mutator + } + + /// The list of mutators, added to this stage (as mutable ref) + #[inline] + fn mutator_mut(&mut self) -> &mut M { + &mut self.mutator + } + + /// Gets the number of iterations as a random number + fn iterations(&self, state: &mut S) -> usize { + 1 + state.rand_mut().below(DEFAULT_MUTATIONAL_MAX_ITERATIONS) as usize + } +} + +impl Stage + for PowerMutationalStage +where + M: Mutator, + I: Input, + S: HasCorpus + Evaluator + HasRand, + C: Corpus, + EM: EventManager, + E: Executor + HasObservers, + OT: ObserversTuple, + CS: CorpusScheduler, + R: Rand, +{ + #[inline] + fn perform( + &self, + state: &mut S, + executor: &mut E, + manager: &mut EM, + scheduler: &CS, + corpus_idx: usize, + ) -> Result<(), Error> { + self.perform_mutational(state, executor, manager, scheduler, corpus_idx) + } +} + +impl PowerMutationalStage +where + M: Mutator, + I: Input, + S: HasCorpus + Evaluator + HasRand, + C: Corpus, + EM: EventManager, + E: Executor + HasObservers, + OT: ObserversTuple, + CS: CorpusScheduler, + R: Rand, +{ + /// Creates a new default mutational stage + pub fn new(mutator: M) -> Self { + Self { + mutator: mutator, + phantom: PhantomData, + } + } +} diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index ef6a467eae..5f4cf154f5 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -515,15 +515,15 @@ where self.solutions_mut().add(Testcase::new(input.clone()))?; } - if !self + if self .add_if_interesting(&input, fitness, scheduler)? - .is_none() + .is_some() { let observers_buf = manager.serialize_observers(observers)?; manager.fire( self, Event::NewTestcase { - input: input, + input, observers_buf, corpus_size: self.corpus().count() + 1, client_config: "TODO".into(), @@ -564,7 +564,7 @@ where let path = entry.path(); let attributes = fs::metadata(&path); - if !attributes.is_ok() { + if attributes.is_err() { continue; } @@ -647,7 +647,7 @@ where executor.pre_exec(self, event_mgr, input)?; let exit_kind = executor.run_target(input)?; - //executor.post_exec(&self, event_mgr, input)?; + executor.post_exec(self, event_mgr, input)?; *self.executions_mut() += 1; executor.post_exec_observers()?; diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index efd56de220..6a0883c707 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -187,7 +187,7 @@ where { pub fn new(print_fn: F) -> Self { Self { - print_fn: print_fn, + print_fn, start_time: current_time(), corpus_size: 0, client_stats: vec![], @@ -196,8 +196,8 @@ where pub fn with_time(print_fn: F, start_time: time::Duration) -> Self { Self { - print_fn: print_fn, - start_time: start_time, + print_fn, + start_time, corpus_size: 0, client_stats: vec![], } diff --git a/libafl/src/utils.rs b/libafl/src/utils.rs index 072c86e3c0..27fcb93115 100644 --- a/libafl/src/utils.rs +++ b/libafl/src/utils.rs @@ -4,8 +4,19 @@ use core::{cell::RefCell, debug_assert, fmt::Debug, time}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use xxhash_rust::xxh3::xxh3_64_with_seed; +#[cfg(unix)] +use libc::pid_t; +#[cfg(all(unix, feature = "std"))] +use std::ffi::CString; #[cfg(feature = "std")] -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + env, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; + +#[cfg(any(unix, feature = "std"))] +use crate::Error; pub trait AsSlice { /// Convert to a slice @@ -204,7 +215,7 @@ impl Rand for Xoshiro256StarRand { self.rand_seed[3] = self.rand_seed[3].rotate_left(45); - return ret; + ret } } @@ -235,7 +246,7 @@ impl Rand for XorShift64Rand { x ^= x >> 7; x ^= x << 17; self.rand_seed = x; - return x; + x } } @@ -262,7 +273,7 @@ impl Rand for Lehmer64Rand { #[inline] fn next(&mut self) -> u64 { self.rand_seed *= 0xda942042e4dd58b5; - return (self.rand_seed >> 64) as u64; + (self.rand_seed >> 64) as u64 } } @@ -378,6 +389,61 @@ impl XKCDRand { } } +/// Child Process Handle +#[cfg(unix)] +pub struct ChildHandle { + pid: pid_t, +} + +#[cfg(unix)] +impl ChildHandle { + /// Block until the child exited and the status code becomes available + pub fn status(&self) -> i32 { + let mut status = -1; + unsafe { + libc::waitpid(self.pid, &mut status, 0); + } + status + } +} + +#[cfg(unix)] +/// The ForkResult +pub enum ForkResult { + Parent(ChildHandle), + Child, +} + +/// Unix has forks. +/// # Safety +/// A Normal fork. Runs on in two processes. Should be memory safe in general. +#[cfg(unix)] +pub unsafe fn fork() -> Result { + match libc::fork() { + pid if pid > 0 => Ok(ForkResult::Parent(ChildHandle { pid })), + pid if pid < 0 => { + // Getting errno from rust is hard, we'll just let the libc print to stderr for now. + // In any case, this should usually not happen. + #[cfg(feature = "std")] + { + let err_str = CString::new("Fork failed").unwrap(); + libc::perror(err_str.as_ptr()); + } + Err(Error::Unknown(format!("Fork failed ({})", pid))) + } + _ => Ok(ForkResult::Child), + } +} + +/// Executes the current process from the beginning, as subprocess. +/// use `start_self.status()?` to wait for the child +#[cfg(feature = "std")] +pub fn startable_self() -> Result { + let mut startable = Command::new(env::current_exe()?); + startable.current_dir(env::current_dir()?).args(env::args()); + Ok(startable) +} + #[cfg(test)] mod tests { //use xxhash_rust::xxh3::xxh3_64_with_seed;