Checked C Michael Hicks The University of Maryland joint work with David Tarditi (MSR), Andrew Ruef (UMD), Sam Elliott (UW) UM
Motivation - Lots of C/C++ code out there. - One open source code indexer (openhub.net) found 8.5 billion lines of C code. - Huge investment in existing (often unsafe) code - Many approaches “saving C” have been proposed : - Static/dynamic analysis (ASAN, Softbound), fuzzing, OS/HW- level mitigations - Rewrite the code in a type-safe language (Java/Rust…) - New dialects of C (Cyclone, CCured, Deputy, …) 2
Checked C Checked C is another take at making system software written in C more reliable and secure. Approach: - Extend C so that type-safe programs can be written in it. - A new language risks introducing unnecessary differences. - Backward compatible , support incremental conversion of code to be type-safe. - Implement in widely used C compilers (Clang/ LLVM ) - Create automated conversion tools - Hypothesis: most source code behaves in a type-safe fashion. - Evaluate and get real-world use . Iterate based on experience. https://github.com/Microsoft/checkedc https://github.com/Microsoft/checkedc-clang 3
Design Like Cyclone • Making code type safe • Static/dynamic bounds checking for pointers and arrays • Initialization of data, type casts, explicit memory management • No temporal safety enforcement at present (use GC) Unlike Cyclone • Backward compatibility • All pointers binary-compatible (no “fat” pointers) • Strict extension (parsing) of C • Unchecked and checked code can co-exist (e.g., in a function) • The former can use checked types in a looser manner • Can prove a “blame” property that characterizes isolation • Can work with improving hardware designs 4
Clang & LLVM Checked LLVM C's x86 / Clang Analyses x64 / Frontend ARM / Optimizations Analyses Other Assembly LLVM IR Code Generators Generator 5
Pointers T* val = malloc(sizeof(T)); Singleton Pointer ptr<T> val = malloc(sizeof(T)); T* vals = calloc(n, sizeof(T)); T vals[N] = { ... }; Array Pointer array_ptr<T> vals = calloc(n, sizeof(T)); T vals checked[N] = { ... }; 6
Bounds Declarations Declaration Invariant array_ptr<T> p : bounds(l, u) l ≤ p < u array_ptr<T> p : count(n) p ≤ p < p+n array_ptr<T> p : byte_count(n) p ≤ p < (char*)p + n Expressions in bounds(l, u) must be non-modifying - No Assignments or Increments/Decrements - No Calls 7
NUL Terminated Pointers char* str = calloc(n, sizeof(char)); NT Array Pointer char str[N] = { ... }; nt_array_ptr<char> str : count(n-1) = calloc(n, sizeof(char)); char str checked[N+1] = { …, ‘\0’ }; l ≤ p ≤ u && ∃ c ≥ u. *c == ‘\0’ nt_array_ptr<T> p : bounds(l, u) can read *u or write ‘\0’ to it 8
Bounds Expansion size_t my_strlcpy( nt_array_ptr<char> dst: count(dst_sz - 1), nt_array_ptr<char> src, size_t dst_sz) { size_t i = 0; nt_array_ptr<char> s : count(i) = src; while (s[i] != ’\0’ && i < dst_sz - 1) { //bounds on s may expand by 1 dst[i] = s[i]; ++i; } dst[i] = ’\0’; //ok to write to upper bound return i; } 9
Where Dynamic Checks Occur p[n] Pointer Dereference int i = *p; p->field Assignment *p = 0; Compound Assignment *p += 1; Increment/Decrement (*p)++; Elided if the compiler can prove the access is safe 10
Copy data bool echo( int16_t user_length, from user_payload size_t user_payload_len, into new buffer in char *user_payload, resp_t *resp) { resp object char *resp_data = malloc(user_length); Example inspired by resp->payload_buf = resp_data; Heartbleed error resp->payload_buf_len = user_length; // memcpy(resp->payload_buf, user_payload_buf, user_length) for (int i = 0; i < user_length; i++) { resp->payload_buf[i] = user_payload_buf[i]; } return true; } user_length is user_payload_len is typedef struct { size_t payload_len; provided by user from the parser char *payload; // ... } resp_t; 11
Copy data bool echo( int16_t user_length, from user_payload size_t user_payload_len, into new buffer in char *user_payload, resp_t *resp) { resp object char *resp_data = malloc(user_length); resp->payload = resp_data; resp->payload_len = user_length; // memcpy(resp->payload_buf, user_payload_buf, user_length) malloc could fail for (int i = 0; i < user_length; i++) { resp->payload_buf[i] = user_payload_buf[i]; } return true; } user_length is user_payload_len is typedef struct { size_t payload_len; provided by user from the parser char *payload; // ... } resp_t; 12
Copy data bool echo( int16_t user_length, from user_payload size_t user_payload_len, into new buffer in char *user_payload, resp_t *resp) { resp object char *resp_data = malloc(user_length); resp->payload = resp_data; resp->payload_len = user_length; // memcpy(resp->payload, user_payload, user_length) for (size_t i = 0; i < user_length; i++) { resp->payload[i] = user_payload[i]; } return true; } user_length is user_payload_len is typedef struct { size_t payload_len; user_length could be provided by user from the parser char *payload; // ... larger than user_payload_len } resp_t; 13
bool echo( Step 1: int16_t user_length, Manually size_t user_payload_len, array_ptr<char> user_payload, Convert to ptr<resp_t> resp) { Checked Types array_ptr<char> resp_data = malloc(user_length); resp->payload = resp_data; resp->payload_len = user_length; // memcpy(resp->payload, user_payload, user_length) for (size_t i = 0; i < user_length; i++) { resp->payload[i] = user_payload[i]; } return true; } typedef struct { size_t payload_len; array_ptr<char> payload; // ... } resp_t; 14
bool echo( int16_t user_length, size_t user_payload_len, array_ptr<char> user_payload : count(user_payload_len), ptr<resp_t> resp) { array_ptr<char> resp_data : count(user_length) = malloc(user_length); resp->payload = resp_data; resp->payload_len = user_length; Step 2: Manually // memcpy(resp->payload, user_payload, user_length) for (size_t i = 0; i < user_length; i++) { Add Bounds resp->payload[i] = user_payload[i]; Declarations } return true; } typedef struct { size_t payload_len; array_ptr<char> payload : count(payload_len); // ... } resp_t; 15
bool echo( Step 3: int16_t user_length, Compiler Inserts size_t user_payload_len, array_ptr<char> user_payload : count(user_payload_len), Checks Automatically ptr<resp_t> resp) { array_ptr<char> resp_data : count(user_length) = malloc(user_length); dynamic_check(resp != NULL); resp->payload = resp_data; resp->payload_len = user_length; malloc now checked // memcpy(resp->payload, user_payload, user_length) for (size_t i = 0; i < user_length; i++) { dynamic_check(user_payload != NULL); dynamic_check(user_payload <= &user_payload[i]); dynamic_check(&user_payload[i] < user_payload + user_payload_len); dynamic_check(resp->payload != NULL); dynamic_check(resp->payload <= &resp->payload[i]); dynamic_check(&resp->payload[i] < resp->payload + resp->payload_len resp->payload[i] = user_payload[i]; } No Memory Disclosure return true; 16 }
bool echo( Step 3: int16_t user_length, Compiler Inserts size_t user_payload_len, array_ptr<char> user_payload : count(user_payload_len), Checks Automatically ptr<resp_t> resp) { array_ptr<char> resp_data : count(user_length) = malloc(user_length); Code Not Bug-Free: dynamic_check(resp != NULL); Will signal run-time error if either resp->payload = resp_data; resp->payload_len = user_length; malloc now checked - malloc(user_length) fails // memcpy(resp->payload, user_payload, user_length) - user_length > user_payload_len for (size_t i = 0; i < user_length; i++) { dynamic_check(user_payload != NULL); dynamic_check(user_payload <= &user_payload[i]); dynamic_check(&user_payload[i] < user_payload + user_payload_len); But: Vulnerable Executions Prevented dynamic_check(resp->payload != NULL); dynamic_check(resp->payload <= &resp->payload[i]); dynamic_check(&resp->payload[i] < resp->payload + resp->payload_len resp->payload[i] = user_payload[i]; } No Memory Disclosure return true; 17 }
Step 4: bool echo( int16_t user_length, Restrictions on bounds size_t user_payload_len, expressions may allow removal array_ptr<char> user_payload : count(user_payload_len), ptr<resp_t> resp) { array_ptr<char> resp_data : count(user_length) = malloc(user_length); dynamic_check(resp != NULL); resp->payload = resp_data; resp->payload_len = user_length; malloc still checked dynamic_check(user_payload != NULL); dynamic_check(resp->payload != NULL); // memcpy(resp->payload, user_payload, user_length) for (size_t i = 0; i < user_length; i++) { dynamic_check(i <= user_payload_len); resp->payload[i] = user_payload[i]; } return true; } No Memory Disclosure 18
Partially Converted Code - We may not want (or be able) to port all at once - Can use checked types wherever we want - Allows some hard-to-prove-safe idioms - But adds risk void more(int *b, int idx, ptr<int *>out) { int oldidx = idx, c; do { c = readvalue(); b[idx++] = c; //could overflow b? } while (c != 0); *out = b+idx-oldidx; //bad if out corrupted } 19
Recommend
More recommend