770 likes | 867 Vues
This research aims to verify the safety of user pointer dereferences to prevent security vulnerabilities in operating systems. The study examines the security implications of unchecked user pointers and proposes a program analysis approach for detecting and rectifying potential issues. The goal is to design a verification tool that can prove statically the absence of unchecked user pointer dereferences in the entire operating system. The challenges include ensuring correctness, precision with minimal false alarms, and scalability to analyze large codebases. The study discusses the soundness and completeness of the verification process, while also addressing potential issues like unsafe memory operations and concurrency. Despite its computational costs, achieving high precision is crucial for inhibiting scalability issues. The goal is to provide robust protection against malicious exploitation and enhance the overall security of 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: }