// 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; type SingleStore = rkv::SingleStore; fn eat_lmdb_err(value: Result) -> Result, 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::(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::(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::(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::(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 = { 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>(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>( fuzz: &mut I, read: rkv::Reader, ) -> Result<(), rkv::StoreError> { match fuzz.next().map(|byte| byte % 2) { Some(0) => Ok(read.abort()), _ => Ok(()), } } fn maybe_commit>( fuzz: &mut I, write: rkv::Writer, ) -> 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>(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 + Clone>( fuzz: &mut I, max_len: usize, ) -> Option> { fuzz.next().map(|byte| { let n = byte as usize; fuzz.clone().take((n * n) % max_len).collect() }) } fn get_any_data + Clone>( fuzz: &mut I, max_len: usize, ) -> Option> { 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 + 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 + 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 + 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>(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 }