5.1 KiB
5.1 KiB
Safety-Critical Coding Standards
This document outlines the safety-critical coding standards applied to Linux Hello, based on MISRA C and similar standards, adapted for both C and Rust code.
Principles Applied
1. Simple Control Flow
- ✅ C: No goto, setjmp, longjmp, or recursion
- ✅ Rust: Avoid recursion where possible; use iterative solutions. Rust's ownership system prevents many control flow issues.
2. Fixed Loop Bounds
- ✅ C: All loops have fixed upper bounds (e.g.,
MAX_ARGS = 32) - ✅ Rust: Use bounded iterators,
take(), or explicit bounds. Example:for item in items.iter().take(MAX_ITEMS) { ... }
3. No Dynamic Memory After Init
- ✅ C: No malloc/free after startup (stack allocation only)
- ⚠️ Rust: Rust's allocator is safer, but we still prefer:
- Pre-allocated buffers where possible
Vec::with_capacity()to avoid reallocations- Stack allocation for small data
4. Function Length Limit (~60 lines)
- ✅ C: All functions under 60 lines
- ✅ Rust: Same principle - break complex functions into smaller, testable units
5. Runtime Assertions (2+ per function)
- ✅ C: Using
assert()andassert_condition()helper - ✅ Rust: Use
debug_assert!(),assert!(), andunwrap_or_else()with validation
6. Smallest Scope for Data
- ✅ C: Declare variables at smallest possible scope
- ✅ Rust: Rust enforces this - variables are scoped to blocks
7. Check All Return Values
- ✅ C: All function returns checked, void casts for intentional ignores
- ✅ Rust: Use
Result<T, E>andOption<T>- compiler enforces checking
8. Minimal Preprocessor
- ✅ C: Only
#includeand simple#defineconstants - ✅ Rust: Use
cfg!()and#[cfg(...)]attributes instead of macros
9. Restricted Pointer Use
- ✅ C: Single level dereferencing only, no function pointers unless essential
- ✅ Rust: Rust's references are safer, but we still:
- Avoid deep nesting (
&&&T) - Prefer owned types over references where possible
- Use function pointers only when necessary (e.g., trait objects)
- Avoid deep nesting (
10. Strictest Warnings
- ✅ C:
-Wall -Wextra -Werror -Wpedantic+ static analysis - ✅ Rust:
#![deny(warnings)]in critical modules,clippywith pedantic lints
Implementation Status
C PAM Module (pam-module/pam_linux_hello.c)
✅ Fully compliant:
- Split
authenticate_face()into smaller functions - Fixed loop bounds (
MAX_ARGS = 32) - All return values checked
- Runtime assertions added
- Strictest compiler warnings enabled
- Single-level pointer dereferencing
- No dynamic allocation after init
Rust Codebase
🔄 In Progress - Applying equivalent principles:
Completed:
- ✅ Return values checked (Rust enforces this)
- ✅ Small scope (Rust enforces this)
- ✅ No goto/setjmp (not applicable in Rust)
- ✅ Minimal preprocessor (Rust uses attributes)
To Improve:
- ⏳ Function length limits (some functions > 60 lines)
- ⏳ Fixed loop bounds (some iterators unbounded)
- ⏳ More runtime assertions
- ⏳ Enable strictest Rust lints
Rust-Specific Safety Measures
Memory Safety
- Rust's ownership system prevents use-after-free, double-free
- No manual memory management needed
unsafeblocks are minimal and documented
Type Safety
- Strong typing prevents many classes of bugs
- Pattern matching ensures exhaustive handling
ResultandOptionenforce error handling
Bounds Checking
- Array/vector bounds checked at runtime (can be disabled with
unsafe) - Iterator bounds enforced by type system
Static Analysis
C Code
# Compile with strictest warnings
make CFLAGS="-Wall -Wextra -Werror -Wpedantic ..."
# Run static analysis
cppcheck --enable=all pam-module/pam_linux_hello.c
splint pam-module/pam_linux_hello.c
Rust Code
# Clippy with pedantic lints
cargo clippy -- -W clippy::pedantic
# Deny warnings in release
RUSTFLAGS="-D warnings" cargo build --release
Code Review Checklist
- No functions > 60 lines
- All loops have fixed bounds
- All return values checked
- Minimum 2 assertions per function
- Variables at smallest scope
- No recursion (unless essential)
- Single-level pointer/reference dereferencing
- Compiles with strictest warnings
- Passes static analysis
Examples
Good: Fixed Loop Bound
const MAX_ITEMS: usize = 100;
for item in items.iter().take(MAX_ITEMS) {
// Process item
}
Good: Assertions
fn process_data(data: &[u8]) -> Result<()> {
debug_assert!(!data.is_empty(), "Data must not be empty");
debug_assert!(data.len() <= MAX_SIZE, "Data exceeds maximum size");
// ... processing
}
Good: Small Functions
// Instead of one 100-line function, split:
fn validate_input(input: &str) -> Result<()> { ... } // 20 lines
fn parse_input(input: &str) -> Result<Data> { ... } // 25 lines
fn process_data(data: Data) -> Result<Output> { ... } // 30 lines
References
- MISRA C:2012 Guidelines
- CERT C Coding Standard
- Rust API Guidelines
- Safety-Critical Software Development