270 likes | 377 Vues
Memory Safety Without Runtime Checks or Garbage Collection. By Dinakar Dhurjati Joint work with Sumant Kowshik, Vikram Adve and Chris Lattner University of Illinois at Urbana-Champaign. Motivation. Secure Online Upgrades in Embedded Systems.
E N D
Memory Safety Without Runtime Checks or Garbage Collection By Dinakar Dhurjati Joint work with Sumant Kowshik, Vikram Adve and Chris Lattner University of Illinois at Urbana-Champaign
Motivation Secure Online Upgrades in Embedded Systems Upgrade := new software modules in to host application • Same address space • Need to protect the host from Buggy/Untrusted modules • Need to ensure each module is memory safe Memory safe := Never access a memory location outside its data area Never execute instructions outside its code area Need Language and Compiler support for memory safety
Existing Solutions Type checker disallows Runtime null pointer checks Runtime array bound checks Not expressible No free + Garbage Collection [ + scoped regions + runtime checks] Runtime checksorgarbage collection unattractive for Embedded Code
Our Approach Goal : 100 % Static Checking • Minimal semantic restrictions to enable static checking • No new syntax or annotations • Aggressive compiler techniques (old and new)
Our Previous Work[CASES2002] Static safety for real time control programs Type checker disallows Initialize to reserved address range Restrict index to be affine in terms of size Language rule + Compiler checks ???
Contributions of this Work • 100% static technique for ensuring heap safety for “type safe” C programs • No Runtime Checks • No Garbage Collection : allow explicit deallocation! • No Programmer annotations • Evaluate our approach on 17 embedded benchmarks • Array Safety • Heap safety • Stack safety
Talk Outline • Introduction • Our Solution • Results • Related Work • Conclusion
Methodology • Do not prevent uses of dangling pointers to freed memory • Ensure that they cannot cause memory safety violation • Builds on a compiler transformation called “Automatic Pool Allocation”
Dangling pointer problem p r r q q->next q q q struct S *p, *q; …. p = q …. free(p) … q->next->val = … //dangling pointer usage r = malloc(sizeof(struct T))
Making Dangling Pointers Safe s q s ->next q->next q struct S *p, *q; …. p = q …. free(p) … q->next->val = … //dangling pointer usage s = malloc(sizeof(struct S)) Principle : If freed memory is reallocated to any object of the same type with same alignment, then dereferencing pointers to freed memory is safe.
Exploiting the principle • First simple solution • N different heaps based on type, N = #types • Never move memory from one heap to other BUT : Increased memory consumption • A more sophisticated solution : Using previously developed compiler transformation called Automatic Pool Allocation
Automatic Pool Allocation • Identifies logical data structures not reachable from outside a function • Creates a separate pool (region) for nodes of that data structure. • At the function exit, entire pool is deallocated • Advantages : • Fine grained pools • Small life times • Type homogenous pools • Explicit deallocation
Pool Allocation Example h(struct S *p) { … for (j = 0; j < 100000; j++) { tmp = malloc(sizeof(struct s)) insert_tmp_to_list(tmp,p); …. q = least_useful_member(p) free(q); } … } p f() { … p =g(); … p->next->val = ….. } g() { … p = create_list_10_nodes(p); h(p); free_everything_but_head(p); … return p; }
Pool Allocation Example h(struct S *p, PoolPointer *PP) { … for (j = 0; j < 100000; j++) { tmp = poolalloc(PP); insert_tmp_to_list(tmp,p); …. q = least_useful_member(p); poolfree(q, PP); } … } PP p f() { PP = poolinit(struct S); … p =g(PP); … p->next->val = ….. pooldestroy(PP) } g(PoolPointer *PP) { … p = create_list_10_nodes(PP); h(p, PP); free_everything_but_head(p, PP); … return p; } Not Memory Safe
Using Pool Allocation for Safety • Pools are type homogenous • Restriction : Do not release memory from a pool until pooldestroy => The principle is satisfied : “Accessible freed memory is reallocated only to objects of the same type”. Memory Safety guaranteed Problem • Could lead to increased memory consumption • Need to identify when it happens
Identifying increased memory usage h(struct S *p, PoolPointer *PP) { … //no free after allocation for (j = 0; j < 10; j++) { …. q = least_useful_member(p) poolfree(q,PP); } … } h(struct S *p, PoolPointer *PP) { … for (j = 0; j < 100000; j++) { tmp = poolalloc(PP); tmp2 = poolalloc(PP2); insert_tmp_to_list(tmp,p); …. q = least_useful_member(p) poolfree(q,PP); } … } h(struct S *p, PoolPointer *PP) { … for (j = 0; j < 100000; j++) { tmp = poolalloc(PP); insert_tmp_to_list(tmp,p); …. q = least_useful_member(p) poolfree(q,PP); } … } f() { PP = poolinit(struct S); … g(p, PP); … p->next->val = ….. pooldestoy(PP); } g(Struct S *p, PoolPointer *PP) { … create_list_10_nodes(p, PP); h(p, PP); free_everything_but_head(p, PP); … } Case 1 : No Reuse Case 2 : Self Reuse Case 3 : Cross Reuse Case 2 : Self Reuse
Algorithm On all control flow paths interprocedurally For every poolfree(…, P) on the path, if before the subsequent pooldestroy(P) there is No poolalloc : P is Case 1 poolalloc only from the same pool : P is Case 2 poolalloc from a different pool : P is Case 3
Implementation C++ LLVM Object code LLVM Linker C GCC Type Safety Array Safety Uninit. Variables Stack Safety Categorizing pools Safe Code with no checks Pool Allocation • Source Language Independence • Link – time Analysis => whole program analysis
Evaluation • 17 applications in MediaBench and MIBench suite of bench marks • Studied how easy it is to port them • Results for 6 of them Total for 17 pgms
Results : Heap Safety All 17 programs are proven heap safe! • 15 Programs had only Case 1 or Case 2 pools Memory safety without increase in memory consumption • 2 programs with Case 3 pools • Rasta : 5 Case-3 pools out of 14 • Epic : 1 Case-3 pool out of 14 • Further Analysis can convert some Case 3 pools to Case 2
Results - II How do our previous techniques work ? • Stack Safety • 16 codes passed the compiler • 1 code needs restructuring to pass • Array Safety • Only 8 codes passed • Indirection vectors caused 5 codes to fail • Detected 4 bugs in benchmarks Array Bounds Checking remaining bottleneck for 100% static checking
Related Work : Static Heap Safety Checking Linear and alias type systems : Vault • Severely restrict aliasing in programs • Require lot of annotations Region based Schemes : TofteTalpin[TOPLAS98], Aiken[PLDI98], Cyclone, RT-Java, Boyapati[PLDI03] • No deallocation within a region • Manual region annotations in most cases
Conclusions • Result : Guarantee heap safety statically for “type safe” C. => New State of the Art : 100 % Static Checking for all C codes that • Are “type safe” • Use only affine array references
URLs SAFECode StaticAnalysisForsafeExecutionof Code http://safecode.cs.uiuc.edu/ LLVM http://llvm.cs.uiuc.edu
Pool Allocation Example h(struct S *p) { … for (j = 0; j < 100000; j++) { insert_tmp_to_list(tmp); …. q = least_useful_member(p) free(q); } … } poolfree(PP,q) f() { … g(p); … p->next->val = ….. } PP = poolinit(Struct S); tmp = malloc(sizeof(Struct S)) tmp = poolalloc(PP) pooldestroy(PP); g(Struct S *p) { … create_list_10_nodes(p); h(p); free_everything_but_head(p); … }
Results : Heap Safety • All 17 are guaranteed heap safe • 15 programs had only Case 1 or Case 2 poolsMemory safety without increase in memory consumption • 2 programs with Case 3 pools • rasta -- 5 Case 3 out of 14 pools • epic -- 1 Case 3 out of 13 pools • Further analysis can convert some case 3 to case 2 pools
Conclusions and Future Work • 100 % static checking for heap safety • Future Work • Improve the array bounds checker • Evaluate the actual increase, if any, in the few case 3 pools. • Detect illegal system calls/illegal sequences of system calls