Skip to content

Commit 3ea3bf8

Browse files
Build SQLCipher from sqlcipher-src 419.0.6's separate libtomcrypt sources
1 parent eb63d31 commit 3ea3bf8

7 files changed

Lines changed: 198 additions & 30 deletions

File tree

‎fuzz/Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎fuzz/build.rs‎

Lines changed: 117 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ use std::{
88
process::Command,
99
};
1010

11+
/// One compiler invocation of a side, its objects merge before the rename.
12+
struct Unit<'a> {
13+
files: &'a [PathBuf],
14+
includes: &'a [PathBuf],
15+
defines: &'a [(&'a str, Option<&'a str>)],
16+
forced_include: Option<&'a str>,
17+
}
18+
1119
struct Side<'a> {
1220
prefix: &'a str,
13-
source: PathBuf,
14-
include: &'a Path,
15-
defines: &'a [(&'a str, Option<&'a str>)],
21+
units: &'a [Unit<'a>],
1622
}
1723

1824
fn main() {
@@ -22,29 +28,60 @@ fn main() {
2228
"the symbol renaming needs ELF objects and GNU-compatible nm and objcopy, so the fuzz crate builds on Linux only"
2329
);
2430
let out = PathBuf::from(env::var_os("OUT_DIR").expect("cargo sets OUT_DIR"));
25-
let mc_dir = sqlite3mc_src::source_dir();
26-
let sc_dir = sqlcipher_src::source_dir();
31+
let crate_dir =
32+
PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
33+
let mc_dir = sqlite3mc_src::source_dir().to_path_buf();
34+
let sc_dir = sqlcipher_src::source_dir().to_path_buf();
35+
let ltc_dir = sc_dir.join(sqlcipher_src::LIBTOMCRYPT_INCLUDE_DIR);
2736
let common = [
2837
("SQLITE_TEMP_STORE", Some("2")),
2938
("SQLITE_ENABLE_DBPAGE_VTAB", None),
3039
("SQLITE_DEFAULT_MEMSTATUS", Some("0")),
3140
];
32-
let mc_defines = [common.as_slice(), &[("SQLITE_CORE", None)]].concat();
41+
let mc_defines: Vec<(&str, Option<&str>)> =
42+
[common.as_slice(), &[("SQLITE_CORE", None)]].concat();
43+
let ltc_files: Vec<PathBuf> = std::iter::once("sqlcipher-entropy.c")
44+
.map(|name| crate_dir.join(name))
45+
.chain(
46+
sqlcipher_src::LIBTOMCRYPT_SOURCES
47+
.iter()
48+
.map(|source| sc_dir.join(source)),
49+
)
50+
.collect();
51+
let mc_files = [mc_dir.join(sqlite3mc_src::SOURCE_FILE)];
52+
let mc_includes = [mc_dir];
53+
let wrapper_files = [crate_dir.join("sqlcipher-wrapper.c")];
54+
let wrapper_includes = [sc_dir, ltc_dir.clone()];
55+
let ltc_includes = [ltc_dir, crate_dir];
3356
build(
3457
&Side {
3558
prefix: "mc",
36-
source: mc_dir.join(sqlite3mc_src::SOURCE_FILE),
37-
include: mc_dir,
38-
defines: &mc_defines,
59+
units: &[Unit {
60+
files: &mc_files,
61+
includes: &mc_includes,
62+
defines: &mc_defines,
63+
forced_include: None,
64+
}],
3965
},
4066
&out,
4167
);
4268
build(
4369
&Side {
4470
prefix: "sc",
45-
source: sc_dir.join(sqlcipher_src::WASM_SOURCE_FILE),
46-
include: sc_dir,
47-
defines: &common,
71+
units: &[
72+
Unit {
73+
files: &wrapper_files,
74+
includes: &wrapper_includes,
75+
defines: &common,
76+
forced_include: None,
77+
},
78+
Unit {
79+
files: &ltc_files,
80+
includes: &ltc_includes,
81+
defines: &[("LTC_SOURCE", None)],
82+
forced_include: Some("sqlcipher-ltc.h"),
83+
},
84+
],
4885
},
4986
&out,
5087
);
@@ -53,38 +90,80 @@ fn main() {
5390
println!("cargo:rustc-link-lib={lib}");
5491
}
5592
println!("cargo:rerun-if-changed=build.rs");
93+
println!("cargo:rerun-if-changed=sqlcipher-wrapper.c");
94+
println!("cargo:rerun-if-changed=sqlcipher-entropy.c");
95+
println!("cargo:rerun-if-changed=sqlcipher-ltc.h");
5696
println!("cargo:rerun-if-env-changed=NM");
5797
println!("cargo:rerun-if-env-changed=OBJCOPY");
98+
println!("cargo:rerun-if-env-changed=LD");
99+
println!("cargo:rerun-if-env-changed=AR");
58100
}
59101

