From: Jinoh Kang jinoh.kang.kr@gmail.com
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.
Since this is a potentially risky change, this behaviour is hidden behind the "WINEPRELOADREMAPSTACK" environment variable. To activate the behaviour, the user needs to set "WINEPRELOADREMAPSTACK=on-conflict". After sufficient testing has been done via staging process, the new behaviour could be the default and the environment variables removed.
Note that changes to argv and envp is *not* visible in /proc/PID/{environ,cmdline} after the stack has been switched, since kernel mm pointer fields are still pointing to the old stack.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- loader/preloader.c | 146 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 21 deletions(-)
diff --git a/loader/preloader.c b/loader/preloader.c index 091fb16a2c0..6ee15f37514 100644 --- a/loader/preloader.c +++ b/loader/preloader.c @@ -258,6 +258,7 @@ enum vma_type_flags #ifdef __arm__ VMA_SIGPAGE = 0x08, #endif + VMA_STACK = 0x10, };
struct vma_area @@ -294,6 +295,7 @@ enum remap_policy #ifdef __arm__ REMAP_POLICY_DEFAULT_SIGPAGE = REMAP_POLICY_SKIP, #endif + REMAP_POLICY_DEFAULT_STACK = REMAP_POLICY_SKIP, };
/* @@ -1320,6 +1322,75 @@ static struct wld_auxv *delete_auxv_entries( struct wld_auxv *auxv, return dstp; }
+/* + * relocate_argvec + * + * Copy argument / environment vector from src to dest, fixing up addresses so + * that addresses relative to src are now relative to dest. + */ +static size_t relocate_argvec( char **dest, char **src, size_t count ) +{ + size_t i; + unsigned long delta = (unsigned long)dest - (unsigned long)src; + + for (i = 0; i < count && src[i]; i++) + dest[i] = src[i] + delta; + + dest[i] = 0; + return i; +} + +/* + * relocate_auxvec + * + * Copy auxiliary vector from src to dest, fixing up addresses so that addresses + * relative to src are now relative to dest. + */ +static void relocate_auxvec( struct wld_auxv *dest, struct wld_auxv *src, size_t count ) +{ + size_t i; + unsigned long delta = (unsigned long)dest - (unsigned long)src; + + for (i = 0; i < count; 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 + delta; + break; + } + /* fallthrough */ + default: + dest[i].a_un.a_val = src[i].a_un.a_val; + break; + } + } +} + +/* + * copy_stackargs + * + * Copy the initial stack containing program arguments to newstack, fixing up + * addresses as appropriate. + */ +static void copy_stackargs( struct stackarg_info *newinfo, struct stackarg_info *oldinfo, void *newstack, void *newstackend ) +{ + stackargs_switch_stack( newinfo, oldinfo, newstack ); + + *(int *)newstack = *(int *)oldinfo->stack; /* Copy argc */ + 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, newinfo->auxv_last - newinfo->auxv ); + wld_memmove( newinfo->auxv_last, oldinfo->auxv_last, + (unsigned long)newstackend - (unsigned long)newinfo->auxv_last ); +} + /* * set_auxiliary_values * @@ -2167,7 +2238,7 @@ static int remap_multiple_vmas( struct vma_area_list *list, unsigned long delta, * * Parse /proc/self/maps into the given VMA area list. */ -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; @@ -2191,6 +2262,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++; } @@ -2221,7 +2295,7 @@ static void free_vma_list( struct vma_area_list *list ) * * Parse /proc/self/maps into a newly allocated VMA area list. */ -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 = page_size / sizeof(struct vma_area); struct vma_area_list vma_list; @@ -2236,7 +2310,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)); @@ -2493,7 +2567,7 @@ static int remap_vdso( struct vma_area_list *vma_list, struct preloader_state *s
/* Refresh VMA list */ free_vma_list( vma_list ); - alloc_scan_vma( vma_list ); + alloc_scan_vma( vma_list, state->s.stack ); return 1;
remap_restore: @@ -2541,7 +2615,7 @@ static int remap_sigpage( struct vma_area_list *vma_list, struct preloader_state
/* Refresh VMA list */ free_vma_list( vma_list ); - alloc_scan_vma( vma_list ); + alloc_scan_vma( vma_list, state->s.stack ); return 1;
remap_restore: @@ -2552,29 +2626,58 @@ remap_restore: } #endif
+/* + * remap_stack + * + * Perform stack remapping if it conflicts with one of the reserved address ranges. + */ +static int remap_stack( struct vma_area_list *vma_list, struct preloader_state *state ) +{ + unsigned long stack_start, stack_size; + struct stackarg_info newinfo; + void *new_stack, *new_stack_base; + int result, i; + + if (find_vma_envelope_range( vma_list, VMA_STACK, + &stack_start, &stack_size ) < 0) return 0; + + result = check_remap_policy( state, "WINEPRELOADREMAPSTACK", + REMAP_POLICY_DEFAULT_STACK, + stack_start, stack_size ); + if (result < 0) goto remove_from_reserve; + if (result == 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; +} + /* * map_reserve_preload_ranges * * Attempt to reserve memory ranges into preload_info. - * If any preload_info entry overlaps with stack, remove the entry instead of - * reserving. */ -static void map_reserve_preload_ranges( const struct vma_area_list *vma_list, - const struct stackarg_info *stackinfo ) +static void map_reserve_preload_ranges( const struct vma_area_list *vma_list ) { size_t i; - unsigned long exclude_start = (unsigned long)stackinfo->stack - 1; - unsigned long exclude_end = (unsigned long)stackinfo->auxv + 1;
for (i = 0; preload_info[i].size; i++) { - if (exclude_end > (unsigned long)preload_info[i].addr && - exclude_start <= (unsigned long)preload_info[i].addr + preload_info[i].size - 1) - { - 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 @@ -2637,15 +2740,16 @@ 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 ); + alloc_scan_vma( &vma_list, state.s.stack ); + map_reserve_preload_ranges( &vma_list );
remap_done = 0; remap_done |= remap_vdso( &vma_list, &state ) > 0; #ifdef __arm__ remap_done |= remap_sigpage( &vma_list, &state ) > 0; #endif - if (remap_done) map_reserve_preload_ranges( &vma_list, &state.s ); + 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 */