When userland doesn't specify sigaction->sa_restorer, we try to put the restorer code on the stack. But this breaks ia32 binaries with non-executable stacks. We now put the restorer code on a gate page. Signed-off-by: Kevin Tian Signed-off-by: Arun Sharma arch/ia64/ia32/binfmt_elf32.c | 39 +++++++++++++++++++++++++++++++++++++++ arch/ia64/ia32/ia32_signal.c | 33 ++++++++++++++++++++++----------- arch/ia64/ia32/ia32_support.c | 28 +++++++++++++++++++++++++--- arch/ia64/ia32/ia32priv.h | 11 ++++++++--- arch/ia64/mm/init.c | 2 +- include/asm-ia64/ia32.h | 2 +- 6 files changed, 96 insertions(+), 19 deletions(-) diff -purN -X dontdiff linux-2.6.8.ori/arch/ia64/ia32/binfmt_elf32.c linux-2.6.8/arch/ia64/ia32/binfmt_elf32.c --- linux-2.6.8.ori/arch/ia64/ia32/binfmt_elf32.c 2004-08-16 15:36:30.000000000 +0800 +++ linux-2.6.8/arch/ia64/ia32/binfmt_elf32.c 2004-09-09 18:31:47.000000000 +0800 @@ -48,6 +48,7 @@ static void elf32_set_personality (void) extern struct page *ia32_shared_page[]; extern unsigned long *ia32_gdt; +extern struct page *ia32_gate_page; struct page * ia32_install_shared_page (struct vm_area_struct *vma, unsigned long address, int *type) @@ -59,10 +60,25 @@ ia32_install_shared_page (struct vm_area return pg; } +struct page * +ia32_install_gate_page (struct vm_area_struct *vma, unsigned long address, int *type) +{ + struct page *pg = ia32_gate_page; + get_page(pg); + if (type) + *type = VM_FAULT_MINOR; + return pg; +} + + static struct vm_operations_struct ia32_shared_page_vm_ops = { .nopage = ia32_install_shared_page }; +static struct vm_operations_struct ia32_gate_page_vm_ops = { + .nopage = ia32_install_gate_page +}; + void ia64_elf32_init (struct pt_regs *regs) { @@ -90,6 +106,29 @@ ia64_elf32_init (struct pt_regs *regs) } /* + * When user stack is not executable, push sigreturn code to stack makes + * segmentation fault raised when returning to kernel. So now sigreturn + * code is locked in specific gate page, which is pointed by pretcode + * when setup_frame_ia32 + */ + vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); + if (vma) { + memset(vma, 0, sizeof(*vma)); + vma->vm_mm = current->mm; + vma->vm_start = IA32_GATE_OFFSET; + vma->vm_end = vma->vm_start + PAGE_SIZE; + vma->vm_page_prot = PAGE_COPY_EXEC; + vma->vm_flags = VM_READ | VM_MAYREAD | VM_EXEC + | VM_MAYEXEC | VM_RESERVED; + vma->vm_ops = &ia32_gate_page_vm_ops; + down_write(¤t->mm->mmap_sem); + { + insert_vm_struct(current->mm, vma); + } + up_write(¤t->mm->mmap_sem); + } + + /* * Install LDT as anonymous memory. This gives us all-zero segment descriptors * until a task modifies them via modify_ldt(). */ diff -purN -X dontdiff linux-2.6.8.ori/arch/ia64/ia32/ia32priv.h linux-2.6.8/arch/ia64/ia32/ia32priv.h --- linux-2.6.8.ori/arch/ia64/ia32/ia32priv.h 2004-08-16 15:36:30.000000000 +0800 +++ linux-2.6.8/arch/ia64/ia32/ia32priv.h 2004-09-09 19:03:34.000000000 +0800 @@ -168,6 +168,9 @@ struct ia32_user_fxsr_struct { #define IA32_SA_HANDLER(ka) ((unsigned long) (ka)->sa.sa_handler & 0xffffffff) #define IA32_SA_RESTORER(ka) ((unsigned long) (ka)->sa.sa_handler >> 32) +#define __IA32_NR_sigreturn 119 +#define __IA32_NR_rt_sigreturn 173 + struct sigaction32 { unsigned int sa_handler; /* Really a pointer, but need to deal with 32 bits */ unsigned int sa_flags; @@ -324,14 +327,16 @@ struct old_linux32_dirent { #define IA32_PAGE_OFFSET 0xc0000000 #define IA32_STACK_TOP IA32_PAGE_OFFSET +#define IA32_GATE_OFFSET IA32_PAGE_OFFSET +#define IA32_GATE_END IA32_PAGE_OFFSET + PAGE_SIZE /* * The system segments (GDT, TSS, LDT) have to be mapped below 4GB so the IA-32 engine can * access them. */ -#define IA32_GDT_OFFSET (IA32_PAGE_OFFSET) -#define IA32_TSS_OFFSET (IA32_PAGE_OFFSET + PAGE_SIZE) -#define IA32_LDT_OFFSET (IA32_PAGE_OFFSET + 2*PAGE_SIZE) +#define IA32_GDT_OFFSET (IA32_PAGE_OFFSET + PAGE_SIZE) +#define IA32_TSS_OFFSET (IA32_PAGE_OFFSET + 2*PAGE_SIZE) +#define IA32_LDT_OFFSET (IA32_PAGE_OFFSET + 3*PAGE_SIZE) #define ELF_EXEC_PAGESIZE IA32_PAGE_SIZE diff -purN -X dontdiff linux-2.6.8.ori/arch/ia64/ia32/ia32_signal.c linux-2.6.8/arch/ia64/ia32/ia32_signal.c --- linux-2.6.8.ori/arch/ia64/ia32/ia32_signal.c 2004-06-17 15:55:32.000000000 +0800 +++ linux-2.6.8/arch/ia64/ia32/ia32_signal.c 2004-09-09 18:31:47.000000000 +0800 @@ -853,14 +853,19 @@ setup_frame_ia32 (int sig, struct k_siga unsigned int restorer = IA32_SA_RESTORER(ka); err |= __put_user(restorer, &frame->pretcode); } else { - err |= __put_user((long)frame->retcode, &frame->pretcode); - /* This is popl %eax ; movl $,%eax ; int $0x80 */ - err |= __put_user(0xb858, (short *)(frame->retcode+0)); - err |= __put_user(__IA32_NR_sigreturn & 0xffff, (short *)(frame->retcode+2)); - err |= __put_user(__IA32_NR_sigreturn >> 16, (short *)(frame->retcode+4)); - err |= __put_user(0x80cd, (short *)(frame->retcode+6)); + /* Pointing to restorer in ia32 gate page */ + err |= __put_user(IA32_GATE_OFFSET, &frame->pretcode); } + /* This is popl %eax ; movl $,%eax ; int $0x80 + * and there for historical reasons only. + * See arch/i386/kernel/signal.c + */ + + err |= __put_user(0xb858, (short *)(frame->retcode+0)); + err |= __put_user(__IA32_NR_sigreturn, (int *)(frame->retcode+2)); + err |= __put_user(0x80cd, (short *)(frame->retcode+6)); + if (err) goto give_sigsegv; @@ -924,13 +929,19 @@ setup_rt_frame_ia32 (int sig, struct k_s unsigned int restorer = IA32_SA_RESTORER(ka); err |= __put_user(restorer, &frame->pretcode); } else { - err |= __put_user((long)frame->retcode, &frame->pretcode); - /* This is movl $,%eax ; int $0x80 */ - err |= __put_user(0xb8, (char *)(frame->retcode+0)); - err |= __put_user(__IA32_NR_rt_sigreturn, (int *)(frame->retcode+1)); - err |= __put_user(0x80cd, (short *)(frame->retcode+5)); + /* Pointing to rt_restorer in ia32 gate page */ + err |= __put_user(IA32_GATE_OFFSET + 8, &frame->pretcode); } + /* This is movl $,%eax ; int $0x80 + * and there for historical reasons only. + * See arch/i386/kernel/signal.c + */ + + err |= __put_user(0xb8, (char *)(frame->retcode+0)); + err |= __put_user(__IA32_NR_rt_sigreturn, (int *)(frame->retcode+1)); + err |= __put_user(0x80cd, (short *)(frame->retcode+5)); + if (err) goto give_sigsegv; diff -purN -X dontdiff linux-2.6.8.ori/arch/ia64/ia32/ia32_support.c linux-2.6.8/arch/ia64/ia32/ia32_support.c --- linux-2.6.8.ori/arch/ia64/ia32/ia32_support.c 2004-08-16 15:36:30.000000000 +0800 +++ linux-2.6.8/arch/ia64/ia32/ia32_support.c 2004-09-09 18:31:47.000000000 +0800 @@ -33,6 +33,7 @@ struct exec_domain ia32_exec_domain; struct page *ia32_shared_page[NR_CPUS]; unsigned long *ia32_boot_gdt; unsigned long *cpu_gdt_table[NR_CPUS]; +struct page *ia32_gate_page; static unsigned long load_desc (u16 selector) @@ -155,7 +156,7 @@ ia32_gdt_init (void) /* * Setup IA32 GDT and TSS */ -void +static void ia32_boot_gdt_init (void) { unsigned long ldt_size; @@ -166,12 +167,12 @@ ia32_boot_gdt_init (void) /* CS descriptor in IA-32 (scrambled) format */ ia32_boot_gdt[__USER_CS >> 3] - = IA32_SEG_DESCRIPTOR(0, (IA32_PAGE_OFFSET-1) >> IA32_PAGE_SHIFT, + = IA32_SEG_DESCRIPTOR(0, (IA32_GATE_END-1) >> IA32_PAGE_SHIFT, 0xb, 1, 3, 1, 1, 1, 1); /* DS descriptor in IA-32 (scrambled) format */ ia32_boot_gdt[__USER_DS >> 3] - = IA32_SEG_DESCRIPTOR(0, (IA32_PAGE_OFFSET-1) >> IA32_PAGE_SHIFT, + = IA32_SEG_DESCRIPTOR(0, (IA32_GATE_END-1) >> IA32_PAGE_SHIFT, 0x3, 1, 3, 1, 1, 1, 1); ldt_size = PAGE_ALIGN(IA32_LDT_ENTRIES*IA32_LDT_ENTRY_SIZE); @@ -181,6 +182,27 @@ ia32_boot_gdt_init (void) 0x2, 0, 3, 1, 1, 1, 0); } +static void +ia32_gate_page_init(void) +{ + unsigned long *sr; + + ia32_gate_page = alloc_page(GFP_KERNEL); + sr = page_address(ia32_gate_page); + /* This is popl %eax ; movl $,%eax ; int $0x80 */ + *sr++ = 0xb858 | (__IA32_NR_sigreturn << 16) | (0x80cdUL << 48); + + /* This is movl $,%eax ; int $0x80 */ + *sr = 0xb8 | (__IA32_NR_rt_sigreturn << 8) | (0x80cdUL << 40); +} + +void +ia32_mem_init(void) +{ + ia32_boot_gdt_init(); + ia32_gate_page_init(); +} + /* * Handle bad IA32 interrupt via syscall */ diff -purN -X dontdiff linux-2.6.8.ori/arch/ia64/mm/init.c linux-2.6.8/arch/ia64/mm/init.c --- linux-2.6.8.ori/arch/ia64/mm/init.c 2004-08-16 15:36:30.000000000 +0800 +++ linux-2.6.8/arch/ia64/mm/init.c 2004-09-09 18:31:47.000000000 +0800 @@ -585,6 +585,6 @@ mem_init (void) setup_gate(); #ifdef CONFIG_IA32_SUPPORT - ia32_boot_gdt_init(); + ia32_mem_init(); #endif } diff -purN -X dontdiff linux-2.6.8.ori/include/asm-ia64/ia32.h linux-2.6.8/include/asm-ia64/ia32.h --- linux-2.6.8.ori/include/asm-ia64/ia32.h 2004-08-16 15:36:43.000000000 +0800 +++ linux-2.6.8/include/asm-ia64/ia32.h 2004-09-09 18:31:47.000000000 +0800 @@ -14,7 +14,7 @@ # ifdef CONFIG_IA32_SUPPORT extern void ia32_cpu_init (void); -extern void ia32_boot_gdt_init (void); +extern void ia32_mem_init (void); extern void ia32_gdt_init (void); extern int ia32_exception (struct pt_regs *regs, unsigned long isr); extern int ia32_intercept (struct pt_regs *regs, unsigned long isr);