770 likes | 861 Views
Verifying the Safety of User Pointer Dereferences. Suhabe Bugrara suhabe@stanford.edu Stanford University Joint work with Alex Aiken. Unchecked User Pointer Dereferences. Security property of operating systems Two types of pointers in operating systems
E N D
Verifying the Safety of User Pointer Dereferences Suhabe Bugrara suhabe@stanford.edu Stanford University Joint work with Alex Aiken
Unchecked User Pointer Dereferences • Security property of operating systems • Two types of pointers in operating systems • kernel pointer: pointer created by the operating system • user pointer: pointer created by a user application and passed to the operating system via an entry point such as a system call • Must check that a user pointer points into userspace before dereferencing it
Unchecked User PointerDereferences 1: static ssize_t read_port(…, char * __user buf, …) { 2: unsigned long i = *ppos; 3: char * __user tmp = buf; 4:
Unchecked User PointerDereferences 1: static ssize_t read_port(…, char * __user buf, …) { 2: unsigned long i = *ppos; 3: char * __user tmp = buf; 4: 7: 8: while (count-- > 0 && i < 65536) { 9: if (__put_user(inb(i),tmp) < 0) //deref 10: return -EFAULT; 11: i++; 12: tmp++; 13: } 14: 15: *ppos = i; 16: return tmp-buf; 17: }
Unchecked User PointerDereferences 1: static ssize_t read_port(…, char * __user buf, …) { 2: unsigned long i = *ppos; 3: char * __user tmp = buf; 4: 5: if (!access_ok(..,buf,...)) //check 6: return -EFAULT; 7: 8: while (count-- > 0 && i < 65536) { 9: if (__put_user(inb(i),tmp) < 0) //deref 10: return -EFAULT; 11: i++; 12: tmp++; 13: } 14: 15: *ppos = i; 16: return tmp-buf; 17: }
Security Vulnerability • Malicious user could • Take control of the operating system • Overwrite kernel data structures • Read sensitive data out of kernel memory • Crash machine by corrupting data
Goal • Design a program analysis to prove statically that no unchecked user pointer dereferences exist in the entire operating system
Challenges • Verification • provide guarantee of correctness • Precision • report low number of false alarms • Scalability • analyze more than 6 MLOC
Verification • Soundness • If the program analysis reports that no vulnerabilities exist, then the program contains none
Verification • Soundness • If the program analysis reports that no vulnerabilities exist, then the program contains none • Completeness • If the program analysis reports that a vulnerability exists, then program contains one
Verification • Soundness • If the program analysis reports that no vulnerabilities exist, then the program contains none • Completeness • If the program analysis reports that a vulnerability exists, then program contains one • Impossible for a program analysis to be both sound and complete
Sound and Incomplete Verifier • Proves the absence of vulnerabilities • May report false alarms
Soundness Caveats • Unsafe memory operations • Concurrency • Inline assembly • Analysis fails to analyze some procedures
Precision • Minimize the number of false alarms • Reasoning more deeply about program • Computationally expensive • High precision inhibits scalability
Example 1: void sys_call (int *u, const int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: }
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user)
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) (*u,user)
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) (*u,user) (*u,user)
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) (*u,user) (*u,user) (*u,user) (*u,checked)
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) (*u,user) (*u,user) (*u,user) (*u,checked) (*u,user) lost precision!
One Possible Approach 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) (*u,user) (*u,user) (*u,user) (*u,checked) (*u,user) lost precision! (*u,user) (*u,error) emit warning! …, but, procedure does not contain any vulnerabilities!
Path Sensitivity • Ability to reason about branch correlations • Programs use substantial amount of branch correlation in practice • Important for reducing the number of false alarms
Example 1: void sys_call (int *u, int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: }
Path Sensitivity 1: void sys_call (int *u, int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: } Valid Path
Path Sensitivity 1: void sys_call (int *u, const int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: } Valid Path
Path Sensitivity 1: void sys_call (int *u, const int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: } Valid Path
Path Sensitivity 1: void sys_call (int *u, const int cmd) { //u is user pointer 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { //check u 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; //dereference u 12: } Invalid Path!
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: }
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true
Path Sensitive Analysis “guard” 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) . . .
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) cmd == 1 && . . .
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) cmd == 1 && !(cmd == 1) && . . .
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) cmd == 1 && !(cmd == 1) && true . . .
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) cmd == 1 && !(cmd == 1) && true false
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) false
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) false
Scalability • Abstraction • Throw away guards at procedure boundaries • Compositionality • Analyze each procedure in isolation
Path Sensitive Analysis 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) false
Abstraction (*u,user) true (*u,checked) cmd == 1 (*u,error) false initial summary
Abstraction α = (*u,user) true (*u,checked) cmd == 1 (*u,error) false abstraction function initial summary
Abstraction α = (*u,user) true (*u,checked) cmd == 1 (*u,error) false (*u,user) true (*u,checked) false (*u,error) false abstraction function initial summary final summary
Abstraction 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,error) false
Abstraction 1: void sys_call (int *u, const int cmd) { 2: int x; 3: 4: if (cmd == 1) { 5: if (!access_ok(u)) { 6: return; 7: } 8: } 9: … 10: if (cmd == 1) 11: x = *u; 12: } (*u,user) true (*u,user) true (*u,user) true (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) cmd == 1 (*u,user) true (*u,checked) false (*u,error) false
Compositionality 1: int get (int *v) { 2: int x; 3: 4: x = *v; 5: 6: return x; 7: }