123 lines
2.9 KiB
Markdown
123 lines
2.9 KiB
Markdown
---
|
|
id: ffi-01
|
|
original_id: P.UNS.FFI.01
|
|
level: P
|
|
impact: HIGH
|
|
---
|
|
|
|
# Avoid Passing Strings Directly to C from Public Rust API
|
|
|
|
## Summary
|
|
|
|
Use `CString` and `CStr` for string handling at FFI boundaries. Never pass Rust `String` or `&str` directly to C.
|
|
|
|
## Rationale
|
|
|
|
- Rust strings are UTF-8, not null-terminated
|
|
- C strings require null terminator
|
|
- Rust strings may contain interior null bytes
|
|
- Memory layout differs between Rust String and C char*
|
|
|
|
## Bad Example
|
|
|
|
```rust
|
|
extern "C" {
|
|
fn c_print(s: *const u8);
|
|
fn c_strlen(s: *const u8) -> usize;
|
|
}
|
|
|
|
// DON'T: Pass Rust string directly
|
|
fn bad_print(s: &str) {
|
|
unsafe {
|
|
c_print(s.as_ptr()); // Not null-terminated!
|
|
}
|
|
}
|
|
|
|
// DON'T: Assume length matches
|
|
fn bad_strlen(s: &str) -> usize {
|
|
unsafe {
|
|
c_strlen(s.as_ptr()) // May read past buffer
|
|
}
|
|
}
|
|
|
|
// DON'T: Use String in FFI signatures
|
|
extern "C" fn bad_callback(s: String) { // Wrong!
|
|
println!("{}", s);
|
|
}
|
|
```
|
|
|
|
## Good Example
|
|
|
|
```rust
|
|
use std::ffi::{CString, CStr};
|
|
use std::os::raw::c_char;
|
|
|
|
extern "C" {
|
|
fn c_print(s: *const c_char);
|
|
fn c_strlen(s: *const c_char) -> usize;
|
|
fn c_get_string() -> *const c_char;
|
|
}
|
|
|
|
// DO: Convert to CString for passing to C
|
|
fn good_print(s: &str) -> Result<(), std::ffi::NulError> {
|
|
let c_string = CString::new(s)?; // Adds null terminator, checks for interior nulls
|
|
unsafe {
|
|
c_print(c_string.as_ptr());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// DO: Use CStr for receiving C strings
|
|
fn good_receive() -> String {
|
|
unsafe {
|
|
let ptr = c_get_string();
|
|
let c_str = CStr::from_ptr(ptr);
|
|
c_str.to_string_lossy().into_owned()
|
|
}
|
|
}
|
|
|
|
// DO: Handle interior null bytes
|
|
fn handle_nulls(s: &str) {
|
|
match CString::new(s) {
|
|
Ok(c_string) => unsafe { c_print(c_string.as_ptr()) },
|
|
Err(e) => {
|
|
// String contains interior null at position e.nul_position()
|
|
eprintln!("String contains null byte at {}", e.nul_position());
|
|
}
|
|
}
|
|
}
|
|
|
|
// DO: Use proper types in callbacks
|
|
extern "C" fn good_callback(s: *const c_char) {
|
|
if !s.is_null() {
|
|
let c_str = unsafe { CStr::from_ptr(s) };
|
|
if let Ok(rust_str) = c_str.to_str() {
|
|
println!("{}", rust_str);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## String Type Comparison
|
|
|
|
| Type | Null-terminated | Encoding | Use |
|
|
|------|-----------------|----------|-----|
|
|
| `String` | No | UTF-8 | Rust owned |
|
|
| `&str` | No | UTF-8 | Rust borrowed |
|
|
| `CString` | Yes | Byte | Rust-to-C owned |
|
|
| `&CStr` | Yes | Byte | Rust-to-C borrowed |
|
|
| `*const c_char` | Yes | Byte | FFI pointer |
|
|
| `OsString` | Platform | Platform | Paths, env |
|
|
|
|
## Checklist
|
|
|
|
- [ ] Am I passing Rust strings to C? → Use CString
|
|
- [ ] Am I receiving C strings? → Use CStr
|
|
- [ ] Does my string contain null bytes? → Handle NulError
|
|
- [ ] Am I checking for null pointers from C?
|
|
|
|
## Related Rules
|
|
|
|
- `ffi-02`: Read documentation for std::ffi types
|
|
- `ffi-06`: Ensure C-ABI string compatibility
|