diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index b81118f..79d7a71 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -115,9 +115,9 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "sqlcipher-src" -version = "419.0.4+sqlcipher-4.19.0-sqlite-3.53.4-libtomcrypt-1.18.2" +version = "419.0.6+sqlcipher-4.19.0-sqlite-3.53.4-libtomcrypt-1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d983a7f69ae93370d5101e82b9442dce8c4e510977d97374c3e668d6dff32d08" +checksum = "ab732d48ebf2a1449e94d071b26544f99845834c75c3c210e927ace097b49aa4" [[package]] name = "sqlite3mc-src" diff --git a/fuzz/build.rs b/fuzz/build.rs index 1d3a5a9..e8385d7 100644 --- a/fuzz/build.rs +++ b/fuzz/build.rs @@ -11,7 +11,7 @@ use std::{ struct Side<'a> { prefix: &'a str, source: PathBuf, - include: &'a Path, + includes: &'a [&'a Path], defines: &'a [(&'a str, Option<&'a str>)], } @@ -34,16 +34,21 @@ fn main() { &Side { prefix: "mc", source: mc_dir.join(sqlite3mc_src::SOURCE_FILE), - include: mc_dir, + includes: &[mc_dir], defines: &mc_defines, }, &out, ); + let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR")); build( &Side { prefix: "sc", - source: sc_dir.join(sqlcipher_src::WASM_SOURCE_FILE), - include: sc_dir, + source: sqlcipher_unit(crate_dir, sc_dir, &out), + includes: &[ + sc_dir, + &sc_dir.join(sqlcipher_src::LIBTOMCRYPT_INCLUDE_DIR), + crate_dir, + ], defines: &common, }, &out, @@ -53,14 +58,24 @@ fn main() { println!("cargo:rustc-link-lib={lib}"); } println!("cargo:rerun-if-changed=build.rs"); + for file in [ + "sqlcipher-wrapper.c", + "sqlcipher-ltc.h", + "sqlcipher-entropy.c", + ] { + println!("cargo:rerun-if-changed={file}"); + } println!("cargo:rerun-if-env-changed=NM"); println!("cargo:rerun-if-env-changed=OBJCOPY"); } fn build(side: &Side<'_>, out: &Path) { - println!("cargo:rerun-if-changed={}", side.include.display()); let mut cc = cc::Build::new(); - cc.file(&side.source).include(side.include).warnings(false); + cc.file(&side.source).warnings(false); + for include in side.includes { + println!("cargo:rerun-if-changed={}", include.display()); + cc.include(include); + } for (name, value) in side.defines { cc.define(name, *value); } @@ -88,6 +103,26 @@ fn build(side: &Side<'_>, out: &Path) { println!("cargo:rustc-link-lib=static={}", side.prefix); } +/// Writes the one translation unit SQLCipher builds from, the wrapper's configuration and SQLCipher, then every +/// libtomcrypt source in order, then the entropy hook, all sharing one configuration as upstream's single-file +/// libtomcrypt does. +fn sqlcipher_unit(crate_dir: &Path, sc_dir: &Path, out: &Path) -> PathBuf { + let sources = std::iter::once(crate_dir.join("sqlcipher-wrapper.c")) + .chain( + sqlcipher_src::LIBTOMCRYPT_SOURCES + .iter() + .map(|source| sc_dir.join(source)), + ) + .chain(std::iter::once(crate_dir.join("sqlcipher-entropy.c"))); + let unit = sources.fold(String::new(), |mut unit, source| { + let _ = writeln!(unit, "#include \"{}\"", source.display()); + unit + }); + let path = out.join("sqlcipher-unit.c"); + fs::write(&path, unit).expect("OUT_DIR is writable"); + path +} + /// Gives the C side the coverage and sanitizer instrumentation `cargo fuzz` gives the Rust side. fn instrument(cc: &mut cc::Build) { let fuzzing = env::var_os("CARGO_CFG_FUZZING").is_some(); diff --git a/fuzz/sqlcipher-entropy.c b/fuzz/sqlcipher-entropy.c new file mode 100644 index 0000000..b1eaf4e --- /dev/null +++ b/fuzz/sqlcipher-entropy.c @@ -0,0 +1,14 @@ +/* SQLCipher's entropy hook, giving its LibTomCrypt RNG the harness's deterministic getentropy. */ +long sqlcipher_wasm_no_clock(void) { abort(); } + +/* Fails closed so rng_get_bytes never falls through to the clock. */ +static unsigned long sqlcipher_wasm_rng(unsigned char *out, unsigned long len, void (*callback)(void)) { + (void)callback; + if (getentropy(out, len) != 0) abort(); + return len; +} + +int sqlcipher_wasm_extra_init(const char *arg) { + ltc_rng = sqlcipher_wasm_rng; + return sqlcipher_extra_init(arg); +} diff --git a/fuzz/sqlcipher-ltc.h b/fuzz/sqlcipher-ltc.h new file mode 100644 index 0000000..3857e89 --- /dev/null +++ b/fuzz/sqlcipher-ltc.h @@ -0,0 +1,29 @@ +/* The LibTomCrypt configuration SQLCipher and every libtomcrypt source in the unit share. */ +#ifndef SQLCIPHER_LTC_H +#define SQLCIPHER_LTC_H + +#define LTC_NOTHING +#define LTC_RIJNDAEL +#define LTC_CBC_MODE +#define LTC_SHA1 +#define LTC_SHA256 +#define LTC_SHA512 +#define LTC_HASH_HELPERS +#define LTC_HMAC +#define LTC_FORTUNA +#define LTC_PKCS_5 +#define LTC_RNG_GET_BYTES +#define LTC_PRNG_ENABLE_LTC_RNG +#define LTC_NO_TEST +#define LTC_NO_FILE +#define LTC_NO_ASM +#define LTC_NO_MATH +#define LTC_CLEAN_STACK +/* Bad arguments return CRYPT_INVALID_ARG instead of raising a signal, which the fuzzer would count as a crash. */ +#define ARGTYPE 4 +/* rng_get_bytes always ends in a clock-jitter generator, so reaching it must abort. */ +#define XCLOCK sqlcipher_wasm_no_clock +#define XCLOCKS_PER_SEC 1 +long sqlcipher_wasm_no_clock(void); + +#endif diff --git a/fuzz/sqlcipher-wrapper.c b/fuzz/sqlcipher-wrapper.c new file mode 100644 index 0000000..5d23bda --- /dev/null +++ b/fuzz/sqlcipher-wrapper.c @@ -0,0 +1,18 @@ +/* SQLCipher's configuration, heading the one unit build.rs assembles with libtomcrypt and the entropy hook. */ +#define SQLITE_HAS_CODEC 1 +#define SQLCIPHER_CRYPTO_LIBTOMCRYPT 1 +/* Installs the entropy hook before SQLCipher's own init runs. */ +#define SQLITE_EXTRA_INIT sqlcipher_wasm_extra_init +#define SQLITE_EXTRA_SHUTDOWN sqlcipher_extra_shutdown +/* SQLCipher's log needs a timestamp and a device the harness does not keep. */ +#define SQLCIPHER_OMIT_LOG 1 +#define SQLCIPHER_OMIT_LOG_DEVICE 1 +#define OMIT_MEMLOCK 1 +/* SQLCipher refuses SQLITE_THREADSAFE=0, and the harness runs single-threaded, so no-op mutexes suffice. */ +#undef SQLITE_THREADSAFE +#define SQLITE_THREADSAFE 1 +#define SQLITE_MUTEX_NOOP 1 + +#define LTC_SOURCE +#include "sqlcipher-ltc.h" +#include "sqlcipher.c" diff --git a/fuzz/src/entropy.rs b/fuzz/src/entropy.rs index 34b3dd9..a65a274 100644 --- a/fuzz/src/entropy.rs +++ b/fuzz/src/entropy.rs @@ -1,5 +1,5 @@ //! Replaces libc's `getentropy`, the call SQLCipher's RNG makes directly (`sqlcipher_wasm_rng` in -//! `WASM_SOURCE_FILE`), with a deterministic stream reset once per [`run`](crate::run::run) call, so +//! `sqlcipher-entropy.c`), with a deterministic stream reset once per [`run`](crate::run::run) call, so //! the same [`Case`](crate::Case) produces the same salts and IVs on every repeat, not just the same //! workload. SQLite3MC's own RNG reads `/dev/urandom` first and falls back to `getentropy` only if //! that open fails, so this does not make SQLite3MC's own writes deterministic; it is enough for any