i386 / sysenter safety test case

From: Zachary Amsden
Date: Tue Mar 08 2005 - 19:40:09 EST


Code inspection of entry.S on i386 showed a potential problem - load through segment without verifying "flatness" on the sysenter path. Turns out this code is safe, but only by a thread ..

ENTRY(sysenter_entry)
movl TSS_sysenter_esp0(%esp),%esp
sysenter_past_esp:
sti
pushl $(__USER_DS)
pushl %ebp
pushfl
pushl $(__USER_CS)
pushl $SYSENTER_RETURN

/*
* Load the potential sixth argument from user stack.
* Careful about security.
*/
cmpl $__PAGE_OFFSET-3,%ebp
jae syscall_fault
1: movl (%ebp),%ebp

If it weren't for the fact that %ebp relative addresses default to using the SS segment, we could have loaded through a user segment here to read arbitrary memory (sysenter does nothing to DS segment). Perhaps this was considered before, but because of the implications, I thought this might be worth annotating in the source. Also provided a test case. Obviously only works on sysenter capable processors. Tested on 2.6.8.

Zach Amsden
zach@xxxxxxxxxx
#include <sys/syscall.h>

.text
.global sysenter_call
.global sysenter_call_2

/* void sysenter_call(pid_t pid, int signo, short ds, void *addr) */

sysenter_call:
push %ebx
push %edi
push %ebp
push %ds
movl %esp, %edi
movl 20(%esp), %ebx /* pid */
movl 24(%esp), %ecx /* signo */
movl 28(%esp), %ds /* exploit DS */
movl 32(%esp), %ebp
movl %ebp, %esp
push $sysenter_return
push %ecx
push %edx
subl $16, %ebp
push $0xbaadf00d
movl $SYS_kill, %eax
sysenter

/* vsyscall page will ret to us here */
sysenter_return:
mov %edi, %esp
pop %ds
pop %ebp
pop %edi
pop %ebx
ret

sysenter_call_2:
push %ebx
push %ebp
movl 20(%esp), %ebx /* pid */
movl 24(%esp), %ecx /* signo */
movl 28(%esp), %ebp
movl $SYS_kill, %eax
sysenter

.data
test: .long 0
/*
* Copyright (c) 2005, Zachary Amsden (zach@xxxxxxxxxx)
* This is licensed under the GPL.
*/

#include <stdio.h>
#include <signal.h>
#include <asm/ldt.h>
#include <asm/segment.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#define __KERNEL__
#include <asm/page.h>

extern void sysenter_call(pid_t pid, int signo, short ds, void *addr);
extern void sysenter_call_2(pid_t pid, int signo, void *addr);
void catch_sig(int signo, struct sigcontext ctx)
{
__asm__ __volatile__("mov %0, %%ds" : : "r" (__USER_DS));
printf("interrupted %%ebp = 0x%x\n", ctx.ebp);
if (ctx.ebp == 0xbaadf00d)
printf("phew\n");
}

void main(void)
{
struct user_desc desc;
short ds;
unsigned long addr;
unsigned *stack;
unsigned long offset;

stack = (unsigned *)mmap(0, 4096, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
stack = &stack[1024];
addr = 0xf0000; /* Try to read BIOS */
offset = __PAGE_OFFSET-(unsigned)stack+addr+16;
signal(SIGUSR1, catch_sig);
desc.entry_number = 0;
desc.base_addr = offset;
desc.limit = 0xffffff;
desc.seg_32bit = 1;
desc.contents = MODIFY_LDT_CONTENTS_DATA;
desc.read_exec_only = 0;
desc.limit_in_pages = 1;
desc.seg_not_present = 0;
desc.useable = 1;
if (modify_ldt(1, &desc, sizeof(desc)) != 0) {
perror("modify_ldt");
}
ds = 0x7; /* TI | RPL 3 */
sysenter_call(getpid(), SIGUSR1, ds, stack);
sysenter_call_2(getpid(), SIGSTOP, __PAGE_OFFSET+4096);
printf("not reached - core should show %%eax == -EFAULT\n");
}