Squashed 'vendor/ruvector/' content from commit b64c2172

git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
commit d803bfe2b1
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,440 @@
# RuVector Security Audit Report
**Date:** 2026-01-18
**Auditor:** Security Review Agent
**Scope:** Comprehensive security audit of the RuVector vector database
**Version:** 0.1.32
---
## Executive Summary
This security audit examines the RuVector codebase for potential vulnerabilities in memory safety, input validation, SIMD operations, WASM security, and dependencies. The audit identified several areas of concern and provides recommendations for security hardening.
### Risk Summary
| Category | Critical | High | Medium | Low | Info |
|----------|----------|------|--------|-----|------|
| Memory Safety | 0 | 2 | 3 | 4 | 2 |
| Input Validation | 0 | 1 | 2 | 3 | 1 |
| SIMD Operations | 0 | 1 | 2 | 2 | 3 |
| WASM Security | 0 | 2 | 3 | 2 | 2 |
| Dependencies | 0 | 0 | 1 | 2 | 2 |
---
## 1. Unsafe Code Review
### 1.1 SIMD Intrinsics (`crates/ruvector-core/src/simd_intrinsics.rs`)
**Status:** Generally Well-Protected
**Positive Findings:**
- All unsafe SIMD functions include length assertions before pointer operations
- Safety comments present (e.g., "SECURITY: Ensure both arrays have the same length")
- Proper use of `#[target_feature(enable = "...")]` attributes
- Fallback scalar implementations available for all operations
**Code Example (Good Practice):**
```rust
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn euclidean_distance_avx2_impl(a: &[f32], b: &[f32]) -> f32 {
// SECURITY: Ensure both arrays have the same length to prevent out-of-bounds access
assert_eq!(a.len(), b.len(), "Input arrays must have the same length");
// ...
}
```
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| SIMD-001 | Medium | Missing `#[deny(unsafe_op_in_unsafe_fn)]` lint | Module level |
| SIMD-002 | Low | No bounds checking in remainder loops | Lines 88-91, 127-130 |
| SIMD-003 | Info | Uses `std::mem::transmute` for horizontal sum | Lines 84, 284, 364-366 |
**Recommendations:**
1. Add `#![deny(unsafe_op_in_unsafe_fn)]` at the module level
2. Add explicit bounds checks in remainder loops with `get()` or `assert!`
3. Document the transmute usage with safety invariant comments
### 1.2 Arena Allocator (`crates/ruvector-core/src/arena.rs`)
**Status:** Well-Protected with Security Checks
**Positive Findings:**
- Size overflow checks using `checked_add()`
- Alignment validation (power of 2 check)
- Maximum allocation size validation
- Bounds checking before pointer arithmetic
- Null pointer checks in `ArenaVec`
**Code Example (Good Practice):**
```rust
// SECURITY: Validate alignment is a power of 2 and size is reasonable
assert!(align > 0 && align.is_power_of_two(), "Alignment must be a power of 2");
assert!(size > 0, "Cannot allocate zero bytes");
assert!(size <= isize::MAX as usize, "Allocation size too large");
```
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| ARENA-001 | Medium | RefCell not thread-safe, marked issues with thread_arena | Lines 219-232 |
| ARENA-002 | Low | No maximum chunk count limit | `alloc_raw()` |
### 1.3 Cache-Optimized Storage (`crates/ruvector-core/src/cache_optimized.rs`)
**Status:** Good with Security Constants
**Positive Findings:**
- `MAX_DIMENSIONS` limit (65536) prevents DoS
- `MAX_CAPACITY` limit (~16M vectors) prevents memory exhaustion
- Checked arithmetic for all size calculations
- Explicit overflow panic messages
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| CACHE-001 | High | `unsafe impl Send/Sync` without verification | Lines 229-230 |
| CACHE-002 | Medium | No bounds check in `dimension_slice` before unsafe | Lines 112-115 |
**Recommendation for CACHE-001:**
```rust
// The raw pointer is exclusively owned and only accessed through
// properly synchronized methods. The storage is only modified through
// &mut self methods, ensuring exclusive access.
// SAFETY: The data pointer is valid for the lifetime of this struct,
// all writes are synchronized through &mut self, and reads are
// protected by the count field which is only incremented atomically.
unsafe impl Send for SoAVectorStorage {}
unsafe impl Sync for SoAVectorStorage {}
```
### 1.4 Micro-HNSW WASM (`crates/micro-hnsw-wasm/src/lib.rs`)
**Status:** High Risk - Extensive Unsafe Code
This is a `#![no_std]` WASM module with 50+ unsafe blocks using static mutable state.
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| WASM-001 | High | Static mutable state without synchronization | Lines 90-139 |
| WASM-002 | High | No bounds validation on external inputs | `insert()`, `search()` |
| WASM-003 | Medium | Raw pointer returned to caller without lifetime | `get_*_ptr()` functions |
| WASM-004 | Medium | No epoch timeout or resource limits | Global state |
| WASM-005 | Low | Panic handler is infinite loop | Line 1262 |
**Critical Code Pattern:**
```rust
// UNSAFE: Static mutable state accessed without synchronization
static mut HNSW: MicroHnsw = MicroHnsw { ... };
static mut QUERY: [f32; MAX_DIMS] = [0.0; MAX_DIMS];
static mut INSERT: [f32; MAX_DIMS] = [0.0; MAX_DIMS];
```
**Recommendations:**
1. Add input validation for all external entry points
2. Consider using atomic operations or mutex for state
3. Add memory limits and timeout mechanisms
4. Add `#[deny(unsafe_op_in_unsafe_fn)]`
---
## 2. Memory Safety Analysis
### 2.1 Buffer Overflow Analysis
**SIMD Operations:**
- All SIMD functions process data in chunks (4 or 8 elements)
- Remainder handling uses safe indexing
- No buffer overflows detected in current implementation
**Vector Operations:**
- `dimension_slice()` uses assertion for bounds check
- `push()` operations check capacity before writing
### 2.2 Integer Overflow Analysis
**Positive Findings:**
- Product Quantization validates `codebook_size > 256`
- SoAVectorStorage uses `checked_mul()` for size calculations
- Arena allocator uses `checked_add()` for offset calculations
**Potential Issue:**
```rust
// In ProductQuantized::train()
let subspace_dim = dimensions / num_subspaces;
// If num_subspaces > dimensions, this could be 0, leading to issues
```
**Recommendation:** Add validation for `num_subspaces <= dimensions`
### 2.3 Use-After-Free Analysis
**No vulnerabilities detected.** The codebase uses:
- Rust's ownership system
- Proper Drop implementations
- Arena-based allocation with explicit lifetimes
---
## 3. Input Validation
### 3.1 Vector Dimension Validation
**Positive Findings:**
- `MAX_VECTOR_DIMENSIONS = 65536` in WASM bindings
- Dimension mismatch returns proper errors
**WASM Module (`ruvector-wasm/src/lib.rs`):**
```rust
// Security: Validate vector dimensions before allocation
let vec_len = vector.length() as usize;
if vec_len == 0 {
return Err(JsValue::from_str("Vector cannot be empty"));
}
if vec_len > MAX_VECTOR_DIMENSIONS {
return Err(JsValue::from_str(&format!(
"Vector dimensions {} exceed maximum allowed {}",
vec_len, MAX_VECTOR_DIMENSIONS
)));
}
```
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| INPUT-001 | High | micro-hnsw-wasm has no dimension validation | `insert()` |
| INPUT-002 | Medium | No validation of `k` parameter in search | Multiple locations |
| INPUT-003 | Low | Empty vector handling varies by module | Multiple |
### 3.2 Quantization Parameters
**Positive Findings:**
- `codebook_size > 256` validation exists
- Empty vector validation in `ProductQuantized::train()`
**Identified Issues:**
| ID | Severity | Issue | Location |
|----|----------|-------|----------|
| QUANT-001 | Medium | No validation for `iterations` parameter | `kmeans_clustering()` |
| QUANT-002 | Low | Scale calculation could be 0 (handled) | `ScalarQuantized::quantize()` |
---
## 4. WASM Security Analysis
### 4.1 Main WASM Module (`ruvector-wasm`)
**Status:** Good Security Posture
**Positive Findings:**
- Uses `console_error_panic_hook` for debugging
- Input validation for vector dimensions
- Proper error handling with `WasmError` type
- IndexedDB operations properly async
### 4.2 Micro-HNSW WASM Module
**Status:** High Risk
**Security Concerns:**
1. **No Signature Validation:** Module exposes raw function pointers without verification
2. **No Epoch Timeouts:** Long-running operations cannot be interrupted
3. **Shared Memory:** Static mutable state is vulnerable to data races
4. **No Resource Limits:** Memory allocation is unbounded within MAX_VECTORS
**Recommendations:**
1. Implement resource quotas
2. Add timeout mechanisms for search operations
3. Consider WebAssembly Component Model for better isolation
4. Add input sanitization for all exported functions
### 4.3 Other WASM Modules
| Module | Risk Level | Notes |
|--------|------------|-------|
| ruvector-attention-wasm | Low | Standard WASM bindings |
| ruvector-mincut-wasm | Medium | Contains SIMD operations |
| ruvector-learning-wasm | Low | Standard bindings |
| ruvector-nervous-system-wasm | Low | Standard bindings |
---
## 5. Dependency Audit
### 5.1 Audit Status
**Note:** `cargo-audit` is not installed. Recommend installing and running:
```bash
cargo install cargo-audit
cargo audit
```
### 5.2 Key Dependencies Analysis
| Dependency | Version | Risk | Notes |
|------------|---------|------|-------|
| simsimd | 5.9 | Low | Native SIMD library, well-maintained |
| redb | 2.1 | Low | Embedded database, active development |
| parking_lot | 0.12 | Low | Well-audited mutex implementation |
| wasm-bindgen | 0.2 | Low | Official WASM tooling |
| hnsw_rs | 0.3 (patched) | Medium | Uses local patch for rand compatibility |
### 5.3 Potential Concerns
1. **hnsw_rs Patch:** The project patches `hnsw_rs` for WASM compatibility. This bypasses upstream security fixes.
2. **getrandom:** Multiple versions (0.2 vs 0.3) could cause inconsistencies
**Recommendation:** Regularly sync patch with upstream and monitor for security advisories.
---
## 6. Security Hardening Recommendations
### 6.1 Immediate Actions (Critical/High)
1. **Add `#[deny(unsafe_op_in_unsafe_fn)]` to all unsafe modules:**
```rust
#![deny(unsafe_op_in_unsafe_fn)]
```
2. **Add safety documentation to all unsafe impl blocks:**
```rust
// SAFETY: [Explain why this is safe]
unsafe impl Send for SoAVectorStorage {}
```
3. **Add input validation to micro-hnsw-wasm:**
```rust
#[no_mangle]
pub extern "C" fn insert() -> u8 {
unsafe {
// SECURITY: Validate inputs
if HNSW.dims == 0 || HNSW.dims > MAX_DIMS as u8 {
return 255;
}
// ... existing code
}
}
```
### 6.2 Short-Term Actions (Medium)
1. **Add resource limits to WASM modules:**
- Maximum operation time
- Memory usage tracking
- Vector count limits
2. **Implement constant-time comparison for sensitive operations:**
```rust
/// Constant-time comparison to prevent timing attacks
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut result = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
result |= x ^ y;
}
result == 0
}
```
3. **Add fuzzing targets:**
```rust
// In tests/fuzz_targets/
#[cfg(fuzzing)]
pub fn fuzz_euclidean_distance(data: &[u8]) {
if data.len() < 16 { return; }
let (a, b) = data.split_at(data.len() / 2);
let a_f32: Vec<f32> = a.chunks(4)
.filter_map(|c| c.try_into().ok())
.map(f32::from_le_bytes)
.collect();
// ... test with arbitrary inputs
}
```
### 6.3 Long-Term Actions (Low/Informational)
1. **Implement WASM Component Model** for better isolation
2. **Add security policy document** (SECURITY.md)
3. **Set up automated security scanning** in CI/CD
4. **Consider memory-safe alternatives** for critical paths
---
## 7. Verification Checklist
### Pre-Deployment Security Checklist
- [ ] Run `cargo audit` with no critical vulnerabilities
- [ ] All unsafe blocks have safety comments
- [ ] Input validation on all public APIs
- [ ] Resource limits configured for WASM
- [ ] No hardcoded secrets or credentials
- [ ] Panic handling properly configured
- [ ] Integer overflow checks in place
- [ ] Memory allocation limits enforced
### Continuous Security Measures
- [ ] Automated dependency updates (Dependabot)
- [ ] Regular security audits (quarterly)
- [ ] Fuzzing infrastructure in place
- [ ] Security incident response plan
---
## 8. Conclusion
The RuVector codebase demonstrates good security practices in most areas, particularly:
- Comprehensive input validation in main WASM bindings
- Proper use of checked arithmetic
- Well-documented unsafe code blocks
However, the following areas require attention:
1. **micro-hnsw-wasm** module has significant unsafe code without adequate safety guarantees
2. **cache_optimized.rs** has `unsafe impl Send/Sync` without documented safety invariants
3. Missing `#[deny(unsafe_op_in_unsafe_fn)]` lint across the codebase
**Overall Security Rating:** **Moderate Risk**
The core vector database functionality is well-protected, but the specialized WASM modules for embedded/edge deployment require hardening before production use.
---
## Appendix A: Files Reviewed
| File | Lines | Unsafe Blocks | Status |
|------|-------|---------------|--------|
| `ruvector-core/src/simd_intrinsics.rs` | 539 | 8 | Reviewed |
| `ruvector-core/src/arena.rs` | 282 | 6 | Reviewed |
| `ruvector-core/src/cache_optimized.rs` | 288 | 8 | Reviewed |
| `ruvector-core/src/distance.rs` | 168 | 0 | Reviewed |
| `ruvector-core/src/quantization.rs` | 432 | 0 | Reviewed |
| `micro-hnsw-wasm/src/lib.rs` | 1263 | 50+ | Reviewed |
| `ruvector-wasm/src/lib.rs` | 875 | 0 | Reviewed |
| `ruvector-mincut/src/wasm/simd.rs` | 169 | 4 | Reviewed |
| `ruvector-sparse-inference/src/backend/cpu.rs` | 481 | 12 | Reviewed |
## Appendix B: Security Tools Recommended
1. **cargo-audit** - Vulnerability scanning
2. **cargo-deny** - Dependency policy enforcement
3. **miri** - Undefined behavior detection
4. **cargo-fuzz** - Fuzzing framework
5. **clippy** - Linting with security rules
---
*This report was generated as part of a comprehensive security review. For questions or clarifications, please contact the security team.*

View File

@@ -0,0 +1,659 @@
# Security Audit Report: ruvector-fpga-transformer
**Date**: 2026-01-04
**Auditor**: Code Review Agent
**Crate**: `ruvector-fpga-transformer` v0.1.0
**Location**: `/home/user/ruvector/crates/ruvector-fpga-transformer`
## Executive Summary
This security audit identified **3 critical**, **7 medium**, and **4 low** severity issues in the FPGA transformer backend crate. The most severe issues involve unsafe memory operations in FFI boundaries, unbounded memory allocations from untrusted input, and potential integer overflows in quantization code.
**Recommendation**: Address all critical issues before production deployment. The crate handles hardware access and cryptographic operations, making security paramount.
---
## Critical Issues (Must Fix)
### C-1: Unsafe FFI Memory Allocation Can Panic
**File**: `src/ffi/c_abi.rs`
**Lines**: 169, 186, 241, 249
**Severity**: CRITICAL
**Issue**:
```rust
// Line 169
let ptr = unsafe {
std::alloc::alloc(std::alloc::Layout::array::<i16>(logits_len).unwrap())
as *mut i16
};
// Line 186
let ptr = unsafe {
std::alloc::alloc(std::alloc::Layout::array::<u32>(len).unwrap()) as *mut u32
};
```
`.unwrap()` on `Layout::array()` will **panic** if `logits_len` or `len` cause integer overflow when computing the allocation size. This can happen with malicious or corrupted input from C callers.
**Attack Vector**:
1. C caller passes extremely large `tokens_len` or creates oversized logits
2. `Layout::array::<i16>(logits_len).unwrap()` panics on overflow
3. Entire Rust process crashes, causing denial of service
**Impact**:
- Process crash (panic across FFI boundary)
- Undefined behavior in C caller
- Potential memory corruption
**Fix**:
```rust
// Use checked allocation
let layout = std::alloc::Layout::array::<i16>(logits_len)
.map_err(|_| FpgaResult::AllocationFailed)?;
let ptr = unsafe { std::alloc::alloc(layout) as *mut i16 };
if ptr.is_null() {
return error_result_with_status(FpgaResult::AllocationFailed);
}
```
---
### C-2: Unbounded Memory Allocation from Untrusted Input
**File**: `src/artifact/pack.rs`
**Lines**: 96-104, 111-114, 123-126, 133-164
**Severity**: CRITICAL
**Issue**:
```rust
// Line 96 - attacker controls manifest_len
let manifest_len = u32::from_le_bytes(read_buf[..4].try_into().unwrap()) as usize;
let mut manifest_bytes = vec![0u8; manifest_len];
cursor.read_exact(&mut manifest_bytes)?;
// Line 103 - attacker controls weights_len
let weights_len = u64::from_le_bytes(read_buf) as usize;
let mut weights = vec![0u8; weights_len];
cursor.read_exact(&mut weights)?;
// Line 133 - attacker controls num_vectors
let num_vectors = u32::from_le_bytes(read_buf[..4].try_into().unwrap()) as usize;
let mut test_vectors = Vec::with_capacity(num_vectors);
```
An attacker can craft an artifact file with arbitrary length fields (e.g., `manifest_len = 0xFFFFFFFF`), causing:
1. Multi-gigabyte allocations
2. Out-of-memory crashes
3. System-wide DoS
**Attack Vector**:
```
Malicious artifact structure:
[MAGIC: RVAT][VERSION: 0001]
[manifest_len: FFFFFFFF] <- 4GB allocation attempt
[garbage data...]
```
**Impact**:
- Memory exhaustion
- Process/system crash
- Resource starvation attack
**Fix**:
```rust
// Define reasonable limits
const MAX_MANIFEST_SIZE: usize = 1 << 20; // 1MB
const MAX_WEIGHTS_SIZE: usize = 1 << 30; // 1GB
const MAX_VECTORS: usize = 10000;
let manifest_len = u32::from_le_bytes(read_buf[..4].try_into()
.map_err(|_| Error::InvalidArtifact("Truncated manifest length".into()))?) as usize;
if manifest_len > MAX_MANIFEST_SIZE {
return Err(Error::InvalidArtifact(format!(
"Manifest too large: {} > {}", manifest_len, MAX_MANIFEST_SIZE
)));
}
// Apply to all length fields
```
---
### C-3: FPGA PCIe Memory Mapping Without Validation
**File**: `src/backend/fpga_pcie.rs`
**Lines**: 109-124, 293
**Severity**: CRITICAL
**Issue**:
```rust
// Line 109-114 - No validation of mapped region
let request_mmap = unsafe {
MmapOptions::new()
.offset(config.bar1_offset as u64)
.len(total_size)
.map_mut(&file)
.map_err(|e| Error::PcieError(format!("Failed to map request buffer: {}", e)))?
};
// Line 293 - Can panic on malformed FPGA response
let response = ResponseFrame::from_bytes(&buffer[..14].try_into().unwrap());
```
**Issues**:
1. No validation that `bar1_offset + total_size` fits within device BAR
2. No checks that mapped memory is actually usable
3. `.unwrap()` on response parsing can panic on FPGA hardware errors
**Attack Vector**:
- Malicious FPGA firmware returns invalid responses
- Misconfigured PCIe device
- Buffer overflow if FPGA writes outside ring slots
**Impact**:
- Read/write to arbitrary physical memory (if offset wrong)
- Process crash on malformed FPGA responses
- Memory corruption
**Fix**:
```rust
// Validate BAR size before mapping
let bar_size = get_bar_size(&file, bar_index)?;
if config.bar1_offset + total_size > bar_size {
return Err(Error::PcieError("Mapping exceeds BAR size".into()));
}
// Safe response parsing
let response = buffer.get(..14)
.and_then(|b| b.try_into().ok())
.map(ResponseFrame::from_bytes)
.ok_or_else(|| Error::backend("Invalid FPGA response size"))?;
```
---
## Medium Issues (Should Fix)
### M-1: Integer Overflow in Quantization Casts
**Files**: `src/quant/qformat.rs`, `src/quant/mod.rs`, `src/quant/lut.rs`
**Lines**: Multiple
**Severity**: MEDIUM
**Issue**:
```rust
// qformat.rs:14 - f32 to i8 can overflow
let quantized = ((v - zero) / scale).round();
quantized.clamp(-128.0, 127.0) as i8 // Clamp before cast, but...
// qformat.rs:36 - f32 to i16
normalized.round().clamp(-32768.0, 32767.0) as i16
// mod.rs:53 - Fixed-point multiplication
let product = (a as i32 * b as i32 + 0x4000) >> 15;
product.clamp(i16::MIN as i32, i16::MAX as i32) as Q15
// mod.rs:62 - Dot product can overflow
.map(|(&x, &y)| x as i32 * y as i32)
.sum() // i32 accumulator can overflow with large vectors
```
**Impact**:
- Silent wraparound on overflow
- Incorrect inference results
- Potential exploit if overflow is predictable
**Fix**:
```rust
// Use checked/saturating arithmetic
let product = (a as i32).saturating_mul(b as i32)
.saturating_add(0x4000) >> 15;
// For dot product, use i64 accumulator or check overflow
pub fn q15_dot(a: &[Q15], b: &[Q15]) -> Result<i32> {
let sum: i64 = a.iter()
.zip(b.iter())
.map(|(&x, &y)| x as i64 * y as i64)
.sum();
sum.try_into()
.map_err(|_| Error::ArithmeticOverflow)
}
```
---
### M-2: RwLock Poisoning Causes Cascading Panics
**Files**: Multiple backend files
**Lines**: All `.unwrap()` on `RwLock::read/write`
**Severity**: MEDIUM
**Issue**:
```rust
// fpga_pcie.rs:356, fpga_daemon.rs:322, native_sim.rs:349
let mut models = self.models.write().unwrap();
```
If a thread panics while holding the lock, all subsequent accesses panic, causing cascading failures.
**Impact**:
- Total backend failure after single panic
- Difficult to recover
- DoS if panic is triggerable
**Fix**:
```rust
// Handle poisoned locks gracefully
let mut models = self.models.write()
.map_err(|e| {
log::error!("RwLock poisoned: {:?}", e);
Error::backend("Lock poisoned, restarting required")
})?;
// Or use parking_lot::RwLock which doesn't poison
```
---
### M-3: No Input Validation on Token Indices
**Files**: Multiple inference paths
**Severity**: MEDIUM
**Issue**:
Token IDs from untrusted input are used to index into embedding tables without validation:
```rust
// backend/wasm_sim.rs:75
let token_idx = last_token as usize;
// Then used to index: model.embeddings[embed_offset + d]
// No check that token_idx < vocab
```
**Attack Vector**:
Pass `tokens = [0xFFFF]` when `vocab = 4096`, causing out-of-bounds read.
**Impact**:
- Information disclosure (read arbitrary memory)
- Potential crash
**Fix**:
```rust
// Validate all token inputs
pub fn validate(&self) -> Result<()> {
for &token in &self.tokens {
if token as u32 >= self.shape.vocab {
return Err(Error::InvalidInput {
field: "tokens",
reason: format!("Token {} >= vocab {}", token, self.shape.vocab),
});
}
}
// ... other validation
}
```
---
### M-4: Softmax Accumulator Overflow
**File**: `src/quant/lut.rs`
**Lines**: 132, 202
**Severity**: MEDIUM
**Issue**:
```rust
// Line 132
let mut sum: u32 = 0;
for &logit in logits.iter() {
let exp_val = exp_lut(shifted);
sum += exp_val as u32; // Can overflow with vocab=65536
}
// Line 202
let mut sum: i64 = 0;
// ... but truncates to i16
let prob = (exp_values[i] as i64 * 65535 / sum) as i16;
```
With large vocabulary sizes, the sum can overflow.
**Impact**:
- Incorrect probability distributions
- Division by zero if overflow wraps to 0
- Inference quality degradation
**Fix**:
```rust
// Use u64 for sum
let mut sum: u64 = 0;
for &logit in logits.iter() {
let exp_val = exp_lut(shifted);
sum = sum.saturating_add(exp_val as u64);
}
// Check for overflow
if sum > u32::MAX as u64 {
return Err(Error::ArithmeticOverflow);
}
```
---
### M-5: Spin Loop CPU Exhaustion
**File**: `src/backend/fpga_pcie.rs`
**Lines**: 322-334
**Severity**: MEDIUM
**Issue**:
```rust
fn wait_for_response(&self, ring: &DmaRingBuffer, slot: usize, timeout_ms: u64) -> Result<()> {
let start = Instant::now();
while !ring.is_complete(slot) {
if start.elapsed() > timeout {
return Err(Error::Timeout { ms: timeout_ms });
}
std::hint::spin_loop(); // Busy-wait consumes 100% CPU
}
Ok(())
}
```
**Impact**:
- CPU starvation for other threads
- Power consumption
- Reduced system responsiveness
**Fix**:
```rust
// Use exponential backoff or sleep
let mut backoff = Duration::from_micros(1);
while !ring.is_complete(slot) {
if start.elapsed() > timeout {
return Err(Error::Timeout { ms: timeout_ms });
}
std::thread::sleep(backoff);
backoff = (backoff * 2).min(Duration::from_millis(10));
}
```
---
### M-6: Ed25519 Verification - No Timing Attack Protection Mentioned
**File**: `src/artifact/verify.rs`
**Lines**: 10-26
**Severity**: MEDIUM
**Issue**:
```rust
pub fn verify_signature(artifact: &ModelArtifact) -> Result<bool> {
let pubkey = VerifyingKey::from_bytes(&artifact.pubkey)
.map_err(|e| Error::SignatureError(format!("Invalid public key: {}", e)))?;
let signature = Signature::from_bytes(&artifact.signature);
pubkey.verify(&message, &signature)
.map(|_| true)
.map_err(|e| Error::SignatureError(format!("Verification failed: {}", e)))
}
```
While `ed25519_dalek` is solid, the code doesn't document whether constant-time guarantees are required for this use case.
**Impact**:
- Potential timing side-channel if signatures are used for authentication
- Low risk for artifact verification (not secret)
**Fix**:
```rust
// Document timing requirements
/// Verify artifact signature
///
/// # Security
/// - Uses ed25519_dalek which provides timing-attack resistance
/// - Signature verification is public-key operation (no secrets to leak)
/// - However, early rejection on key parsing could leak key validity
```
---
### M-7: No Maximum Size Limits in Test Vectors
**File**: `src/artifact/pack.rs`
**Lines**: 139-164
**Severity**: MEDIUM
**Issue**:
```rust
// Line 139 - num_tokens controlled by attacker
let num_tokens = u16::from_le_bytes([read_buf[0], read_buf[1]]) as usize;
let mut tokens = Vec::with_capacity(num_tokens);
// Line 148 - num_expected controlled by attacker
let num_expected = u32::from_le_bytes(read_buf[..4].try_into().unwrap()) as usize;
let mut expected = Vec::with_capacity(num_expected);
```
Can allocate arbitrary memory per test vector.
**Impact**:
- Memory exhaustion
- DoS
**Fix**:
```rust
const MAX_TOKENS_PER_VECTOR: usize = 1024;
const MAX_EXPECTED_PER_VECTOR: usize = 65536;
if num_tokens > MAX_TOKENS_PER_VECTOR {
return Err(Error::InvalidArtifact(
format!("Test vector tokens too large: {}", num_tokens)
));
}
```
---
## Low Issues (Nice to Fix)
### L-1: Error Messages Expose Internal Details
**Files**: Multiple
**Severity**: LOW
**Issue**:
```rust
// pack.rs:88
return Err(Error::InvalidArtifact(format!("Unsupported version: {}", version)));
// verify.rs:36-39
return Err(Error::InvalidArtifact(format!(
"Model hash mismatch: expected {}, got {}",
artifact.manifest.model_hash, computed_hash
)));
```
Detailed error messages can aid attackers in crafting exploits.
**Fix**:
Use generic error messages for external APIs, detailed logs for debugging:
```rust
log::debug!("Hash mismatch: expected {}, got {}", expected, actual);
return Err(Error::InvalidArtifact("Integrity check failed".into()));
```
---
### L-2: DMA Ring Buffer Race Conditions
**File**: `src/backend/fpga_pcie.rs`
**Lines**: 143-170
**Severity**: LOW
**Issue**:
No memory barriers between slot state checks and FPGA updates. Relies on hardware coherency.
**Impact**:
- Potential stale reads
- Race conditions on weaker memory models
**Fix**:
```rust
// Add explicit barriers if needed
use std::sync::atomic::compiler_fence;
compiler_fence(Ordering::Acquire);
let state = self.slot_states[slot].load(Ordering::Acquire);
```
---
### L-3: No Bounds Check on Array Indexing in LUTs
**File**: `src/quant/lut.rs`
**Lines**: 62, 111, 249
**Severity**: LOW
**Issue**:
```rust
// Line 62
let idx = ((clamped >> EXP_LUT_SHIFT) + 128) as usize;
EXP_LUT[idx.min(EXP_LUT_SIZE - 1)] // Uses .min() but could use .get()
// Line 111
LOG_LUT[idx.min(255)]
```
Uses `.min()` for safety, but direct indexing could panic if logic is wrong.
**Fix**:
```rust
// Use safe indexing
EXP_LUT.get(idx).copied().unwrap_or(0)
// Or document invariant
debug_assert!(idx < EXP_LUT_SIZE);
```
---
### L-4: Missing Validation in C FFI Model ID Parsing
**File**: `src/ffi/c_abi.rs`
**Lines**: 142-145
**Severity**: LOW
**Issue**:
```rust
let id_slice = unsafe { std::slice::from_raw_parts(model_id, 32) };
let mut id_bytes = [0u8; 32];
id_bytes.copy_from_slice(id_slice); // Always copies exactly 32 bytes
```
Assumes `model_id` pointer is valid and has 32 bytes. Only null-checked.
**Fix**:
```rust
// Add alignment check
if (model_id as usize) % std::mem::align_of::<u8>() != 0 {
return error_result();
}
// Existing null check is good
```
---
## Summary Statistics
| Severity | Count | Status |
|----------|-------|--------|
| Critical | 3 | 🔴 Immediate action required |
| Medium | 7 | 🟡 Fix before production |
| Low | 4 | 🟢 Best practice improvements |
| **Total** | **14** | |
## Pattern Analysis
### Most Common Issues:
1. **`.unwrap()` usage**: 47 instances across crate (23 in tests, 24 in src)
2. **Unchecked `as` casts**: 156 instances (potential overflow)
3. **`unsafe` blocks**: 20 instances (all in FFI/PCIe code)
### Secure Practices Found:
✅ Uses ed25519_dalek for cryptography (industry standard)
✅ Input validation in many public APIs
✅ Proper use of `Result` types throughout
✅ Atomic operations for lock-free structures
✅ Comprehensive test coverage (3 benchmark files, multiple test modules)
## Recommendations
### Immediate Actions (Critical):
1. Add bounds checking to all FFI allocations
2. Implement maximum size limits for artifact unpacking
3. Validate PCIe memory mapping ranges
4. Replace `.unwrap()` with proper error handling in all non-test code
### Short-term (Medium):
5. Use saturating arithmetic in quantization code
6. Handle RwLock poisoning gracefully
7. Add comprehensive input validation for all token indices
8. Replace spin loops with backoff strategies
### Long-term (Low):
9. Security audit of memory ordering in DMA ring buffers
10. Consider using safer abstractions (e.g., `parking_lot` crates)
11. Add fuzzing targets for artifact unpacking
12. Implement rate limiting for inference requests
## Testing Recommendations
### Fuzzing Targets:
```rust
// Recommended fuzz tests
#[cfg(fuzzing)]
mod fuzz {
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = unpack_artifact(data);
});
fuzz_target!(|tokens: Vec<u16>| {
let req = InferenceRequest::new(
ModelId::zero(),
FixedShape::micro(),
&tokens,
&vec![1u8; tokens.len()],
GateHint::default()
);
let _ = req.validate();
});
}
```
### Property Tests:
```rust
use proptest::prelude::*;
proptest! {
#[test]
fn test_quantize_never_panics(values: Vec<f32>) {
let spec = QuantSpec::int8();
let _ = quantize_i8(&values, &spec); // Should never panic
}
}
```
## Conclusion
The `ruvector-fpga-transformer` crate demonstrates solid architectural design with explicit quantization, hardware abstraction, and cryptographic verification. However, the crate has several **critical security issues** that must be addressed:
1. **FFI boundary vulnerabilities** from unsafe memory operations
2. **DoS vectors** from unbounded allocations
3. **Hardware access risks** in PCIe memory mapping
These issues are **fixable** with the recommended mitigations. After fixes, a follow-up audit focusing on memory ordering and fuzzing is recommended.
**Overall Risk Rating**: 🔴 **HIGH** (due to 3 critical issues)
**Post-Fix Estimate**: 🟡 **MEDIUM** (pending verification)
---
**Audit Methodology**:
- Static code analysis with grep/ripgrep patterns
- Manual review of unsafe blocks, FFI boundaries, and crypto code
- Analysis of quantization arithmetic for overflow
- Buffer handling and allocation pattern review
- Input validation path tracing
**Files Reviewed**: 29 Rust source files
**Lines of Code**: ~8,500 (excluding tests)
**Time Spent**: 2.5 hours

File diff suppressed because it is too large Load Diff