Trust-Level Safety Verification for C Compilers

· nat's blog


If I ever get around to writing a C compiler, I would like to tackle this concept. I didn't write it myself, I just brainstormed this with the soon defunct Cody...

Abstract #

This paper presents a novel approach to C compiler safety that combines static analysis with layered trust hierarchies and API-level safety contracts. Rather than treating all code as equally trustworthy or untrustworthy, our system establishes trust levels that allow safe libraries to provide guarantees to user code while still permitting them to use lower-level unsafe operations internally. The compiler emits metadata about operations and API calls, which a specialized linker then verifies against trust boundaries and safety contracts.

1. Introduction #

1.1 Problem Statement #

Traditional C compilers provide little to no safety guarantees, leading to common vulnerabilities like buffer overflows, use-after-free, and pointer arithmetic errors. While static analysis tools exist, they often produce false positives or require marking arbitrary code as "trusted," which defeats the purpose of safety verification.

1.2 Our Approach #

We propose a layered trust model where:

2. Trust Level Hierarchy #

2.1 Trust Level Definition #

1enum trust_level {
2    TRUST_USER_CODE = 0,        // Highest restrictions
3    TRUST_SAFE_LIBRARY = 1,     // Can call lower levels safely  
4    TRUST_SYSTEM_LIBRARY = 2,   // Can call anything
5    TRUST_LIBC = 3             // Lowest level, no restrictions
6};

2.2 Trust Level Rules #

Caller Level Can Call Restrictions
TRUST_USER_CODE Safe libraries only No pointer arithmetic, no direct libc calls
TRUST_SAFE_LIBRARY System libraries + libc Must provide safety contracts
TRUST_SYSTEM_LIBRARY Anything Minimal restrictions
TRUST_LIBC Anything No restrictions

3. Safety Contracts #

3.1 Contract Types #

1enum safety_contract {
2    CONTRACT_NO_OVERFLOW    = 1 << 0,  // Prevents buffer overflows
3    CONTRACT_NULL_SAFE      = 1 << 1,  // Handles NULL pointers gracefully
4    CONTRACT_BOUNDS_CHECKED = 1 << 2,  // All array accesses are bounds-checked
5    CONTRACT_MEMORY_SAFE    = 1 << 3,  // No memory leaks or use-after-free
6    CONTRACT_THREAD_SAFE    = 1 << 4   // Safe for concurrent access
7};

3.2 API Contract Declaration #

1// Example: mulle-buffer library
2__attribute__((trust_level(TRUST_SAFE_LIBRARY)))
3__attribute__((safety_contract(no_overflow, null_safe, bounds_checked)))
4int mulle_buffer_add_bytes(struct mulle_buffer *buffer, const void *data, size_t len);
5
6__attribute__((trust_level(TRUST_SAFE_LIBRARY)))
7__attribute__((can_call_level(TRUST_LIBC)))  // Explicit permission
8char* mulle_buffer_get_string(struct mulle_buffer *buffer);

4. Compiler Implementation #

4.1 Metadata Structure #

 1typedef struct {
 2    enum operation_type {
 3        RAW_POINTER_ARITHMETIC,     // User did: ptr + offset
 4        SAFE_API_CALL,             // User called contracted API
 5        UNSAFE_CAST,               // User did: (int*)ptr
 6        CONTRACTED_ACCESS,         // Access through safe API
 7        TRUST_VIOLATION           // Calling unauthorized API
 8    } type;
 9    
10    union {
11        struct {
12            const char* api_name;
13            enum safety_contract contracts;
14            enum trust_level api_level;
15        } api_info;
16        
17        struct {
18            int line;
19            const char* operation;
20            enum trust_level caller_level;
21        } raw_info;
22        
23        struct {
24            const char* caller;
25            enum trust_level caller_level;
26            const char* callee;
27            enum trust_level callee_level;
28            int line;
29        } violation_info;
30    };
31} operation_metadata_t;

4.2 Compilation Process #

 1// Compiler integration pseudocode
 2void emit_operation_metadata(enum operation_type type, const char* caller, 
 3                           const char* callee, int line) {
 4    enum trust_level caller_level = get_function_trust_level(caller);
 5    enum trust_level callee_level = get_function_trust_level(callee);
 6    
 7    operation_metadata_t metadata = {
 8        .type = type,
 9        .caller = caller,
10        .caller_level = caller_level,
11        .callee = callee,
12        .callee_level = callee_level,
13        .line = line
14    };
15    
16    // Emit to .compiler_safety_metadata section
17    emit_to_metadata_section(&metadata);
18}

5. Usage Examples #

5.1 Compliant User Code #

 1// User code (TRUST_USER_CODE level)
 2void safe_user_function(const char *input) {
 3    struct mulle_buffer buf = MULLE_BUFFER_INIT;
 4    
 5    // ✅ ALLOWED: User calling safe library
 6    mulle_buffer_add_bytes(&buf, input, strlen(input));
 7    
 8    // ✅ ALLOWED: Safe API provides guarantees
 9    char *result = mulle_buffer_extract_string(&buf);
10    
11    // Use result safely...
12    mulle_buffer_done(&buf);
13}

