diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /tools/fuzzing/rust | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/fuzzing/rust')
-rw-r--r-- | tools/fuzzing/rust/Cargo.toml | 11 | ||||
-rw-r--r-- | tools/fuzzing/rust/RustFuzzingTargets.cpp | 15 | ||||
-rw-r--r-- | tools/fuzzing/rust/RustFuzzingTargets.h | 23 | ||||
-rw-r--r-- | tools/fuzzing/rust/moz.build | 16 | ||||
-rw-r--r-- | tools/fuzzing/rust/src/lib.rs | 341 |
5 files changed, 406 insertions, 0 deletions
diff --git a/tools/fuzzing/rust/Cargo.toml b/tools/fuzzing/rust/Cargo.toml new file mode 100644 index 0000000000..1779e191fd --- /dev/null +++ b/tools/fuzzing/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gecko-fuzz-targets" +version = "0.1.0" +authors = ["fuzzing@mozilla.com"] + +[dependencies] +libc = "0.2" +tempfile = "3" +lazy_static = "1.4.0" +rkv = { version = "0.16", features = ["with-fuzzer-no-link"] } +lmdb-rkv = { version = "0.14", features = ["with-fuzzer-no-link"] } diff --git a/tools/fuzzing/rust/RustFuzzingTargets.cpp b/tools/fuzzing/rust/RustFuzzingTargets.cpp new file mode 100644 index 0000000000..7b4dd6c86c --- /dev/null +++ b/tools/fuzzing/rust/RustFuzzingTargets.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FuzzingInterface.h" +#include "RustFuzzingTargets.h" + +int FuzzingInitDummy(int* argc, char*** argv) { return 0; } + +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDummy, fuzz_rkv_db_file, RkvDbFile); +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDummy, fuzz_rkv_db_name, RkvDbName); +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDummy, fuzz_rkv_key_write, RkvKeyWrite); +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDummy, fuzz_rkv_val_write, RkvValWrite); +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitDummy, fuzz_rkv_calls, RkvCalls); diff --git a/tools/fuzzing/rust/RustFuzzingTargets.h b/tools/fuzzing/rust/RustFuzzingTargets.h new file mode 100644 index 0000000000..8f44fb277c --- /dev/null +++ b/tools/fuzzing/rust/RustFuzzingTargets.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Interface definitions for fuzzing rust modules + */ + +#ifndef RustFuzzingTargets_h__ +#define RustFuzzingTargets_h__ + +extern "C" { + +int fuzz_rkv_db_file(const uint8_t* raw_data, size_t size); +int fuzz_rkv_db_name(const uint8_t* raw_data, size_t size); +int fuzz_rkv_key_write(const uint8_t* raw_data, size_t size); +int fuzz_rkv_val_write(const uint8_t* raw_data, size_t size); +int fuzz_rkv_calls(const uint8_t* raw_data, size_t size); + +} // extern "C" + +#endif // RustFuzzingTargets_h__ diff --git a/tools/fuzzing/rust/moz.build b/tools/fuzzing/rust/moz.build new file mode 100644 index 0000000000..aa6aaa29dc --- /dev/null +++ b/tools/fuzzing/rust/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library("fuzzer-rust-targets") + +SOURCES += [ + "RustFuzzingTargets.cpp", +] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/tools/fuzzing/rust/src/lib.rs b/tools/fuzzing/rust/src/lib.rs new file mode 100644 index 0000000000..1df45153c1 --- /dev/null +++ b/tools/fuzzing/rust/src/lib.rs @@ -0,0 +1,341 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#[macro_use] +extern crate lazy_static; +extern crate libc; +extern crate lmdb; +extern crate rkv; +extern crate tempfile; + +use rkv::backend::{ + BackendEnvironmentBuilder, SafeMode, SafeModeDatabase, SafeModeEnvironment, + SafeModeRoTransaction, SafeModeRwTransaction, +}; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::iter; +use std::path::Path; +use std::sync::Arc; +use std::thread; +use tempfile::Builder; + +type Rkv = rkv::Rkv<SafeModeEnvironment>; +type SingleStore = rkv::SingleStore<SafeModeDatabase>; + +fn eat_lmdb_err<T>(value: Result<T, rkv::StoreError>) -> Result<Option<T>, rkv::StoreError> { + match value { + Ok(value) => Ok(Some(value)), + Err(rkv::StoreError::LmdbError(_)) => Ok(None), + Err(err) => { + println!("Not a crash, but an error outside LMDB: {}", err); + println!("A refined fuzzing test, or changes to RKV, might be required."); + Err(err) + } + } +} + +fn panic_with_err(err: rkv::StoreError) { + println!("Got error: {}", err); + Result::Err(err).unwrap() +} + +#[no_mangle] +pub extern "C" fn fuzz_rkv_db_file(raw_data: *const u8, size: libc::size_t) -> libc::c_int { + let data = unsafe { std::slice::from_raw_parts(raw_data as *const u8, size as usize) }; + + // First 8192 bytes are for the lock file. + if data.len() < 8192 { + return 0; + } + let (lock, db) = data.split_at(8192); + let mut lock_file = File::create("data.lock").unwrap(); + lock_file.write_all(lock).unwrap(); + let mut db_file = File::create("data.mdb").unwrap(); + db_file.write_all(db).unwrap(); + + let env = Rkv::with_capacity::<SafeMode>(Path::new("."), 2).unwrap(); + let store = env + .open_single("test", rkv::StoreOptions::create()) + .unwrap(); + + let reader = env.read().unwrap(); + eat_lmdb_err(store.get(&reader, &[0])).unwrap(); + + 0 +} + +#[no_mangle] +pub extern "C" fn fuzz_rkv_db_name(raw_data: *const u8, size: libc::size_t) -> libc::c_int { + let data = unsafe { std::slice::from_raw_parts(raw_data as *const u8, size as usize) }; + + let root = Builder::new().prefix("fuzz_rkv_db_name").tempdir().unwrap(); + fs::create_dir_all(root.path()).unwrap(); + + let env = Rkv::new::<SafeMode>(root.path()).unwrap(); + let name = String::from_utf8_lossy(data); + println!("Checking string: '{:?}'", name); + // Some strings are invalid database names, and are handled as store errors. + // Ignore those errors, but not others. + let store = eat_lmdb_err(env.open_single(name.as_ref(), rkv::StoreOptions::create())).unwrap(); + + if let Some(store) = store { + let reader = env.read().unwrap(); + eat_lmdb_err(store.get(&reader, &[0])).unwrap(); + }; + + 0 +} + +#[no_mangle] +pub extern "C" fn fuzz_rkv_key_write(raw_data: *const u8, size: libc::size_t) -> libc::c_int { + let data = unsafe { std::slice::from_raw_parts(raw_data as *const u8, size as usize) }; + + let root = Builder::new() + .prefix("fuzz_rkv_key_write") + .tempdir() + .unwrap(); + fs::create_dir_all(root.path()).unwrap(); + + let env = Rkv::new::<SafeMode>(root.path()).unwrap(); + let store = env + .open_single("test", rkv::StoreOptions::create()) + .unwrap(); + + let mut writer = env.write().unwrap(); + // Some data are invalid values, and are handled as store errors. + // Ignore those errors, but not others. + eat_lmdb_err(store.put(&mut writer, data, &rkv::Value::Str("val"))).unwrap(); + + 0 +} + +#[no_mangle] +pub extern "C" fn fuzz_rkv_val_write(raw_data: *const u8, size: libc::size_t) -> libc::c_int { + let data = unsafe { std::slice::from_raw_parts(raw_data as *const u8, size as usize) }; + + let root = Builder::new() + .prefix("fuzz_rkv_val_write") + .tempdir() + .unwrap(); + fs::create_dir_all(root.path()).unwrap(); + + let env = Rkv::new::<SafeMode>(root.path()).unwrap(); + let store = env + .open_single("test", rkv::StoreOptions::create()) + .unwrap(); + + let mut writer = env.write().unwrap(); + let string = String::from_utf8_lossy(data); + let value = rkv::Value::Str(&string); + store.put(&mut writer, "key", &value).unwrap(); + + 0 +} + +lazy_static! { + static ref STATIC_DATA: Vec<String> = { + let sizes = vec![4, 16, 128, 512, 1024]; + let mut data = Vec::new(); + + for (i, s) in sizes.into_iter().enumerate() { + let bytes = iter::repeat('a' as u8 + i as u8).take(s).collect(); + data.push(String::from_utf8(bytes).unwrap()); + } + + data + }; +} + +#[no_mangle] +pub extern "C" fn fuzz_rkv_calls(raw_data: *const u8, size: libc::size_t) -> libc::c_int { + let data = unsafe { std::slice::from_raw_parts(raw_data as *const u8, size as usize) }; + let mut fuzz = data.iter().copied(); + + fn maybe_do<I: Iterator<Item = u8>>(fuzz: &mut I, f: impl FnOnce(&mut I) -> ()) -> Option<()> { + match fuzz.next().map(|byte| byte % 2) { + Some(0) => Some(f(fuzz)), + _ => None, + } + } + + fn maybe_abort<I: Iterator<Item = u8>>( + fuzz: &mut I, + read: rkv::Reader<SafeModeRoTransaction>, + ) -> Result<(), rkv::StoreError> { + match fuzz.next().map(|byte| byte % 2) { + Some(0) => Ok(read.abort()), + _ => Ok(()), + } + }; + + fn maybe_commit<I: Iterator<Item = u8>>( + fuzz: &mut I, + write: rkv::Writer<SafeModeRwTransaction>, + ) -> Result<(), rkv::StoreError> { + match fuzz.next().map(|byte| byte % 3) { + Some(0) => write.commit(), + Some(1) => Ok(write.abort()), + _ => Ok(()), + } + }; + + fn get_static_data<'a, I: Iterator<Item = u8>>(fuzz: &mut I) -> Option<&'a String> { + fuzz.next().map(|byte| { + let data = &*STATIC_DATA; + let n = byte as usize; + data.get(n % data.len()).unwrap() + }) + }; + + fn get_fuzz_data<I: Iterator<Item = u8> + Clone>( + fuzz: &mut I, + max_len: usize, + ) -> Option<Vec<u8>> { + fuzz.next().map(|byte| { + let n = byte as usize; + fuzz.clone().take((n * n) % max_len).collect() + }) + }; + + fn get_any_data<I: Iterator<Item = u8> + Clone>( + fuzz: &mut I, + max_len: usize, + ) -> Option<Vec<u8>> { + match fuzz.next().map(|byte| byte % 2) { + Some(0) => get_static_data(fuzz).map(|v| v.clone().into_bytes()), + Some(1) => get_fuzz_data(fuzz, max_len), + _ => None, + } + }; + + fn store_put<I: Iterator<Item = u8> + Clone>(fuzz: &mut I, env: &Rkv, store: &SingleStore) { + let key = match get_any_data(fuzz, 1024) { + Some(key) => key, + None => return, + }; + let value = match get_any_data(fuzz, std::usize::MAX) { + Some(value) => value, + None => return, + }; + + let mut writer = env.write().unwrap(); + let mut full = false; + + match store.put(&mut writer, key, &rkv::Value::Blob(&value)) { + Ok(_) => {} + Err(rkv::StoreError::LmdbError(lmdb::Error::BadValSize)) => {} + Err(rkv::StoreError::LmdbError(lmdb::Error::MapFull)) => full = true, + Err(err) => panic_with_err(err), + }; + + if full { + writer.abort(); + store_resize(fuzz, env); + } else { + maybe_commit(fuzz, writer).unwrap(); + } + } + + fn store_get<I: Iterator<Item = u8> + Clone>(fuzz: &mut I, env: &Rkv, store: &SingleStore) { + let key = match get_any_data(fuzz, 1024) { + Some(key) => key, + None => return, + }; + + let mut reader = match env.read() { + Ok(reader) => reader, + Err(rkv::StoreError::LmdbError(lmdb::Error::ReadersFull)) => return, + Err(err) => return panic_with_err(err), + }; + + match store.get(&mut reader, key) { + Ok(_) => {} + Err(rkv::StoreError::LmdbError(lmdb::Error::BadValSize)) => {} + Err(err) => panic_with_err(err), + }; + + maybe_abort(fuzz, reader).unwrap(); + } + + fn store_delete<I: Iterator<Item = u8> + Clone>(fuzz: &mut I, env: &Rkv, store: &SingleStore) { + let key = match get_any_data(fuzz, 1024) { + Some(key) => key, + None => return, + }; + + let mut writer = env.write().unwrap(); + + match store.delete(&mut writer, key) { + Ok(_) => {} + Err(rkv::StoreError::LmdbError(lmdb::Error::BadValSize)) => {} + Err(rkv::StoreError::LmdbError(lmdb::Error::NotFound)) => {} + Err(err) => panic_with_err(err), + }; + + maybe_commit(fuzz, writer).unwrap(); + } + + fn store_resize<I: Iterator<Item = u8>>(fuzz: &mut I, env: &Rkv) { + let n = fuzz.next().unwrap_or(1) as usize; + env.set_map_size(1_048_576 * (n % 100)).unwrap() // 1,048,576 bytes, i.e. 1MiB. + }; + + let root = Builder::new().prefix("fuzz_rkv_calls").tempdir().unwrap(); + fs::create_dir_all(root.path()).unwrap(); + + let mut builder: SafeMode = Rkv::environment_builder(); + builder.set_max_dbs(1); // need at least one db + + maybe_do(&mut fuzz, |fuzz| { + let n = fuzz.next().unwrap_or(126) as u32; // default + builder.set_max_readers(1 + n); + }); + maybe_do(&mut fuzz, |fuzz| { + let n = fuzz.next().unwrap_or(0) as u32; + builder.set_max_dbs(1 + n); + }); + maybe_do(&mut fuzz, |fuzz| { + let n = fuzz.next().unwrap_or(1) as usize; + builder.set_map_size(1_048_576 * (n % 100)); // 1,048,576 bytes, i.e. 1MiB. + }); + + let env = Rkv::from_builder(root.path(), builder).unwrap(); + let store = env + .open_single("test", rkv::StoreOptions::create()) + .unwrap(); + + let shared_env = Arc::new(env); + let shared_store = Arc::new(store); + + let use_threads = fuzz.next().map(|byte| byte % 10 == 0).unwrap_or(false); + let max_threads = if use_threads { 16 } else { 1 }; + let num_threads = fuzz.next().unwrap_or(0) as usize % max_threads + 1; + let chunk_size = fuzz.len() / num_threads; + + let threads = (0..num_threads).map(|_| { + let env = shared_env.clone(); + let store = shared_store.clone(); + + let chunk: Vec<_> = fuzz.by_ref().take(chunk_size).collect(); + let mut fuzz = chunk.into_iter(); + + thread::spawn(move || loop { + match fuzz.next().map(|byte| byte % 4) { + Some(0) => store_put(&mut fuzz, &env, &store), + Some(1) => store_get(&mut fuzz, &env, &store), + Some(2) => store_delete(&mut fuzz, &env, &store), + Some(3) => store_resize(&mut fuzz, &env), + _ => break, + } + }) + }); + + for handle in threads { + handle.join().unwrap() + } + + 0 +} |