Re: [PATCH 1/2] Fix libX11 early init causing mmap layout changed
Jinyang He <[email protected]> Fri, 12 Jul 2024 16:16:04 +0800
| Newsgroups | gmane.lisp.gcl.devel |
|---|---|
| Message-ID | <[email protected]> |
On 2024-07-12 00:11, Camm Maguire wrote: > Greetings, and thank you so much for your explanation! Please correct > me if any of the following are in error, as I am having trouble locating > an arch with this sequence of calls to test. (amd64 does not reexec, > i386 does not call malloc before main). Enmmm, it is need libX11 1.8+ with "--enable-thread-safety-constructor". In Linux LoongArch machine, the "readelf -d raw_pre_gcl | grep NEEDED" shows, ">>>libX11.so.6<<<, libopcodes-2.42.so, libm.so.6, libgmp.so.10," "libtirpc.so.3, libreadline.so.8, libc.so.6" are needed. While I saw another Debian12 amd64 machine, "libm.so.6, libgmp.so.10, libtirpc.so.3," "libc.so.6" are needed. Why amd64 not need libX11...? (enable_xgcl? My machine seems cannot use "--enable-xgcl=yes") (I observed that libX11 init_array calls XInitThreads on it.) > 1) The primary problem is the need for a persistent load address of > libboot.so across raw_pre and saved_pre images. (This requirement > itself is a weakness in the bootstrap process in the master branch and > should be addressed directly.) yes > 2) The problem only exists on arches which must call execve to reset the > personality to avoid sbrk address randomization, and which call malloc > before main. (Presumably lacking the former, the layout is already > selected before we even get to malloc before main, and lacking the > latter, we have not set the rlimit_stack by the time we get to execve.) yes > If this is right, my remaining mystery is why the raw_ and saved_ images > differ in the layout selection. My understanding was: > > a) Both either call malloc before main or do not. The malloc path, "malloc->malloc_internal-> gcl_alloc_initialized ? gcl_init_alloc->set rlimit_stack : not set rlimit_stack The first time raw_ set rlimit_stack and set gcl_alloc_initialized = true. Then saved_ not set rlimit_stack before reexec although called malloc because the gcl_alloc_initialized=true and we skip gcl_init_alloc. > b) Both either reexec to set the personality or do not > > therefore the layout should be the same by the time we get to > init_boot(). It seems clean that either a) or b) are in error. Do you > know which? > > It does seem that the simplest solution is to simply leave the stack > limits alone when calling gcl_init_alloc before main. This will work, > right? I think this works, but may it break something and where set stack rlimit after main (or not set stack rlimit anymore)? I'm unfamiliar with gcl, so I worry about something broken. > This is obviously exceedingly fragile and wrong headed for us to try to > keep up with linux kernel mmap layout algorithms. As I said before the > underlying issue is we have a bootstrap problem. The idea is to have > some small set of code implementing some core lisp functions in C which > are later provided in lisp. We want to jettison the memory used at the > right time, so dlopen comes to mind. I think libX11.so can also use dlopen after main. It also seems to solve this problem, but not clear for me. > But the right way to do this is to > hardlink this library in for the pre_ images only, and arrange all > others not to need it at all. This appears to call for a delicate > reordering of the image initialization process and is involved. In the > meantime, if you agree, we will just skip rlimit_stack in gcl_init_alloc > before main. That works well on LoongArch. I think it is necessary to test xgcl on amd64 with libX11. Thanks for your patience and detailed reply. Jinyang > Take care, > > > Jinyang He <[email protected]> writes: > >> On 2024-07-11 09:03, Camm Maguire wrote: >>> Greetings! In followup to my earlier query, can you please detail what >>> happens when resource limits are set before execve as you mention in >>> your comment? >> For clarity, I'll briefly describe the mmap mechanism on Linux. It provides >> two memory layouts usually, "arch_get_unmapped_{area, area_topdown}". >> When user mode does mmap, it calls, (source based 6.10.0-rc6) >> do_mmap->__get_unmapped_area->mm_get_unmapped_area_vmflags->arch_get_unmapped_{area, >> area_topdown} >> It choose topdown or other by MMF_TOPDOWN bit. >> When user mode does execve, it calls, >> ...->load_elf_binary->setup_new_exec->arch_pick_mmap_layout->... >> The arch_pick_mmap_layout clear or set MMF_TOPDOWN bit by "mmap_is_legacy". >> The CONFIG_STACK_GROWSUP is always false except parisc. Thus, if the >> "rlim_stack->rlim_cur == RLIM_INFINITY" it always returns true and clear >> the MMF_TOPDOWN bit. Once the MMF_TOPDOWN bit is set in do_execve, we >> have little chance to change it in user mode. >> >> The above details means, once the "rlim_stack->rlim_cur" set to -1ull before >> execve. The new exec mmap layout is not topdown. >> The quickly check cmd, >> "$ cat /proc/self/maps; ulimit -s unlimited; cat /proc/self/maps" >> >> In another story, I'll give the interaction between gcl and libX11. >> The call trace is follows, (libX11 with 1.8+ >> --enable-thread-safety-constructor) >> #0 gcl_init_alloc (cs_start=0x7ffffffef9a8) at alloc.c:1203 >> #1 0x0000000000153df4 in malloc_internal (size=40) at alloc.c:1665 >> #2 0x0000000000153ed8 in malloc (size=40) at alloc.c:1682 >> #3 0x00007ffff7e63474 in XInitThreads () at >> /usr/src/debug/libx11/libX11-1.8.7/src/locking.c:614 >> #4 0x00007ffff7fbdc84 in call_init (l=<optimized out>, argc=1, >> argv=0x7ffffffefa98, env=0x7ffffffefaa8) at dl-init.c:74 >> #5 call_init (l=<optimized out>, argc=<optimized out>, >> argv=0x7ffffffefa98, env=0x7ffffffefaa8) at dl-init.c:26 >> #6 0x00007ffff7fbddac in _dl_init (main_map=0x7ffff7fed360, argc=1, >> argv=0x7ffffffefa98, env=0x7ffffffefaa8) at dl-init.c:121 >> #7 0x00007ffff7fd1bb8 in _start () from >> /lib64/ld-linux-loongarch-lp64d.so.1 >> The gcl depends on libX11.so so when load libX11.so, it calls XInitThreads. >> >> In the past, the order of call gcl_init_alloc is >> main->execve->main->gcl_init_alloc(set rlimit) >> Now the order is >> XInitThreads(gcl_init_alloc(set rlimit))->main->execve->main... >> In short, the glibc::malloc is preempted by gcl::malloc and causing >> earlier set stack rlimit before doing execve. >> >> When make raw_pre_gcl to saved_pre_gcl, the raw_pre_gcl calls >> gcl_init_alloc due to "gcl_alloc_initialized" is false and then >> the mmap memory layout changed after execve. When exec saved_pre_gcl, >> the "gcl_alloc_initialized" is true so mmap memory layout is not changed. >> The "libboot.so" has been mapped to diffrent virtuall address, >> finally the function call is broken. >> >> >>> Isn't this still the case with this patch? >> This patch restore the stack limits to keep the same stack limits after >> execve itself. It is some like we should keep the same environments except >> setting "GCL_UNRANDOMIZE" and setting "ADDR_NO_RANDOMIZE". However, the >> stack limits has been changed before execve, it changes the environments. >>> Rather the >>> patch appears to intend to reassert this setting immediately before >>> execve. >>> >>> Take care, >>> >>>> Greetings, and thanks so much again for these patches! >>>> Can you please give me a bit of explanation on this one? What alters >>>> the resource limits between gcl_init_alloc 'before main' and main >>>> itself? >> The raw_pre_gcl calling gcl_init_alloc but saved_pre_gcl not calling it >> when do initcall(XInitThreads), it happens before do execve. >>>> Would this patch work just as well if the saved resource limits >>>> were restored in the second invocation of gcl_init_alloc from main >>>> instead of immediately prior to execve in unrandomize.h? >> Badly, the mmap memory layout is determined at execve. >> >> Thanks, >> Jinyang >>>> Take care, >>> Jinyang He <[email protected]> writes: >>> >>>> After libX.so release 1.8 with `--enable-thread-safety-constructor`, >>>> the libX.so calls `malloc` indirectly by calling XInitThreads() in >>>> init_array. The raw_ preempts `malloc` in EXE and calls gcl_init_alloc() >>>> before main. It set rlimit_stack before execve itself, and in linux >>>> kernel this action changes the mmap way from topdown to downtop >>>> (in many archs). Thus, saved the rlimit_stack if before_main and >>>> restore it if it need re-execve itself. >>>> --- >>>> gcl/h/unrandomize.h | 16 ++++++++++++++++ >>>> gcl/o/alloc.c | 10 ++++++++++ >>>> gcl/o/main.c | 2 ++ >>>> 3 files changed, 28 insertions(+) >>>> >>>> diff --git a/gcl/h/unrandomize.h b/gcl/h/unrandomize.h >>>> index 04b7c507e..9ca66d0a9 100644 >>>> --- a/gcl/h/unrandomize.h >>>> +++ b/gcl/h/unrandomize.h >>>> @@ -5,6 +5,9 @@ >>>> #include <string.h> >>>> #include <alloca.h> >>>> #include <errno.h> >>>> +#if defined(__linux__) && defined(RLIMIT_STACK) >>>> +#include <sys/resource.h> >>>> +#endif >>>> { >>>> errno=0; >>>> @@ -53,6 +56,19 @@ >>>> errno=0; >>>> #ifdef HAVE_GCL_CLEANUP >>>> gcl_cleanup(0); >>>> +#endif >>>> +#if defined(__linux__) && defined(RLIMIT_STACK) >>>> + { >>>> + extern struct rlimit rl_stack_saved; >>>> + /* Reset the rlim_cur incase*/ >>>> + if (rl_stack_saved.rlim_cur != 0 && rl_stack_saved.rlim_max != 0) >>>> + if(setrlimit(RLIMIT_STACK, &rl_stack_saved)) { >>>> + printf("restore rlimit_stack failure %d\n",errno); >>>> + exit(-1); >>>> + } >>>> + rl_stack_saved = (struct rlimit){0, 0}; >>>> + errno=0; >>>> + } >>>> #endif >>>> execve(*a,a,n); >>>> printf("execve failure %d\n",errno); >>>> diff --git a/gcl/o/alloc.c b/gcl/o/alloc.c >>>> index 8620df495..4d29ec860 100644 >>>> --- a/gcl/o/alloc.c >>>> +++ b/gcl/o/alloc.c >>>> @@ -1189,6 +1189,10 @@ init_tm(enum type t, char *name, int elsize, int nelts, int sgc,int distinct) { >>>> simplicity. set_maxpage is overloaded, and the positioning of its >>>> call is too fragile. 20050115 CM*/ >>>> static int gcl_alloc_initialized; >>>> +#if defined(__linux__) && defined(RLIMIT_STACK) >>>> +extern int before_main; >>>> +struct rlimit rl_stack_saved; >>>> +#endif >>>> object malloc_list=Cnil; >>>> @@ -1238,6 +1242,12 @@ gcl_init_alloc(void *cs_start) { >>>> } >>>> massert(!getrlimit(RLIMIT_STACK, &rl)); >>>> +#ifdef __linux__ >>>> + if (before_main) >>>> + rl_stack_saved = rl; >>>> + else >>>> + rl_stack_saved = (struct rlimit){0, 0}; >>>> +#endif >>>> if (rl.rlim_cur!=RLIM_INFINITY && (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > rl.rlim_cur)) { >>>> rl.rlim_cur = rl.rlim_max; /* == RLIM_INFINITY ? rl.rlim_max : rl.rlim_max/64; */ >>>> massert(!setrlimit(RLIMIT_STACK,&rl)); >>>> diff --git a/gcl/o/main.c b/gcl/o/main.c >>>> index 6621c3a16..be241d1af 100644 >>>> --- a/gcl/o/main.c >>>> +++ b/gcl/o/main.c >>>> @@ -574,8 +574,10 @@ DEFUN("KCL-SELF",object,fSkcl_self,SI,0,0,NONE,OO,OO,OO,OO,(void),"") { >>>> } >>>> +int before_main=1; >>>> int >>>> main(int argc, char **argv, char **envp) { >>>> + before_main=0; >>>> GET_FULL_PATH_SELF(kcl_self); >>>> *argv=kcl_self; >> >> >>