Today, the preloader abandons reserved address ranges that conflict with the call stack area.
Fix this by attempting to copy the stack somewhere else, and switching to it before entering the ld.so entry point. This way, the preloader does not have to give up the address reservation.
This behavior is enabled only when the "WINEPRELOADREMAPSTACK" environment variable is set to "on-conflict". In the future, it could become the default behaviour.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- loader/preloader.c | 123 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 16 deletions(-)
diff --git a/loader/preloader.c b/loader/preloader.c index b13ba1dac57..6c4c4c806bb 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -197,6 +197,7 @@ enum vma_type_flags VMA_NORMAL = 0x01, VMA_VDSO = 0x02, VMA_VVAR = 0x04, + VMA_STACK = 0x08, };
struct vma_area @@ -224,7 +225,8 @@ enum remap_policy REMAP_POLICY_SKIP = 2, LAST_REMAP_POLICY,
- REMAP_POLICY_DEFAULT_VDSO = REMAP_POLICY_SKIP, + REMAP_POLICY_DEFAULT_VDSO = REMAP_POLICY_SKIP, + REMAP_POLICY_DEFAULT_STACK = REMAP_POLICY_SKIP, };
/* @@ -1024,6 +1026,59 @@ static void shift_stackargs( struct stackarg_info *newinfo, struct stackarg_info newinfo->auxv_end = (void *)((unsigned long)oldinfo->auxv_end + offset); }
+static size_t relocate_argvec( char **dest, char **src, size_t count ) +{ + size_t i; + unsigned long offset = (unsigned long)dest - (unsigned long)src; + + for (i = 0; i < count && src[i]; i++) + dest[i] = src[i] + offset; + + dest[i] = 0; + return i; +} + +static size_t relocate_auxvec( struct wld_auxv *dest, struct wld_auxv *src ) +{ + size_t i; + unsigned long offset = (unsigned long)dest - (unsigned long)src; + + for (i = 0; src[i].a_type != AT_NULL; i++) + { + dest[i].a_type = src[i].a_type; + switch (dest[i].a_type) + { + case AT_RANDOM: + case AT_PLATFORM: + case AT_BASE_PLATFORM: + case AT_EXECFN: + if (src[i].a_un.a_val >= (unsigned long)src) + { + dest[i].a_un.a_val = src[i].a_un.a_val + offset; + break; + } + /* fallthrough */ + default: + dest[i].a_un.a_val = src[i].a_un.a_val; + break; + } + } + + return i; +} + +static void copy_stackargs( struct stackarg_info *newinfo, struct stackarg_info *oldinfo, void *newstack, void *newstackend ) +{ + shift_stackargs( newinfo, oldinfo, newstack ); + + *(int *)newstack = *(int *)oldinfo->stack; + relocate_argvec( newinfo->argv, oldinfo->argv, newinfo->envp - newinfo->argv ); + relocate_argvec( newinfo->envp, oldinfo->envp, (char **)newinfo->auxv - newinfo->envp ); + relocate_auxvec( newinfo->auxv, oldinfo->auxv ); + wld_memmove( newinfo->auxv_end, oldinfo->auxv_end, + (unsigned long)newstackend - (unsigned long)newinfo->auxv_end ); +} + /* * set_auxiliary_values * @@ -1811,7 +1866,7 @@ static int remap_multiple_vmas( struct vma_area_list *list, unsigned long offset return 0; }
-static void scan_vma( struct vma_area_list *list, size_t *act_count ) +static void scan_vma( struct vma_area_list *list, size_t *act_count, void *stack_ptr ) { int fd; size_t n = 0; @@ -1835,6 +1890,9 @@ static void scan_vma( struct vma_area_list *list, size_t *act_count ) { if (parse_maps_line( &item, line ) >= 0) { + if (item.start <= (unsigned long)stack_ptr && + item.end > (unsigned long)stack_ptr) + item.type_flags |= VMA_STACK; if (list->list_end < list->alloc_end) insert_vma_entry( list, &item ); n++; } @@ -1855,7 +1913,7 @@ static void free_vma_list( struct vma_area_list *list ) list->alloc_end = 0; }
-static void alloc_scan_vma( struct vma_area_list *listp ) +static void alloc_scan_vma( struct vma_area_list *listp, void *stack_ptr ) { size_t max_count = 4096 / sizeof(struct vma_area); struct vma_area_list vma_list; @@ -1870,7 +1928,7 @@ static void alloc_scan_vma( struct vma_area_list *listp ) vma_list.list_end = vma_list.base; vma_list.alloc_end = vma_list.base + max_count;
- scan_vma( &vma_list, &max_count ); + scan_vma( &vma_list, &max_count, stack_ptr ); if (vma_list.list_end - vma_list.base == max_count) { wld_memmove(listp, &vma_list, sizeof(*listp)); @@ -1926,7 +1984,7 @@ static int remap_vdso( struct vma_area_list *vma_list, struct preloader_state *s remap_multiple_vmas( vma_list, offset, VMA_VDSO, 0 ) < 0) goto remap_restore;
free_vma_list( vma_list ); - alloc_scan_vma( vma_list ); + alloc_scan_vma( vma_list, state->s.stack );
if (find_vma_envelope_range( vma_list, VMA_VDSO | VMA_VVAR, &new_vdso_start, &new_vdso_size ) < 0 || vdso_start + offset != new_vdso_start || @@ -1952,18 +2010,47 @@ remap_restore: return -1; }
-static void map_reserve_preload_ranges( struct vma_area_list *vma_list, void *exclude_start, void *exclude_end ) +static int remap_stack( struct vma_area_list *vma_list, struct preloader_state *state ) +{ + enum remap_policy policy; + unsigned long stack_start, stack_size; + struct stackarg_info newinfo; + void *new_stack, *new_stack_base; + int i; + + if (find_vma_envelope_range( vma_list, VMA_STACK, + &stack_start, &stack_size ) < 0) return 0; + + policy = stackargs_get_remap_policy( &state->s, "WINEPRELOADREMAPSTACK", REMAP_POLICY_DEFAULT_STACK ); + if (policy == REMAP_POLICY_SKIP) goto remove_from_reserve; + if (policy != REMAP_POLICY_FORCE && + find_preload_reserved_area( (void *)stack_start, stack_size ) < 0) return 0; + + new_stack_base = wld_mmap( NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0 ); + if (new_stack_base == (void *)-1) goto remove_from_reserve; + + new_stack = (void *)((unsigned long)new_stack_base + ((unsigned long)state->s.stack - stack_start)); + copy_stackargs( &newinfo, &state->s, new_stack, (void *)((unsigned long)new_stack_base + stack_size) ); + + wld_memmove( &state->s, &newinfo, sizeof(state->s) ); + + free_vma_list( vma_list ); + alloc_scan_vma( vma_list, state->s.stack ); + return 1; + +remove_from_reserve: + while ((i = find_preload_reserved_area( (void *)stack_start, stack_size )) >= 0) + remove_preload_range( i ); + return -1; +} + +static void map_reserve_preload_ranges( struct vma_area_list *vma_list ) { size_t i; for (i = 0; preload_info[i].size; i++) { - if ((char *)exclude_end >= (char *)preload_info[i].addr && - (char *)exclude_start <= (char *)preload_info[i].addr + preload_info[i].size) - { - remove_preload_range( i ); - i--; - } - else if (map_reserve_unmapped_range( vma_list, preload_info[i].addr, preload_info[i].size ) < 0) + if (map_reserve_unmapped_range( vma_list, preload_info[i].addr, preload_info[i].size ) < 0) { /* don't warn for low 64k */ if (preload_info[i].addr >= (void *)0x10000 @@ -1996,6 +2083,7 @@ void* wld_start( void **stack ) struct wine_preload_info **wine_main_preload_info; struct preloader_state state = { 0 }; struct vma_area_list vma_list = { NULL }; + int remap_done;
parse_stackargs( &state.s, *stack );
@@ -2024,10 +2112,13 @@ void* wld_start( void **stack ) reserve = stackargs_getenv( &state.s, "WINEPRELOADRESERVE" ); if (reserve) preload_reserve( reserve );
- alloc_scan_vma( &vma_list ); - map_reserve_preload_ranges( &vma_list, state.s.stack, state.s.auxv ); + alloc_scan_vma( &vma_list, state.s.stack ); + map_reserve_preload_ranges( &vma_list );
- if (remap_vdso( &vma_list, &state ) > 0) map_reserve_preload_ranges( &vma_list, state.s.stack, state.s.auxv ); + remap_done = 0; + remap_done |= remap_vdso( &vma_list, &state ) > 0; + remap_done |= remap_stack( &vma_list, &state) > 0; + if (remap_done) map_reserve_preload_ranges( &vma_list );
/* add an executable page at the top of the address space to defeat * broken no-exec protections that play with the code selector limit */