Precise and Scalable Detection of Double-Fetch Bugs in OS Kernels Meng Xu , Chenxiong Qian, Kangjie Lu + , Michael Backes*, Taesoo Kim Georgia Tech | University of Minnesota + | CISPA, Germany* � 1
What is Double-Fetch?
Address Space Separation 0x00000000 User / Program 3 GB Address Space 0xC0000000 Kernel 1 GB Address Space 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 3
No Dereference on Userspace Pointers 0x00000000 User / Program 3 GB Address Space 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB …… Address Space Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 4
No Dereference on Userspace Pointers 0x00000000 User / Program 3 GB Address Space 0xDEADBEEF 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB …… Address Space 0xDEADBEEF Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 5
No Dereference on Userspace Pointers 0x00000000 User / Program 3 GB Address Space 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB *kptr = *uptr; …… Address Space 0xDEADBEEF Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 6
No Dereference on Userspace Pointers 0x00000000 User / Program 3 GB Address Space 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB copy_from_user(kptr, uptr, 4); …… Address Space 0xDEADBEEF Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 7
Shared Userspace Pointer Across Threads 0x00000000 …… User / Program 3 GB Address Space 0xDEADBEEF 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB copy_from_user(kptr, uptr, 4); …… Address Space 0xDEADBEEF Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 8
Shared Userspace Pointer Across Threads 0x00000000 …… User / Program 3 GB Address Space 0xDEADBEEF 0xDEADBEEF 0xC0000000 void kfunc (int __user *uptr, int *kptr) { Kernel 1 GB copy_from_user(kptr, uptr, 4); …… Address Space 0xDEADBEEF Uninitialized } 0xFFFFFFFF A Typical Address Space Separation Scheme with a 32-bit Virtual Address Space � 9
Why Double-Fetch? 1 static int perf_copy_attr_simplified ?? bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 4 5 u32 size; 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 10
Why Double-Fetch? 1 static int perf_copy_attr_simplified ?? bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 11
Why Double-Fetch? 1 static int perf_copy_attr_simplified ?? bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 12
Why Double-Fetch? 1 static int perf_copy_attr_simplified ?? bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 13
Why Double-Fetch? 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 14
Why Double-Fetch? 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 30 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 15
Why Double-Fetch? 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 30 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 16
What Goes Wrong in This Process?
Up-until First-Fetch 1 static int perf_copy_attr_simplified ?? bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 30 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 18
Wrong Assumption: Atomicity in Syscall 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 65535 30 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 19
Wrong Assumption: Atomicity in Syscall 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 65535 65535 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 65535 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later 24 memcpy(buf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 20
When The Exploit Happens 1 static int perf_copy_attr_simplified 30 bytes 2 (struct perf_event_attr __user *uattr, 3 struct perf_event_attr *attr) { 65535 4 5 u32 size; 4 bytes 6 7 // first fetch 8 if (get_user(size, &uattr->size)) 30 9 return -EFAULT; 10 11 // sanity checks 12 if (size > PAGE_SIZE || 13 size < PERF_ATTR_SIZE_VER0) 14 return -EINVAL; 15 16 // second fetch 17 if (copy_from_user(attr, uattr, size)) 65535 18 return -EFAULT; 19 20 ...... 21 } 22 23 // BUG: when attr->size is used later kernel information leak! 24 copy_to_user(ubuf, attr, attr->size); Adapted from perf_copy_attr in file kernel/events/core.c � 21
Why Double-Fetch is Prevalent in Kernels? 1. Size checking 2. Dependency look-up 3. Protocol/signature check 4. Information guessing 5. ……
Double-Fetch: Dependency Lookup Adapted from __mptctl_ioctl in file drivers/message/fusion/mptctl.c � 23
Recommend
More recommend