60102
fn build(side: &Side<'_>, out: &Path) {
61-
println!("cargo:rerun-if-changed={}", side.include.display());
62-
let mut cc = cc::Build::new();
63-
cc.file(&side.source).include(side.include).warnings(false);
64-
for (name, value) in side.defines {
65-
cc.define(name, *value);
66-
}
67-
instrument(&mut cc);
68-
let objects = cc.compile_intermediates();
69-
let [object] = objects.as_slice() else {
70-
panic!(
103+
let mut objects = Vec::new();
104+
for unit in side.units {
105+
for file in unit.files {
106+
println!("cargo:rerun-if-changed={}", file.display());
107+
}
108+
let mut cc = cc::Build::new();
109+
for file in unit.files {
110+
cc.file(file);
111+
}
112+
for dir in unit.includes {
113+
cc.include(dir);
114+
}
115+
cc.warnings(false);
116+
for (name, value) in unit.defines {
117+
cc.define(name, *value);
118+
}
119+
if let Some(header) = unit.forced_include {
120+
cc.flag("-include").flag(header);
121+
}
122+
instrument(&mut cc);
123+
let unit_objects = cc.compile_intermediates();
124+
assert_eq!(
125+
unit_objects.len(),
126+
unit.files.len(),
71127
"{} compiled to {} objects",
72-
side.source.display(),
73-
objects.len()
128+
unit.files[0].display(),
129+
unit_objects.len()
74130
);
131+
objects.extend(unit_objects);
132+
}
133+
134+
let combined = if objects.len() == 1 {
135+
objects.remove(0)
136+
} else {
137+
// Clang's .group sections break the final link once a partial link merges them.
138+
let linked: Vec<PathBuf> = objects
139+
.iter()
140+
.map(|object| strip_group_sections(object))
141+
.collect();
142+
let combined = out.join(format!("{}.combined.o", side.prefix));
143+
// The per-global asan sections survive a bfd partial link, while a gold one
144+
// drops most of them and ASan then never registers the side's globals.
145+
run(Command::new(tool("LD", "ld"))
146+
.arg("-r")
147+
.arg("-o")
148+
.arg(&combined)
149+
.args(&linked));
150+
combined
75151
};
76152

77153
let map = out.join(format!("{}.redefine", side.prefix));
78-
fs::write(&map, redefinitions(object, side.prefix)).expect("OUT_DIR is writable");
154+
fs::write(&map, redefinitions(&combined, side.prefix)).expect("OUT_DIR is writable");
79155
let renamed = out.join(format!("{}.o", side.prefix));
80156
run(Command::new(tool("OBJCOPY", "objcopy"))
81157
.arg(format!("--redefine-syms={}", map.display()))
82-
.arg(object)
158+
.arg(&combined)
83159
.arg(&renamed));
84160

85161
let archive = out.join(format!("lib{}.a", side.prefix));
86162
let _ = fs::remove_file(&archive);
87-
run(cc.get_archiver().arg("crs").arg(&archive).arg(&renamed));
163+
run(Command::new(tool("AR", "ar"))
164+
.arg("crs")
165+
.arg(&archive)
166+
.arg(&renamed));
88167
println!("cargo:rustc-link-lib=static={}", side.prefix);
89168
}
90169

@@ -118,6 +197,17 @@ fn redefinitions(object: &Path, prefix: &str) -> String {
118197
})
119198
}
120199

200+
/// Returns a copy of `object` without its `.group` section.
201+
fn strip_group_sections(object: &Path) -> PathBuf {
202+
let stem = object.file_stem().expect("object has a stem");
203+
let stripped = object.with_file_name(format!("{}.ng.o", stem.to_string_lossy()));
204+
run(Command::new(tool("OBJCOPY", "objcopy"))
205+
.arg("--remove-section=.group")
206+
.arg(object)
207+
.arg(&stripped));
208+
stripped
209+
}
210+
121211
fn tool(var: &str, default: &str) -> String {
122212
env::var(var).unwrap_or_else(|_| default.to_owned())
123213
}

‎fuzz/sqlcipher-entropy.c‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* SQLCipher's entropy hook, giving its LibTomCrypt RNG the harness's deterministic getentropy. */
2+
#include <stddef.h>
3+
#include <stdlib.h>
4+
5+
#include "sqlcipher-ltc.h"
6+
#include <tomcrypt.h>
7+
8+
/* Declared here, the fuzz harness provides it at link time. */
9+
int getentropy(void *buffer, size_t len);
10+
int sqlcipher_extra_init(const char *arg);
11+
12+
long sqlcipher_wasm_no_clock(void) { abort(); }
13+
14+
/* Fails closed so rng_get_bytes never falls through to the clock. */
15+
static unsigned long sqlcipher_wasm_rng(unsigned char *out, unsigned long len, void (*callback)(void)) {
16+
(void)callback;
17+
if (getentropy(out, len) != 0) abort();
18+
return len;
19+
}
20+
21+
int sqlcipher_wasm_extra_init(const char *arg) {
22+
ltc_rng = sqlcipher_wasm_rng;
23+
return sqlcipher_extra_init(arg);
24+
}

