1 / 77

Verifying the Safety of User Pointer Dereferences

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

andrew
Download Presentation

Verifying the Safety of User Pointer Dereferences

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Verifying the Safety of User Pointer Dereferences Suhabe Bugrara suhabe@stanford.edu Stanford University Joint work with Alex Aiken

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

  3. Unchecked User PointerDereferences 1: static ssize_t read_port(…, char * __user buf, …) { 2: unsigned long i = *ppos; 3: char * __user tmp = buf; 4:

  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: }

  5. 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: }

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

  7. Goal • Design a program analysis to prove statically that no unchecked user pointer dereferences exist in the entire operating system

  8. Challenges • Verification • provide guarantee of correctness • Precision • report low number of false alarms • Scalability • analyze more than 6 MLOC

  9. Verification

  10. Verification • Soundness • If the program analysis reports that no vulnerabilities exist, then the program contains none

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

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

  13. Sound and Incomplete Verifier • Proves the absence of vulnerabilities • May report false alarms

  14. Soundness Caveats • Unsafe memory operations • Concurrency • Inline assembly • Analysis fails to analyze some procedures

  15. Precision • Minimize the number of false alarms • Reasoning more deeply about program • Computationally expensive • High precision inhibits scalability

  16. 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: }

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

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

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

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

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

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

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

  24. 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: }

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

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

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

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

  29. 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: }

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

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

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

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

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

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

  36. 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)  . . .

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

  38. 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) && . . .

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

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

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

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

  43. Scalability • Abstraction • Throw away guards at procedure boundaries • Compositionality • Analyze each procedure in isolation

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

  45. Abstraction (*u,user)  true (*u,checked)  cmd == 1 (*u,error)  false initial summary

  46. Abstraction α = (*u,user)  true (*u,checked)  cmd == 1 (*u,error)  false abstraction function initial summary

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

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

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

  50. Compositionality 1: int get (int *v) { 2: int x; 3: 4: x = *v; 5: 6: return x; 7: }

More Related