checked c
play

Checked C Michael Hicks The University of Maryland joint work with - PowerPoint PPT Presentation

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


  1. Checked C Michael Hicks The University of Maryland joint work with David Tarditi (MSR), Andrew Ruef (UMD), Sam Elliott (UW) UM

  2. 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

  3. 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

  4. 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

  5. Clang & LLVM Checked LLVM C's x86 / Clang Analyses x64 / Frontend ARM / Optimizations Analyses Other Assembly LLVM IR Code Generators Generator 5

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  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); 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

  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_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

  13. 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

  14. 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

  15. 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

  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); 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 }

  17. 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 }

  18. 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

  19. 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