‎fuzz/sqlcipher-ltc.h‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* SQLCipher's one LibTomCrypt configuration, shared by every SQLCipher and LibTomCrypt unit so struct layouts agree. */
2+
#ifndef SQLCIPHER_LTC_H
3+
#define SQLCIPHER_LTC_H
4+
5+
#define LTC_NOTHING
6+
#define LTC_RIJNDAEL
7+
#define LTC_CBC_MODE
8+
#define LTC_SHA1
9+
#define LTC_SHA256
10+
#define LTC_SHA512
11+
#define LTC_HASH_HELPERS
12+
#define LTC_HMAC
13+
#define LTC_FORTUNA
14+
#define LTC_PKCS_5
15+
#define LTC_RNG_GET_BYTES
16+
#define LTC_PRNG_ENABLE_LTC_RNG
17+
#define LTC_NO_TEST
18+
#define LTC_NO_FILE
19+
#define LTC_NO_ASM
20+
#define LTC_NO_MATH
21+
#define LTC_CLEAN_STACK
22+
/* Bad arguments return CRYPT_INVALID_ARG instead of raising a signal, which the fuzzer would count as a crash. */
23+
#define ARGTYPE 4
24+
/* rng_get_bytes always ends in a clock-jitter generator, so reaching it must abort. */
25+
#define XCLOCK sqlcipher_wasm_no_clock
26+
#define XCLOCKS_PER_SEC 1
27+
long sqlcipher_wasm_no_clock(void);
28+
29+
#endif

‎fuzz/sqlcipher-wrapper.c‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* SQLCipher with its LibTomCrypt provider, whose sources build as separate units with the same configuration. */
2+
#define SQLITE_HAS_CODEC 1
3+
#define SQLCIPHER_CRYPTO_LIBTOMCRYPT 1
4+
/* Installs the entropy hook before SQLCipher's own init runs. */
5+
#define SQLITE_EXTRA_INIT sqlcipher_wasm_extra_init
6+
#define SQLITE_EXTRA_SHUTDOWN sqlcipher_extra_shutdown
7+
/* SQLCipher's log needs a timestamp and a device the harness does not keep. */
8+
#define SQLCIPHER_OMIT_LOG 1
9+
#define SQLCIPHER_OMIT_LOG_DEVICE 1
10+
#define OMIT_MEMLOCK 1
11+
/* SQLCipher refuses SQLITE_THREADSAFE=0, and the harness runs single-threaded, so no-op mutexes suffice. */
12+
#undef SQLITE_THREADSAFE
13+
#define SQLITE_THREADSAFE 1
14+
#define SQLITE_MUTEX_NOOP 1
15+
16+
#include "sqlcipher-ltc.h"
17+
#include "sqlcipher.c"

‎fuzz/src/entropy.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Replaces libc's `getentropy`, the call SQLCipher's RNG makes directly (`sqlcipher_wasm_rng` in
2-
//! `WASM_SOURCE_FILE`), with a deterministic stream reset once per [`run`](crate::run::run) call, so
2+
//! `sqlcipher-entropy.c`), with a deterministic stream reset once per [`run`](crate::run::run) call, so
33
//! the same [`Case`](crate::Case) produces the same salts and IVs on every repeat, not just the same
44
//! workload. SQLite3MC's own RNG reads `/dev/urandom` first and falls back to `getentropy` only if
55
//! that open fails, so this does not make SQLite3MC's own writes deterministic; it is enough for any

‎fuzz/src/lib.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ mod ffi;
1313
mod input;
1414
mod run;
1515
pub mod seedgen;
16+
/// The `ASan` default `detect_odr_violation` level (2 since clang 18) reports the two
17+
/// renamed copies of the SQLite amalgamation as a violation, so the harness turns the
18+
/// check off. `ASan` reads its default options from the weak symbol of this name, a
19+
/// function returning the options string.
20+
#[no_mangle]
21+
const extern "C" fn __asan_default_options() -> *const u8 {
22+
c"detect_odr_violation=0".as_ptr().cast::<u8>()
23+
}
1624

1725
pub use input::{
1826
Algorithm, AttachCase, BackupCase, Case, Checkpoint, Cipher, Config, Key, NativeCase,

0 commit comments

Comments
 (0)