id: ffi-15 original_id: P.UNS.FFI.15 level: P impact: HIGH
Validate Non-Robust External Values
Summary
Data received from external sources (FFI, files, network) may be invalid. Validate before using it as Rust types with stricter invariants.
Rationale
- External data can be malicious or corrupted
- Rust types have invariants (e.g., valid UTF-8 for str)
- Invalid data causes undefined behavior
Bad Example
// DON'T: Trust external data
extern "C" {
fn get_status() -> u8;
}
#[derive(Debug)]
enum Status { Active = 0, Inactive = 1, Pending = 2 }
fn bad_convert() -> Status {
let raw = unsafe { get_status() };
// BAD: Assumes C returns valid enum value
unsafe { std::mem::transmute(raw) } // UB if raw > 2
}
// DON'T: Trust strings from C
fn bad_string(ptr: *const c_char) -> &str {
let cstr = unsafe { CStr::from_ptr(ptr) };
// BAD: Assumes valid UTF-8
cstr.to_str().unwrap()
}
// DON'T: Trust size values
fn bad_size(ptr: *const u8, len: usize) -> Vec<u8> {
// BAD: len could be huge, causing OOM
// BAD: len could exceed actual data
unsafe { std::slice::from_raw_parts(ptr, len) }.to_vec()
}
Good Example
// DO: Validate enum values
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Status {
Active = 0,
Inactive = 1,
Pending = 2,
}
impl TryFrom<u8> for Status {
type Error = InvalidStatusError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Status::Active),
1 => Ok(Status::Inactive),
2 => Ok(Status::Pending),
_ => Err(InvalidStatusError(value)),
}
}
}
fn good_convert() -> Result<Status, InvalidStatusError> {
let raw = unsafe { get_status() };
Status::try_from(raw) // Returns error for invalid values
}
// DO: Handle invalid UTF-8
fn good_string(ptr: *const c_char) -> Result<String, std::str::Utf8Error> {
if ptr.is_null() {
return Ok(String::new());
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().map(|s| s.to_owned())
}
fn good_string_lossy(ptr: *const c_char) -> String {
if ptr.is_null() {
return String::new();
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_string_lossy().into_owned() // Replaces invalid UTF-8
}
// DO: Validate sizes
const MAX_REASONABLE_SIZE: usize = 100 * 1024 * 1024; // 100 MB
fn good_size(ptr: *const u8, len: usize) -> Result<Vec<u8>, ValidationError> {
if ptr.is_null() {
return Err(ValidationError::NullPointer);
}
if len > MAX_REASONABLE_SIZE {
return Err(ValidationError::SizeTooLarge);
}
// Still need to trust that ptr points to len valid bytes
// Document this as a caller requirement
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
Ok(slice.to_vec())
}
// DO: Use num_enum for safe enum conversion
// use num_enum::TryFromPrimitive;
//
// #[derive(TryFromPrimitive)]
// #[repr(u8)]
// enum Status { Active = 0, Inactive = 1, Pending = 2 }
Validation Patterns
| External Data | Validation |
|---|---|
| Enum discriminant | Match against valid values |
| String | Check UTF-8 or use lossy conversion |
| Size/length | Check against maximum |
| Pointer | Check for null |
| Boolean | Explicit 0/1 check or treat any non-zero as true |
| Float | Check for NaN, infinity if problematic |
Checklist
- Am I validating external enum values?
- Am I handling potential invalid UTF-8?
- Am I checking sizes against reasonable limits?
- Am I using TryFrom instead of transmute?
Related Rules
ffi-12: Document invariant assumptionssafety-02: Verify safety invariants
你可能感兴趣的文章
Do Not Expose Raw Pointers in Public APIs
Unsafe Code Authors Must Verify Safety Invariants
Ensure Consistent Data Layout for Custom Types
Add Safety Section in Docs for Public Unsafe Functions
Do Not Use Union Variants Across Different Lifetimes
Use MaybeUninit
Ensure C-ABI Compatibility for Strings Between Rust and C
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types