mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-27 13:53:31 +01:00
hashx: Rust hook for inspecting and modifying the random number stream
This patch has no effect on the C tor build. Adds a function hashx_rng_callback() to the hashx API, defined only when HASHX_RNG_CALLBACK is defined. This is then used in the Rust wrapper to implement a similar rng_callback(). Included some minimal test cases. This code is intented for use in cross-compatibility fuzzing tests which drive multiple implementations of hashx with the same custom Rng stream. Signed-off-by: Micah Elizabeth Scott <beth@torproject.org>
This commit is contained in:
parent
4667195ded
commit
0ca2e62b28
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "tor-c-equix"
|
name = "tor-c-equix"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "LGPL-3.0-only"
|
license = "LGPL-3.0-only"
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ fn main() {
|
|||||||
"hashx/src/siphash_rng.c",
|
"hashx/src/siphash_rng.c",
|
||||||
"hashx/src/virtual_memory.c",
|
"hashx/src/virtual_memory.c",
|
||||||
])
|
])
|
||||||
|
// Activate our patch for hashx_rng_callback
|
||||||
|
.define("HASHX_RNG_CALLBACK", "1")
|
||||||
// Equi-X always uses HashX size 8 (64-bit output)
|
// Equi-X always uses HashX size 8 (64-bit output)
|
||||||
.define("HASHX_SIZE", "8")
|
.define("HASHX_SIZE", "8")
|
||||||
// Avoid shared library API declarations, link statically
|
// Avoid shared library API declarations, link statically
|
||||||
@ -31,6 +33,7 @@ fn main() {
|
|||||||
.header_contents(
|
.header_contents(
|
||||||
"wrapper.h",
|
"wrapper.h",
|
||||||
r#"
|
r#"
|
||||||
|
#define HASHX_RNG_CALLBACK 1
|
||||||
#define HASHX_SIZE 8
|
#define HASHX_SIZE 8
|
||||||
#define HASHX_SHARED 1
|
#define HASHX_SHARED 1
|
||||||
#define EQUIX_SHARED 1
|
#define EQUIX_SHARED 1
|
||||||
|
@ -169,6 +169,25 @@ HASHX_API hashx_result hashx_exec(const hashx_ctx* ctx,
|
|||||||
*/
|
*/
|
||||||
HASHX_API void hashx_free(hashx_ctx* ctx);
|
HASHX_API void hashx_free(hashx_ctx* ctx);
|
||||||
|
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
/*
|
||||||
|
* Set a callback for inspecting or modifying the HashX random number stream.
|
||||||
|
*
|
||||||
|
* The callback and its user pointer are associated with the provided context
|
||||||
|
* even if it's re-used for another hash program. A callback value of NULL
|
||||||
|
* disables the callback.
|
||||||
|
*
|
||||||
|
* @param ctx is pointer to a HashX instance.
|
||||||
|
* @param callback is invoked after each new 64-bit pseudorandom value
|
||||||
|
* is generated in a buffer. The callback may record it and/or replace
|
||||||
|
* it. A NULL pointer here disables the callback.
|
||||||
|
* @param user_data is an opaque parameter given to the callback
|
||||||
|
*/
|
||||||
|
HASHX_API void hashx_rng_callback(hashx_ctx* ctx,
|
||||||
|
void (*callback)(uint64_t*, void*),
|
||||||
|
void* user_data);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -55,3 +55,13 @@ void hashx_free(hashx_ctx* ctx) {
|
|||||||
free(ctx);
|
free(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
void hashx_rng_callback(hashx_ctx* ctx,
|
||||||
|
void (*callback)(uint64_t*, void*),
|
||||||
|
void* callback_user_data)
|
||||||
|
{
|
||||||
|
ctx->program.rng_callback = callback;
|
||||||
|
ctx->program.rng_callback_user_data = callback_user_data;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
@ -554,6 +554,10 @@ bool hashx_program_generate(const siphash_state* key, hashx_program* program) {
|
|||||||
.ports = {{ 0 }}
|
.ports = {{ 0 }}
|
||||||
};
|
};
|
||||||
hashx_siphash_rng_init(&ctx.gen, key);
|
hashx_siphash_rng_init(&ctx.gen, key);
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
ctx.gen.callback = program->rng_callback;
|
||||||
|
ctx.gen.callback_user_data = program->rng_callback_user_data;
|
||||||
|
#endif
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
ctx.registers[i].last_op = -1;
|
ctx.registers[i].last_op = -1;
|
||||||
ctx.registers[i].latency = 0;
|
ctx.registers[i].latency = 0;
|
||||||
|
@ -29,6 +29,10 @@ typedef struct hashx_program {
|
|||||||
int branch_count;
|
int branch_count;
|
||||||
int branches[16];
|
int branches[16];
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
void (*rng_callback)(uint64_t *buffer, void *user_data);
|
||||||
|
void *rng_callback_user_data;
|
||||||
|
#endif
|
||||||
} hashx_program;
|
} hashx_program;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -15,6 +15,11 @@ uint8_t hashx_siphash_rng_u8(siphash_rng* gen) {
|
|||||||
gen->buffer8 = hashx_siphash13_ctr(gen->counter, &gen->keys);
|
gen->buffer8 = hashx_siphash13_ctr(gen->counter, &gen->keys);
|
||||||
gen->counter++;
|
gen->counter++;
|
||||||
gen->count8 = sizeof(gen->buffer8);
|
gen->count8 = sizeof(gen->buffer8);
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
if (gen->callback) {
|
||||||
|
gen->callback(&gen->buffer8, gen->callback_user_data);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
gen->count8--;
|
gen->count8--;
|
||||||
return gen->buffer8 >> (gen->count8 * 8);
|
return gen->buffer8 >> (gen->count8 * 8);
|
||||||
@ -25,6 +30,11 @@ uint32_t hashx_siphash_rng_u32(siphash_rng* gen) {
|
|||||||
gen->buffer32 = hashx_siphash13_ctr(gen->counter, &gen->keys);
|
gen->buffer32 = hashx_siphash13_ctr(gen->counter, &gen->keys);
|
||||||
gen->counter++;
|
gen->counter++;
|
||||||
gen->count32 = sizeof(gen->buffer32) / sizeof(uint32_t);
|
gen->count32 = sizeof(gen->buffer32) / sizeof(uint32_t);
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
if (gen->callback) {
|
||||||
|
gen->callback(&gen->buffer32, gen->callback_user_data);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
gen->count32--;
|
gen->count32--;
|
||||||
return (uint32_t)(gen->buffer32 >> (gen->count32 * 32));
|
return (uint32_t)(gen->buffer32 >> (gen->count32 * 32));
|
||||||
|
@ -13,6 +13,10 @@ typedef struct siphash_rng {
|
|||||||
uint64_t counter;
|
uint64_t counter;
|
||||||
uint64_t buffer8, buffer32;
|
uint64_t buffer8, buffer32;
|
||||||
unsigned count8, count32;
|
unsigned count8, count32;
|
||||||
|
#ifdef HASHX_RNG_CALLBACK
|
||||||
|
void (*callback)(uint64_t *buffer, void *user_data);
|
||||||
|
void *callback_user_data;
|
||||||
|
#endif
|
||||||
} siphash_rng;
|
} siphash_rng;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
//! See `LICENSE` for licensing information.
|
//! See `LICENSE` for licensing information.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use core::ffi::c_void;
|
||||||
|
use core::mem;
|
||||||
|
use core::ptr::null_mut;
|
||||||
|
|
||||||
pub mod ffi {
|
pub mod ffi {
|
||||||
//! Low-level access to the C API
|
//! Low-level access to the C API
|
||||||
|
|
||||||
@ -34,8 +38,14 @@ pub const HASHX_SIZE: usize = ffi::HASHX_SIZE as usize;
|
|||||||
/// Output value obtained by executing a HashX hash function
|
/// Output value obtained by executing a HashX hash function
|
||||||
pub type HashXOutput = [u8; HASHX_SIZE];
|
pub type HashXOutput = [u8; HASHX_SIZE];
|
||||||
|
|
||||||
|
/// Type for callback functions that inspect or replace the pseudorandom stream
|
||||||
|
pub type RngCallback = Box<dyn FnMut(u64) -> u64>;
|
||||||
|
|
||||||
/// Safe wrapper around a HashX context
|
/// Safe wrapper around a HashX context
|
||||||
pub struct HashX(*mut ffi::hashx_ctx);
|
pub struct HashX {
|
||||||
|
ctx: *mut ffi::hashx_ctx,
|
||||||
|
rng_callback: Option<RngCallback>,
|
||||||
|
}
|
||||||
|
|
||||||
impl HashX {
|
impl HashX {
|
||||||
/// Allocate a new HashX context
|
/// Allocate a new HashX context
|
||||||
@ -44,7 +54,10 @@ impl HashX {
|
|||||||
if ctx.is_null() {
|
if ctx.is_null() {
|
||||||
panic!("out of memory in hashx_alloc");
|
panic!("out of memory in hashx_alloc");
|
||||||
}
|
}
|
||||||
Self(ctx)
|
Self {
|
||||||
|
ctx,
|
||||||
|
rng_callback: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new hash function within this context, using the given seed
|
/// Create a new hash function within this context, using the given seed
|
||||||
@ -53,14 +66,15 @@ impl HashX {
|
|||||||
/// error occurs while the interpreter is disabled.
|
/// error occurs while the interpreter is disabled.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn make(&mut self, seed: &[u8]) -> HashXResult {
|
pub fn make(&mut self, seed: &[u8]) -> HashXResult {
|
||||||
unsafe { ffi::hashx_make(self.0, seed.as_ptr() as *const std::ffi::c_void, seed.len()) }
|
unsafe { ffi::hashx_make(self.ctx, seed.as_ptr() as *const c_void, seed.len()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check which implementation was selected by `make`
|
/// Check which implementation was selected by `make`
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn query_type(&mut self) -> Result<HashXType, HashXResult> {
|
pub fn query_type(&mut self) -> Result<HashXType, HashXResult> {
|
||||||
let mut buffer = HashXType::HASHX_TYPE_INTERPRETED; // Arbitrary default
|
let mut buffer = HashXType::HASHX_TYPE_INTERPRETED; // Arbitrary default
|
||||||
let result = unsafe { ffi::hashx_query_type(self.0, &mut buffer as *mut ffi::hashx_type) };
|
let result =
|
||||||
|
unsafe { ffi::hashx_query_type(self.ctx, &mut buffer as *mut ffi::hashx_type) };
|
||||||
match result {
|
match result {
|
||||||
HashXResult::HASHX_OK => Ok(buffer),
|
HashXResult::HASHX_OK => Ok(buffer),
|
||||||
e => Err(e),
|
e => Err(e),
|
||||||
@ -71,23 +85,45 @@ impl HashX {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn exec(&mut self, input: u64) -> Result<HashXOutput, HashXResult> {
|
pub fn exec(&mut self, input: u64) -> Result<HashXOutput, HashXResult> {
|
||||||
let mut buffer: HashXOutput = Default::default();
|
let mut buffer: HashXOutput = Default::default();
|
||||||
let result = unsafe {
|
let result =
|
||||||
ffi::hashx_exec(
|
unsafe { ffi::hashx_exec(self.ctx, input, &mut buffer as *mut u8 as *mut c_void) };
|
||||||
self.0,
|
|
||||||
input,
|
|
||||||
&mut buffer as *mut u8 as *mut std::ffi::c_void,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
match result {
|
match result {
|
||||||
HashXResult::HASHX_OK => Ok(buffer),
|
HashXResult::HASHX_OK => Ok(buffer),
|
||||||
e => Err(e),
|
e => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a callback function that may inspect and/or modify the internal
|
||||||
|
/// pseudorandom number stream used by this context.
|
||||||
|
///
|
||||||
|
/// The function will be owned by this context, and it replaces any
|
||||||
|
/// previous function that may have been set. Returns the previous callback
|
||||||
|
/// if any.
|
||||||
|
pub fn rng_callback(&mut self, callback: Option<RngCallback>) -> Option<RngCallback> {
|
||||||
|
// Keep ownership of our Rust value in the context wrapper, to match
|
||||||
|
// the lifetime of the mutable pointer that the C API saves.
|
||||||
|
let result = mem::replace(&mut self.rng_callback, callback);
|
||||||
|
match &mut self.rng_callback {
|
||||||
|
None => unsafe { ffi::hashx_rng_callback(self.ctx, None, null_mut()) },
|
||||||
|
Some(callback) => unsafe {
|
||||||
|
ffi::hashx_rng_callback(
|
||||||
|
self.ctx,
|
||||||
|
Some(wrapper),
|
||||||
|
callback as *mut RngCallback as *mut c_void,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unsafe extern "C" fn wrapper(buffer: *mut u64, callback: *mut c_void) {
|
||||||
|
let callback: &mut RngCallback = unsafe { mem::transmute(callback) };
|
||||||
|
buffer.write(callback(buffer.read()));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for HashX {
|
impl Drop for HashX {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let ctx = std::mem::replace(&mut self.0, std::ptr::null_mut());
|
let ctx = mem::replace(&mut self.ctx, null_mut());
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::hashx_free(ctx);
|
ffi::hashx_free(ctx);
|
||||||
}
|
}
|
||||||
@ -146,7 +182,7 @@ impl EquiX {
|
|||||||
unsafe {
|
unsafe {
|
||||||
ffi::equix_verify(
|
ffi::equix_verify(
|
||||||
self.0,
|
self.0,
|
||||||
challenge.as_ptr() as *const std::ffi::c_void,
|
challenge.as_ptr() as *const c_void,
|
||||||
challenge.len(),
|
challenge.len(),
|
||||||
solution as *const ffi::equix_solution,
|
solution as *const ffi::equix_solution,
|
||||||
)
|
)
|
||||||
@ -159,7 +195,7 @@ impl EquiX {
|
|||||||
unsafe {
|
unsafe {
|
||||||
ffi::equix_solve(
|
ffi::equix_solve(
|
||||||
self.0,
|
self.0,
|
||||||
challenge.as_ptr() as *const std::ffi::c_void,
|
challenge.as_ptr() as *const c_void,
|
||||||
challenge.len(),
|
challenge.len(),
|
||||||
buffer as *mut ffi::equix_solutions_buffer,
|
buffer as *mut ffi::equix_solutions_buffer,
|
||||||
)
|
)
|
||||||
@ -169,7 +205,7 @@ impl EquiX {
|
|||||||
|
|
||||||
impl Drop for EquiX {
|
impl Drop for EquiX {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let ctx = std::mem::replace(&mut self.0, std::ptr::null_mut());
|
let ctx = mem::replace(&mut self.0, null_mut());
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::equix_free(ctx);
|
ffi::equix_free(ctx);
|
||||||
}
|
}
|
||||||
@ -180,6 +216,8 @@ impl Drop for EquiX {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use hex_literal::hex;
|
use hex_literal::hex;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn equix_context() {
|
fn equix_context() {
|
||||||
@ -290,4 +328,52 @@ mod tests {
|
|||||||
assert_eq!(ctx.exec(123456), Ok(hex!("ab3d155bf4bbb0aa")));
|
assert_eq!(ctx.exec(123456), Ok(hex!("ab3d155bf4bbb0aa")));
|
||||||
assert_eq!(ctx.exec(987654321123456789), Ok(hex!("8dfef0497c323274")));
|
assert_eq!(ctx.exec(987654321123456789), Ok(hex!("8dfef0497c323274")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rng_callback_read() {
|
||||||
|
// Use a Rng callback to read the sequence of pseudorandom numbers
|
||||||
|
// without changing them, and spot check the list we get back.
|
||||||
|
let mut ctx = HashX::new(HashXType::HASHX_TRY_COMPILE);
|
||||||
|
let seq = Arc::new(RefCell::new(Vec::new()));
|
||||||
|
{
|
||||||
|
let seq = seq.clone();
|
||||||
|
ctx.rng_callback(Some(Box::new(move |value| {
|
||||||
|
seq.borrow_mut().push(value);
|
||||||
|
value
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
assert_eq!(seq.borrow().len(), 0);
|
||||||
|
assert_eq!(ctx.make(b"abc"), HashXResult::HASHX_OK);
|
||||||
|
assert_eq!(ctx.exec(12345).unwrap(), hex!("c0bc95da7cc30f37"));
|
||||||
|
assert_eq!(seq.borrow().len(), 563);
|
||||||
|
assert_eq!(
|
||||||
|
seq.borrow()[..4],
|
||||||
|
[
|
||||||
|
0xf695edd02205449d,
|
||||||
|
0x51c1ac51cd19a7d1,
|
||||||
|
0xadf4cb303b9814cf,
|
||||||
|
0x79793a52d965083d
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rng_callback_replace() {
|
||||||
|
// Use a Rng callback to replace the random number stream.
|
||||||
|
// We have to choose the replacement somewhat carefully since
|
||||||
|
// many stationary replacement values will cause infinite loops.
|
||||||
|
let mut ctx = HashX::new(HashXType::HASHX_TYPE_INTERPRETED);
|
||||||
|
let counter = Arc::new(RefCell::new(0u32));
|
||||||
|
{
|
||||||
|
let counter = counter.clone();
|
||||||
|
ctx.rng_callback(Some(Box::new(move |_value| {
|
||||||
|
*counter.borrow_mut() += 1;
|
||||||
|
0x0807060504030201
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
assert_eq!(*counter.borrow(), 0);
|
||||||
|
assert_eq!(ctx.make(b"abc"), HashXResult::HASHX_OK);
|
||||||
|
assert_eq!(ctx.exec(12345).unwrap(), hex!("825a9b6dd5d074af"));
|
||||||
|
assert_eq!(*counter.borrow(), 575);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user