initial commit
Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled
Some checks failed
CI / lint (push) Successful in 1m37s
CI / test-python (push) Successful in 1m49s
CI / test-zig (push) Successful in 1m39s
CI / test-wasm (push) Successful in 1m54s
CI / test (push) Successful in 14m44s
CI / miri (push) Successful in 14m18s
CI / build (push) Successful in 1m9s
CI / fuzz-regression (push) Successful in 9m9s
CI / publish (push) Failing after 1m10s
CI / publish-python (push) Failing after 1m46s
CI / publish-wasm (push) Has been cancelled
Signed-off-by: Kamal Tufekcic <kamal@lo.sh>
This commit is contained in:
commit
1d99048c95
165830 changed files with 79062 additions and 0 deletions
|
|
@ -0,0 +1,21 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use soliton_capi::SolitonDecodedSessionInit;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Exercise the CAPI session init decoder with adversarial wire bytes.
|
||||
// Tests length validation, size cap (64 KiB), field parsing, UTF-8
|
||||
// crypto_version allocation, and the free path.
|
||||
let mut out = MaybeUninit::<SolitonDecodedSessionInit>::zeroed();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_kex_decode_session_init(
|
||||
data.as_ptr(), data.len(), out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
if rc == 0 {
|
||||
unsafe {
|
||||
soliton_capi::soliton_decoded_session_init_free(out.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
});
|
||||
52
soliton_capi/fuzz/fuzz_targets/fuzz_capi_dm_queue_decrypt.rs
Normal file
52
soliton_capi/fuzz/fuzz_targets/fuzz_capi_dm_queue_decrypt.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use soliton_capi::{SolitonBuf, SolitonKeyRing};
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
const FUZZ_KEY: [u8; 32] = [0x42; 32];
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Exercise the CAPI DM queue decryption path with adversarial input.
|
||||
// Covers attack surface beyond the core fuzz target: check_len! on
|
||||
// recipient_fp_len, cstr_to_str batch_id parsing (interior NUL),
|
||||
// null-pointer guards, MAX_BLOB_LEN cap, keyring reentrancy guard.
|
||||
let mut keyring: *mut SolitonKeyRing = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_keyring_new(FUZZ_KEY.as_ptr(), 32, 1, &mut keyring)
|
||||
};
|
||||
if rc != 0 || keyring.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split fuzz input: first byte selects recipient_fp_len to exercise
|
||||
// check_len! with invalid sizes, remaining bytes are the blob.
|
||||
if data.is_empty() {
|
||||
unsafe { soliton_capi::soliton_keyring_free(&mut keyring) };
|
||||
return;
|
||||
}
|
||||
let fp_len = data[0] as usize;
|
||||
let blob = &data[1..];
|
||||
|
||||
// Fixed recipient fingerprint — the fp_len variation tests the
|
||||
// check_len! guard path, not fingerprint content.
|
||||
let recipient_fp = [0xAAu8; 32];
|
||||
let batch_id = CString::new("fuzz-batch").unwrap();
|
||||
let mut plaintext_out = SolitonBuf { ptr: ptr::null_mut(), len: 0 };
|
||||
|
||||
let _ = unsafe {
|
||||
soliton_capi::soliton_dm_queue_decrypt(
|
||||
keyring,
|
||||
blob.as_ptr(), blob.len(),
|
||||
recipient_fp.as_ptr(), fp_len,
|
||||
batch_id.as_ptr(),
|
||||
&mut plaintext_out,
|
||||
)
|
||||
};
|
||||
|
||||
if !plaintext_out.ptr.is_null() {
|
||||
unsafe { soliton_capi::soliton_buf_free(&mut plaintext_out) };
|
||||
}
|
||||
|
||||
unsafe { soliton_capi::soliton_keyring_free(&mut keyring) };
|
||||
});
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use soliton_capi::SolitonRatchet;
|
||||
use std::ptr;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Exercise the CAPI ratchet deserialization path with adversarial input.
|
||||
// Tests null-pointer guards, length validation, magic number setup,
|
||||
// CAS guard handling, and proper cleanup on both success and failure paths.
|
||||
let mut ratchet: *mut SolitonRatchet = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_ratchet_from_bytes(data.as_ptr(), data.len(), &mut ratchet)
|
||||
};
|
||||
if rc == 0 && !ratchet.is_null() {
|
||||
unsafe { soliton_capi::soliton_ratchet_free(&mut ratchet); }
|
||||
}
|
||||
});
|
||||
41
soliton_capi/fuzz/fuzz_targets/fuzz_capi_storage_decrypt.rs
Normal file
41
soliton_capi/fuzz/fuzz_targets/fuzz_capi_storage_decrypt.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use soliton_capi::{SolitonBuf, SolitonKeyRing};
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
const FUZZ_KEY: [u8; 32] = [0x42; 32];
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Exercise the CAPI storage decryption path with adversarial blob input.
|
||||
// A valid keyring is constructed once; the fuzz input is the encrypted blob.
|
||||
// Tests version routing, flag validation, AEAD tag check, decompression
|
||||
// bounds, and SolitonBuf output allocation.
|
||||
let mut keyring: *mut SolitonKeyRing = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_keyring_new(FUZZ_KEY.as_ptr(), 32, 1, &mut keyring)
|
||||
};
|
||||
if rc != 0 || keyring.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let channel = CString::new("fuzz-ch").unwrap();
|
||||
let segment = CString::new("fuzz-seg").unwrap();
|
||||
let mut plaintext_out = SolitonBuf { ptr: ptr::null_mut(), len: 0 };
|
||||
|
||||
let _ = unsafe {
|
||||
soliton_capi::soliton_storage_decrypt(
|
||||
keyring,
|
||||
data.as_ptr(), data.len(),
|
||||
channel.as_ptr(),
|
||||
segment.as_ptr(),
|
||||
&mut plaintext_out,
|
||||
)
|
||||
};
|
||||
|
||||
if !plaintext_out.ptr.is_null() {
|
||||
unsafe { soliton_capi::soliton_buf_free(&mut plaintext_out); }
|
||||
}
|
||||
|
||||
unsafe { soliton_capi::soliton_keyring_free(&mut keyring); }
|
||||
});
|
||||
53
soliton_capi/fuzz/fuzz_targets/fuzz_capi_stream_decrypt.rs
Normal file
53
soliton_capi/fuzz/fuzz_targets/fuzz_capi_stream_decrypt.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::ptr;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Exercise the CAPI streaming decryption path with adversarial input.
|
||||
// Tests header validation, AEAD rejection, decompression bounds,
|
||||
// and output buffer handling through the FFI boundary.
|
||||
if data.len() < 26 {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = [0x42u8; 32];
|
||||
let header = &data[..26];
|
||||
let rest = &data[26..];
|
||||
|
||||
let mut dec: *mut soliton_capi::SolitonStreamDecryptor = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_stream_decrypt_init(
|
||||
key.as_ptr(), 32,
|
||||
header.as_ptr(), 26,
|
||||
ptr::null(), 0,
|
||||
&mut dec,
|
||||
)
|
||||
};
|
||||
if rc != 0 || dec.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Feed remaining bytes as chunks in 2048-byte slices.
|
||||
// 1 MiB chunk size + 256-byte zstd overhead margin — matches STREAM_CHUNK_SIZE.
|
||||
let out_cap = 1_048_576 + 256;
|
||||
let mut out_buf = vec![0u8; out_cap];
|
||||
|
||||
for chunk_data in rest.chunks(2048) {
|
||||
let mut out_written: usize = 0;
|
||||
let mut is_final: bool = false;
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_stream_decrypt_chunk(
|
||||
dec,
|
||||
chunk_data.as_ptr(), chunk_data.len(),
|
||||
out_buf.as_mut_ptr(), out_cap,
|
||||
&mut out_written,
|
||||
&mut is_final,
|
||||
)
|
||||
};
|
||||
if rc != 0 || is_final {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { soliton_capi::soliton_stream_decrypt_free(&mut dec); }
|
||||
});
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::ptr;
|
||||
|
||||
// Exercise the CAPI random-access streaming decryption path with adversarial
|
||||
// input. Unlike the sequential fuzz_capi_stream_decrypt target, this tests
|
||||
// index-based nonce derivation — the fuzzer controls both the chunk data and
|
||||
// the chunk index, exploring index arithmetic edge cases (overflow, large
|
||||
// gaps, u64::MAX boundary) that sequential processing never reaches.
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Minimum: 26 (header) + 8 (index) + 1 (chunk data).
|
||||
if data.len() < 35 {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = [0x42u8; 32];
|
||||
let header = &data[..26];
|
||||
let index_bytes = &data[26..34];
|
||||
let chunk_data = &data[34..];
|
||||
|
||||
// Fuzz-controlled index — exercises nonce derivation for arbitrary positions.
|
||||
let index = u64::from_le_bytes(index_bytes.try_into().unwrap());
|
||||
|
||||
let mut dec: *mut soliton_capi::SolitonStreamDecryptor = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_stream_decrypt_init(
|
||||
key.as_ptr(), 32,
|
||||
header.as_ptr(), 26,
|
||||
ptr::null(), 0,
|
||||
&mut dec,
|
||||
)
|
||||
};
|
||||
if rc != 0 || dec.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1 MiB + 256-byte zstd overhead margin.
|
||||
let out_cap = 1_048_576 + 256;
|
||||
let mut out_buf = vec![0u8; out_cap];
|
||||
let mut out_written: usize = 0;
|
||||
let mut is_final: bool = false;
|
||||
|
||||
// Single chunk at a fuzz-controlled index.
|
||||
let _ = unsafe {
|
||||
soliton_capi::soliton_stream_decrypt_chunk_at(
|
||||
dec,
|
||||
index,
|
||||
chunk_data.as_ptr(), chunk_data.len(),
|
||||
out_buf.as_mut_ptr(), out_cap,
|
||||
&mut out_written,
|
||||
&mut is_final,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { soliton_capi::soliton_stream_decrypt_free(&mut dec); }
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use std::ptr;
|
||||
|
||||
// Exercise the CAPI random-access streaming encryption path with adversarial
|
||||
// input. Tests index-based nonce derivation on the encryption side — the
|
||||
// fuzzer controls the chunk index and plaintext, exploring nonce arithmetic
|
||||
// edge cases and output buffer sizing for non-standard chunk positions.
|
||||
//
|
||||
// Non-final chunks must be exactly STREAM_CHUNK_SIZE (1 MiB). With libFuzzer's
|
||||
// default max_len of 4096, plaintext is always shorter — so is_last is forced
|
||||
// true for short inputs. The fuzz byte only controls is_last when plaintext
|
||||
// happens to be exactly 1 MiB (requires -max_len=1048585 to reach).
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
// Minimum: 8 (index) + 1 (is_last flag) + 0 (plaintext can be empty).
|
||||
if data.len() < 9 {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = [0x42u8; 32];
|
||||
|
||||
let index = u64::from_le_bytes(data[..8].try_into().unwrap());
|
||||
let plaintext = &data[9..];
|
||||
|
||||
// Non-final chunks require exactly 1,048,576 bytes of plaintext.
|
||||
// Force is_last=true for any other size so the fuzzer can explore
|
||||
// the success path with short inputs. The fuzz byte only takes
|
||||
// effect when plaintext happens to be exactly STREAM_CHUNK_SIZE.
|
||||
const STREAM_CHUNK_SIZE: usize = 1_048_576;
|
||||
let is_last = if plaintext.len() == STREAM_CHUNK_SIZE {
|
||||
data[8] & 1 != 0
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let mut enc: *mut soliton_capi::SolitonStreamEncryptor = ptr::null_mut();
|
||||
let rc = unsafe {
|
||||
soliton_capi::soliton_stream_encrypt_init(
|
||||
key.as_ptr(), 32,
|
||||
ptr::null(), 0,
|
||||
false, // compress
|
||||
&mut enc,
|
||||
)
|
||||
};
|
||||
if rc != 0 || enc.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
// SOLITON_STREAM_ENCRYPT_MAX = 1_048_849.
|
||||
let out_cap = 1_048_849;
|
||||
let mut out_buf = vec![0u8; out_cap];
|
||||
let mut out_written: usize = 0;
|
||||
|
||||
let _ = unsafe {
|
||||
soliton_capi::soliton_stream_encrypt_chunk_at(
|
||||
enc,
|
||||
index,
|
||||
plaintext.as_ptr(), plaintext.len(),
|
||||
is_last,
|
||||
out_buf.as_mut_ptr(), out_cap,
|
||||
&mut out_written,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe { soliton_capi::soliton_stream_encrypt_free(&mut enc); }
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue