ruvector_security/ffi.rs
1//! FFI safety utilities
2//!
3//! Provides safe wrappers for unsafe FFI operations including:
4//! - Pointer validation
5//! - Tracked allocations with safe deallocation
6//! - SAFETY documentation patterns
7
8use crate::error::{SecurityError, SecurityResult};
9use std::alloc::{alloc, dealloc, Layout};
10use std::marker::PhantomData;
11use std::ptr::NonNull;
12
13/// Maximum buffer size for FFI operations (256 MB)
14pub const MAX_FFI_BUFFER_SIZE: usize = 256 * 1024 * 1024;
15
16/// Error type for FFI operations
17pub type FfiError = SecurityError;
18
19/// Validates a raw pointer before use in unsafe code.
20///
21/// # Arguments
22/// * `ptr` - Pointer to validate
23/// * `len` - Number of elements (not bytes)
24///
25/// # Returns
26/// * `Ok(())` if the pointer is valid
27/// * `Err(FfiError)` if validation fails
28///
29/// # Example
30///
31/// ```rust,ignore
32/// use ruvector_security::ffi::validate_ptr;
33///
34/// let data = vec![1.0f32, 2.0, 3.0];
35/// let ptr = data.as_ptr();
36///
37/// // Valid pointer
38/// assert!(validate_ptr(ptr, data.len()).is_ok());
39///
40/// // Null pointer
41/// let null: *const f32 = std::ptr::null();
42/// assert!(validate_ptr(null, 1).is_err());
43/// ```
44#[inline]
45pub fn validate_ptr<T>(ptr: *const T, len: usize) -> SecurityResult<()> {
46 // Null check
47 if ptr.is_null() {
48 return Err(SecurityError::NullPointer);
49 }
50
51 // Alignment check
52 let alignment = std::mem::align_of::<T>();
53 if (ptr as usize) % alignment != 0 {
54 return Err(SecurityError::MisalignedPointer {
55 ptr: ptr as usize,
56 required_alignment: alignment,
57 });
58 }
59
60 // Size overflow check
61 let element_size = std::mem::size_of::<T>();
62 let byte_len = len.checked_mul(element_size).ok_or(SecurityError::SizeOverflow)?;
63
64 // Reasonable size bounds
65 if byte_len > MAX_FFI_BUFFER_SIZE {
66 return Err(SecurityError::BufferTooLarge(byte_len));
67 }
68
69 Ok(())
70}
71
72/// Validates a mutable raw pointer before use.
73#[inline]
74pub fn validate_ptr_mut<T>(ptr: *mut T, len: usize) -> SecurityResult<()> {
75 validate_ptr(ptr as *const T, len)
76}
77
78/// Safely creates a slice from a raw pointer after validation.
79///
80/// # Safety
81///
82/// This function is unsafe because:
83/// - The caller must ensure the pointer points to valid memory
84/// - The memory must not be modified during the lifetime of the returned slice
85/// - The memory must be valid for the entire length
86///
87/// # Example
88///
89/// ```rust,ignore
90/// use ruvector_security::ffi::slice_from_ptr;
91///
92/// let data = vec![1i16, 2, 3, 4];
93/// let ptr = data.as_ptr();
94///
95/// // SAFETY: We own `data` and know the pointer is valid
96/// let slice = unsafe { slice_from_ptr(ptr, data.len())? };
97/// assert_eq!(slice, &[1, 2, 3, 4]);
98/// ```
99///
100/// # Errors
101///
102/// Returns an error if pointer validation fails.
103#[inline]
104pub unsafe fn slice_from_ptr<'a, T>(ptr: *const T, len: usize) -> SecurityResult<&'a [T]> {
105 validate_ptr(ptr, len)?;
106
107 // SAFETY: We've validated the pointer is:
108 // - Non-null
109 // - Properly aligned for T
110 // - Within reasonable size bounds
111 // The caller guarantees the memory is valid and won't be mutated.
112 Ok(std::slice::from_raw_parts(ptr, len))
113}
114
115/// Safely creates a mutable slice from a raw pointer after validation.
116///
117/// # Safety
118///
119/// Same requirements as `slice_from_ptr`, plus:
120/// - The caller must have exclusive access to the memory
121#[inline]
122pub unsafe fn slice_from_ptr_mut<'a, T>(ptr: *mut T, len: usize) -> SecurityResult<&'a mut [T]> {
123 validate_ptr_mut(ptr, len)?;
124
125 // SAFETY: We've validated the pointer, and caller guarantees exclusive access.
126 Ok(std::slice::from_raw_parts_mut(ptr, len))
127}
128
129/// A tracked allocation that stores its layout for safe deallocation.
130///
131/// This wrapper ensures that memory is deallocated with the same layout
132/// that was used for allocation, preventing undefined behavior.
133///
134/// # Example
135///
136/// ```rust
137/// use ruvector_security::TrackedAllocation;
138///
139/// // Allocate memory for 100 f32 values
140/// let mut alloc = TrackedAllocation::<f32>::new(100).unwrap();
141///
142/// // Write data
143/// unsafe {
144/// for i in 0..100 {
145/// *alloc.as_mut_ptr().add(i) = i as f32;
146/// }
147/// }
148///
149/// // Memory is automatically deallocated when `alloc` is dropped
150/// ```
151pub struct TrackedAllocation<T> {
152 /// Non-null pointer to allocated memory
153 ptr: NonNull<T>,
154 /// Layout used for allocation
155 layout: Layout,
156 /// Number of elements
157 len: usize,
158 /// Marker for drop check
159 _marker: PhantomData<T>,
160}
161
162impl<T> TrackedAllocation<T> {
163 /// Allocate memory for `count` elements of type T.
164 ///
165 /// # Errors
166 ///
167 /// Returns an error if:
168 /// - `count` is zero
169 /// - The allocation size overflows
170 /// - The allocator fails
171 pub fn new(count: usize) -> SecurityResult<Self> {
172 if count == 0 {
173 return Err(SecurityError::SizeOverflow);
174 }
175
176 let layout = Layout::array::<T>(count).map_err(|_| SecurityError::SizeOverflow)?;
177
178 if layout.size() > MAX_FFI_BUFFER_SIZE {
179 return Err(SecurityError::BufferTooLarge(layout.size()));
180 }
181
182 // SAFETY: We've validated the layout is valid and within bounds.
183 let ptr = unsafe { alloc(layout) as *mut T };
184
185 let ptr = NonNull::new(ptr).ok_or(SecurityError::AllocationFailed)?;
186
187 Ok(Self {
188 ptr,
189 layout,
190 len: count,
191 _marker: PhantomData,
192 })
193 }
194
195 /// Allocate and zero-initialize memory.
196 pub fn new_zeroed(count: usize) -> SecurityResult<Self> {
197 let alloc = Self::new(count)?;
198
199 // SAFETY: We just allocated this memory and have exclusive access.
200 unsafe {
201 std::ptr::write_bytes(alloc.ptr.as_ptr(), 0, count);
202 }
203
204 Ok(alloc)
205 }
206
207 /// Get a raw pointer to the allocation.
208 #[inline]
209 pub fn as_ptr(&self) -> *const T {
210 self.ptr.as_ptr()
211 }
212
213 /// Get a mutable raw pointer to the allocation.
214 #[inline]
215 pub fn as_mut_ptr(&mut self) -> *mut T {
216 self.ptr.as_ptr()
217 }
218
219 /// Get the number of elements.
220 #[inline]
221 pub fn len(&self) -> usize {
222 self.len
223 }
224
225 /// Check if the allocation is empty (always false for valid allocations).
226 #[inline]
227 pub fn is_empty(&self) -> bool {
228 self.len == 0
229 }
230
231 /// Get the layout used for allocation.
232 #[inline]
233 pub fn layout(&self) -> Layout {
234 self.layout
235 }
236
237 /// Convert to a slice.
238 ///
239 /// # Safety
240 ///
241 /// The caller must ensure the memory has been properly initialized.
242 #[inline]
243 pub unsafe fn as_slice(&self) -> &[T] {
244 std::slice::from_raw_parts(self.ptr.as_ptr(), self.len)
245 }
246
247 /// Convert to a mutable slice.
248 ///
249 /// # Safety
250 ///
251 /// The caller must ensure the memory has been properly initialized.
252 #[inline]
253 pub unsafe fn as_slice_mut(&mut self) -> &mut [T] {
254 std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
255 }
256
257 /// Copy data from a slice into the allocation.
258 ///
259 /// # Panics
260 ///
261 /// Panics if the slice length doesn't match the allocation length.
262 pub fn copy_from_slice(&mut self, src: &[T])
263 where
264 T: Copy,
265 {
266 assert_eq!(
267 src.len(),
268 self.len,
269 "Source slice length must match allocation length"
270 );
271
272 // SAFETY: We have exclusive access and lengths match.
273 unsafe {
274 std::ptr::copy_nonoverlapping(src.as_ptr(), self.ptr.as_ptr(), self.len);
275 }
276 }
277
278 /// Take ownership and return raw parts.
279 ///
280 /// After calling this, the caller is responsible for deallocation.
281 /// Use `TrackedAllocation::from_raw_parts` to reconstruct.
282 pub fn into_raw_parts(self) -> (*mut T, Layout, usize) {
283 let parts = (self.ptr.as_ptr(), self.layout, self.len);
284 std::mem::forget(self); // Prevent Drop from running
285 parts
286 }
287
288 /// Reconstruct from raw parts.
289 ///
290 /// # Safety
291 ///
292 /// The caller must provide parts from a previous `into_raw_parts` call
293 /// with matching type T.
294 pub unsafe fn from_raw_parts(ptr: *mut T, layout: Layout, len: usize) -> Option<Self> {
295 NonNull::new(ptr).map(|ptr| Self {
296 ptr,
297 layout,
298 len,
299 _marker: PhantomData,
300 })
301 }
302}
303
304impl<T> Drop for TrackedAllocation<T> {
305 fn drop(&mut self) {
306 // SAFETY: Layout matches what was used in allocation.
307 // The pointer is valid because it came from a successful allocation.
308 unsafe {
309 dealloc(self.ptr.as_ptr() as *mut u8, self.layout);
310 }
311 }
312}
313
314// TrackedAllocation is Send if T is Send
315unsafe impl<T: Send> Send for TrackedAllocation<T> {}
316
317// TrackedAllocation is Sync if T is Sync
318unsafe impl<T: Sync> Sync for TrackedAllocation<T> {}
319
320/// Helper macro for documenting unsafe blocks
321///
322/// Use this pattern in your code:
323///
324/// ```rust,ignore
325/// // SAFETY: This block is safe because:
326/// // - Pointer `ptr` was validated via `validate_ptr()`
327/// // - We have exclusive access to the memory
328/// // - The length is checked to be within bounds
329/// unsafe {
330/// std::slice::from_raw_parts(ptr, len)
331/// }
332/// ```
333#[macro_export]
334macro_rules! safety_doc {
335 ($($reason:tt)*) => {
336 // SAFETY: $($reason)*
337 };
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_validate_ptr_null() {
346 let ptr: *const f32 = std::ptr::null();
347 assert!(matches!(validate_ptr(ptr, 1), Err(SecurityError::NullPointer)));
348 }
349
350 #[test]
351 fn test_validate_ptr_valid() {
352 let data = vec![1.0f32, 2.0, 3.0];
353 assert!(validate_ptr(data.as_ptr(), data.len()).is_ok());
354 }
355
356 #[test]
357 fn test_validate_ptr_overflow() {
358 let data = [0u8; 8];
359 // Try to create a slice larger than max buffer
360 assert!(matches!(
361 validate_ptr(data.as_ptr(), MAX_FFI_BUFFER_SIZE + 1),
362 Err(SecurityError::BufferTooLarge(_))
363 ));
364 }
365
366 #[test]
367 fn test_tracked_allocation() {
368 let mut alloc = TrackedAllocation::<i32>::new(100).unwrap();
369 assert_eq!(alloc.len(), 100);
370
371 // Write data
372 unsafe {
373 for i in 0..100 {
374 *alloc.as_mut_ptr().add(i) = i as i32;
375 }
376
377 // Verify data
378 let slice = alloc.as_slice();
379 for (i, &val) in slice.iter().enumerate() {
380 assert_eq!(val, i as i32);
381 }
382 }
383
384 // Allocation is automatically freed on drop
385 }
386
387 #[test]
388 fn test_tracked_allocation_zeroed() {
389 let alloc = TrackedAllocation::<u64>::new_zeroed(50).unwrap();
390
391 unsafe {
392 for &val in alloc.as_slice() {
393 assert_eq!(val, 0);
394 }
395 }
396 }
397
398 #[test]
399 fn test_tracked_allocation_copy_from_slice() {
400 let mut alloc = TrackedAllocation::<f32>::new(4).unwrap();
401 let source = [1.0f32, 2.0, 3.0, 4.0];
402
403 alloc.copy_from_slice(&source);
404
405 unsafe {
406 assert_eq!(alloc.as_slice(), &source);
407 }
408 }
409
410 #[test]
411 fn test_tracked_allocation_into_raw_parts() {
412 let alloc = TrackedAllocation::<u8>::new(64).unwrap();
413 let (ptr, layout, len) = alloc.into_raw_parts();
414
415 assert!(!ptr.is_null());
416 assert_eq!(len, 64);
417
418 // Reconstruct and drop properly
419 unsafe {
420 let _ = TrackedAllocation::<u8>::from_raw_parts(ptr, layout, len);
421 }
422 }
423
424 #[test]
425 fn test_slice_from_ptr() {
426 let data = vec![1i16, 2, 3, 4, 5];
427
428 unsafe {
429 let slice = slice_from_ptr(data.as_ptr(), data.len()).unwrap();
430 assert_eq!(slice, &[1, 2, 3, 4, 5]);
431 }
432 }
433
434 #[test]
435 fn test_slice_from_ptr_null() {
436 let ptr: *const u32 = std::ptr::null();
437
438 unsafe {
439 assert!(matches!(
440 slice_from_ptr(ptr, 10),
441 Err(SecurityError::NullPointer)
442 ));
443 }
444 }
445}