# HG changeset patch # User Taylor R Campbell # Date 1744941415 0 # Fri Apr 18 01:56:55 2025 +0000 # Branch trunk # Node ID f0c0c2d90c130d5e6e06ac9f5a230a3902d23412 # Parent 438b14ea036dd2bd2e1109f5e08f7d74109e6fae # EXP-Topic riastradh-pr57792-relrorounding ld.elf_so(1): Defer RELRO a bit for eagerly-bound (-z now) programs. GNU binutils links programs a little differently if they're eagerly bound with -Wl,-z,now. In particular, the part of the .got relocated for ifuncs is covered by the RELRO region in eagerly-bound programs (-z now), but not in lazily-bound programs (default). Presumably this is done under the premise that eagerly-bound programs will resolve all ifuncs at startup _before_ mprotecting the main object's RELRO region. But ld.elf_so was mprotecting it first, in _rtld_do_copy_relocations, and then resolving ifuncs -- at which the memory locations where it tries to write the resolutions will be mapped read-only. (In contrast, for lazily bound programs, ifuncs may also be resolved lazily, so the locations they relocate are _not_ covered by the RELRO region and we don't run into any trouble) To avoid this trouble, for eagerly-bound programs, we defer the RELRO mprotect of the main object by a little bit -- we move it from _rtld_do_copy_relocations to just after _rtld_call_init_functions. PR lib/57792: SIGSEGV in ld.elf_so / _rtld_call_ifunc diff -r 438b14ea036d -r f0c0c2d90c13 libexec/ld.elf_so/reloc.c --- a/libexec/ld.elf_so/reloc.c Thu Apr 17 13:43:59 2025 +0000 +++ b/libexec/ld.elf_so/reloc.c Fri Apr 18 01:56:55 2025 +0000 @@ -154,7 +154,22 @@ int } } #ifdef GNU_RELRO - if (_rtld_relro(dstobj, true) == -1) + /* + * If the main program is lazily bound (default -- whether or + * not LD_BINDNOW is set in the calling environment), we are + * now done writing to anything covered by RELRO and we can + * safely make it read-only. There may still be ifunc + * resolution to do later; it will happen in a read/write + * segment and will not be made read-only. + * + * But if the main program is eagerly bound (i.e., the object + * has DF_1_NOW set in DT_FLAGS_1, whether or not LD_BIND_NOW + * is set in the calling environment), we delay protecting the + * RELRO region as read-only until we have resolved ifuncs -- + * at which point we will make the ifunc resolution read-only + * too. + */ + if (!dstobj->z_now && _rtld_relro(dstobj, true) == -1) return -1; #endif #endif /* RTLD_INHIBIT_COPY_RELOCS */ diff -r 438b14ea036d -r f0c0c2d90c13 libexec/ld.elf_so/rtld.c --- a/libexec/ld.elf_so/rtld.c Thu Apr 17 13:43:59 2025 +0000 +++ b/libexec/ld.elf_so/rtld.c Fri Apr 18 01:56:55 2025 +0000 @@ -806,6 +806,27 @@ Elf_Addr _rtld_exclusive_exit(&mask); +#ifdef GNU_RELRO + /* + * If the main program is lazily bound (default -- whether or + * not LD_BINDNOW is set in the calling environment), its RELRO + * region has already been mapped read-only in + * _rtld_do_copy_relocations. The ifunc resolutions lie + * outside this region, so future lazy ifunc resolution is + * unaffected by the RELRO region's being read-only. + * + * If the main program is eagerly bound (i.e., the object has + * DF_1_NOW set in DT_FLAGS_1, whether or not LD_BIND_NOW is + * set in the calling environment), we deferred that from + * _rtld_do_copy_relocations so that the ifunc resolution, we + * have now resolved all ifuncs in it, so we can commit the + * RELRO region to be read-only -- and that means ifunc + * resolutions are read-only too. + */ + if (_rtld_objmain->z_now && _rtld_relro(_rtld_objmain, true) == -1) + return -1; +#endif + /* * Return with the entry point and the exit procedure in at the top * of stack.