5.2 Safe Library Implementation #

 1// Safe library (TRUST_SAFE_LIBRARY level)
 2__attribute__((trust_level(TRUST_SAFE_LIBRARY)))
 3int mulle_buffer_add_bytes(struct mulle_buffer *buffer, const void *data, size_t len) {
 4    // ✅ ALLOWED: Safe library handles NULL safely
 5    if (!buffer || !data) return -1;  // null_safe contract
 6    
 7    // ✅ ALLOWED: Safe library prevents overflow
 8    if (len > SIZE_MAX - buffer->size) return -1;  // no_overflow contract
 9    
10    // ✅ ALLOWED: Safe library can call libc
11    if (buffer->size + len > buffer->capacity) {
12        void *new_data = realloc(buffer->data, buffer->capacity * 2);
13        if (!new_data) return -1;
14        buffer->data = new_data;
15        buffer->capacity *= 2;
16    }
17    
18    // ✅ ALLOWED: Safe library can do pointer arithmetic safely
19    memcpy((char*)buffer->data + buffer->size, data, len);
20    buffer->size += len;
21    return 0;
22}

5.3 Trust Violations #

 1// Trust violations that would be caught
 2void bad_user_code(void) {
 3    // ❌ VIOLATION: User code calling libc directly
 4    char *ptr = malloc(100);  // Linker error!
 5    
 6    // ❌ VIOLATION: User code doing pointer arithmetic  
 7    ptr[50] = 'x';  // Linker error!
 8    
 9    // ❌ VIOLATION: User code calling unsafe API
10    strcpy(ptr, "hello");  // Linker error!
11}

6. Linker Verification #

6.1 Verification Algorithm #

 1bool verify_operation_metadata(operation_metadata_t *metadata) {
 2    switch (metadata->type) {
 3        case RAW_POINTER_ARITHMETIC:
 4            // Only allowed for TRUST_SAFE_LIBRARY and below
 5            return metadata->caller_level >= TRUST_SAFE_LIBRARY;
 6            
 7        case SAFE_API_CALL:
 8            // Verify API contract exists and caller has permission
 9            return verify_api_contract_exists(metadata->api_info.api_name) &&
10                   verify_call_permission(metadata->caller_level, 
11                                         metadata->api_info.api_level);
12            
13        case TRUST_VIOLATION:
14            // Always a violation
15            return false;
16            
17        default:
18            return true;
19    }
20}

6.2 Call Permission Verification #

 1bool verify_call_permission(enum trust_level caller_level, 
 2                           enum trust_level callee_level) {
 3    // Rule 1: Can only call same level or higher trust
 4    if (callee_level < caller_level) {
 5        return false;  // Lower trust level cannot call higher trust
 6    }
 7    
 8    // Rule 2: User code cannot call libc directly
 9    if (caller_level == TRUST_USER_CODE && callee_level == TRUST_LIBC) {
10        return false;
11    }
12    
13    return true;
14}

7. Linker Output and Reporting #

7.1 Violation Reports #

 1$ ld -verify-trust-levels user.o -lmulle-buffer -o program
 2
 3TRUST VIOLATIONS FOUND:
 4----------------------------------------
 5user.o:bad_user_code:23 - TRUST_USER_CODE calling malloc (TRUST_LIBC)
 6  └─ User code cannot call libc directly. Use safe library instead.
 7
 8user.o:bad_user_code:24 - TRUST_USER_CODE performing pointer arithmetic
 9  └─ User code cannot perform pointer arithmetic. Use safe APIs.
10
11user.o:unsafe_function:45 - TRUST_USER_CODE calling strcpy (TRUST_LIBC)  
12  └─ User code cannot call unsafe libc functions. Use mulle_buffer_add_bytes.
13
14TRUST SUMMARY:
15----------------------------------------
16- User code: 15 function calls, 3 violations
17- mulle-buffer: 8 libc calls (all permitted)
18- Safety score: 80% (3 violations out of 15 operations)
19
20RECOMMENDATIONS:
21----------------------------------------
22- Replace malloc/free with mulle_buffer APIs
23- Replace pointer arithmetic with safe buffer operations
24- Use mulle_buffer_add_bytes instead of strcpy

7.2 Success Report #

 1$ ld -verify-trust-levels clean_user.o -lmulle-buffer -o program
 2
 3TRUST VERIFICATION PASSED:
 4----------------------------------------
 5- User code: 12 function calls, 0 violations
 6- mulle-buffer: 8 libc calls (all permitted via safety contracts)
 7- Safety score: 100%
 8
 9SAFETY CONTRACTS VERIFIED:
10----------------------------------------
11- mulle_buffer_add_bytes: no_overflow, null_safe, bounds_checked ✓
12- mulle_buffer_get_string: null_safe, memory_safe ✓
13- mulle_buffer_done: memory_safe ✓

8. Benefits and Advantages #

8.1 Safety Without Performance Cost #

8.2 Gradual Adoption #

8.3 Clear Responsibility Model #

9. Implementation Considerations #

9.1 Compiler Integration #

9.2 Linker Modifications #

9.3 Standard Library Integration #

10. Future Work #

10.1 Advanced Contracts #

10.2 Dynamic Verification #

10.3 Language Extensions #

11. Conclusion #

The proposed trust-level safety verification system provides a practical approach to C safety that balances security with usability. By establishing clear trust boundaries and allowing safe libraries to provide guarantees through API contracts, we can achieve memory safety for user code while maintaining the performance and flexibility that makes C attractive for systems programming.

The key insight is that safety is not binary but layered - different levels of code should have different privileges and responsibilities. This approach allows for gradual adoption and provides a clear migration path from unsafe to safe C programming practices.


This whitepaper presents a conceptual framework for trust-level safety verification in C compilers. Implementation details may vary based on specific compiler and linker architectures.

last updated: