Page MenuHomeFreeBSD

Handle non-PLT GNU IFUNC relocations in rtld

Authored by luporl on Jul 2 2020, 7:10 PM.



The code that handled non-PLT GNU IFUNC relocations was removed in r343484.
This may leave some relocations unhandled, which can cause crashes or misbehavior.

For instance, on PowerPC64, the code snippet below causes a crash:

void *(*f)(void *, const void *, size_t);

f = memcpy;
f(dst, src, len);

This happens because memcpy is an IFUNC on PowerPC64, and the assignment of its
pointer to f causes a non-PLT GNU IFUNC relocation to be generated. As the
relocation is not handled, it remains 0, resulting in an indirect call to a
NULL pointer. Another example is Postgres, that has a very similar code fragment
and is currently crashing when invoked by initdb.

The problem also occurs on amd64, but it's harder to trigger it.
But it can be reproduced by creating a test library that exports an
imemcpy function, that mimics memcpy but is an IFUNC, and building the
test program as PIE. Or by using amd64_get_fbase function instead of memcpy.
It also occurs on non-PIE binaries, when a shared object takes the address of
an IFUNC defined on another shared object
(but if the same IFUNC is also used by the main binary, then rtld end up finding
the symbol in the PLT of the main binary and the issue doesn't occur).
On PowerPC64, it happens more frequently because its binaries always use
the TOC to reference global symbols, that has a role similar to the GOT.

One possible solution for the issue described would be to revert r343484
(ifunc_init() would also need to be called earlier, before relocate_objects()).
The main problem with it is that, as described in r343484 and D17529, it
restricts what IFUNC resolvers can do, mainly by disallowing them to use
symbols that are defined (or visible) outside the resolver's translation unit.
Note, however, that this conforms to the restrictions listed at
Because of that and also to take advantage of the late IFUNC resolving that is
now implemented in rtld, this patch simply moves the resolving of non-PLT GNU IFUNC
relocations inside resolve_object_ifunc().

Test Plan

Tested changes on amd64 and PowerPC64, with a few sample programs.

TODO: buildworld on amd64 and PowerPC64, with patched rtld.

Diff Detail

rS FreeBSD src repository
Automatic diff as part of commit; lint not applicable.
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

luporl requested review of this revision.Jul 2 2020, 7:10 PM
luporl created this revision.
kib added a comment.Jul 3 2020, 7:22 AM

Provide the amd64 example, in binary form.

luporl added a comment.Jul 3 2020, 4:45 PM
In D25550#564939, @kib wrote:

Provide the amd64 example, in binary form.


Minimal will just use an IFUNC pointer and crash. PIE and non-PIE test several cases, but print the pointers that are null, instead of crashing.

kib added a comment.Jul 3 2020, 5:13 PM

Other then the note, I think it is fine. Indeed we did not handled non_plt_gnu_ifuncs.

3124 ↗(On Diff #74029)

SYMLOOK_EARLY must not be set unconditionally, it causes wrong locking when used from dlopen(). You basically should do (flags & SYMLOOK_EARLY) | SYMLOOK_IFUNC.

luporl added inline comments.Jul 3 2020, 6:14 PM
3124 ↗(On Diff #74029)

Actually, I had replaced flags by SYMLOOK_EARLY in a previous version of this patch, where I was calling reloc_non_plt() from _rtld(), where there is no flags, and it ended up here.
So I guess flags | SYMLOOK_IFUNC would be better in this case.

luporl updated this revision to Diff 74053.Jul 3 2020, 6:15 PM

Address kib's review comments

luporl marked an inline comment as done.Jul 3 2020, 6:15 PM
kib accepted this revision.Jul 3 2020, 7:37 PM

Please merge to 12 and 11.

This revision is now accepted and ready to land.Jul 3 2020, 7:37 PM
This revision was automatically updated to reflect the committed changes.