Hacker News new | ask | show | jobs
by wahern 47 days ago
1) IFUNC is hardly the only way to run code before main.

2) The alternative they present is arguably less secure because the function pointer will remain writable for the life of the process, whereas with IFUNC the GOT will eventually be made immutable (or can be... not sure if that's the default behavior). In general function pointers aren't great for security unless you explicitly make the memory backing the pointer(s) unwritable, which at least is easier to do for a global table than it is for things like C++ vtables (because there's the extra indirection through data pointers involved to get to the table).

3 comments

Yeah, this blog is misguided. As a higher level criticism: it's confusing[1] the technical details with the payload with the exploit chain that deployed it.

The interesting thing is obviously not that you can get code to run at high privilege level by modifying a system component. I mean, duh, as it were.

The interesting thing is that the attackers (almost) got downstream Linux distros to suck down and deploy that malicious component for them. And that's not down to an oddball glibc feature, it happened because they got some human beings to trust a malicious actor. GNU glibc can't patch that!

[1] Incorrectly, as you point out.

> The alternative they present is arguably less secure because the function pointer will remain writable for the life of the process

They also suggest an alternative to storing the function pointer, store the bit flags that decide which function to call. That restricts the call targets to only the legitimate ones intended.

> The alternative they present is arguably less secure because the function pointer will remain writable for the life of the process

The article mentions this, and also points to mprotect which you can use to protect the pointer.

Why people jump to criticize without reading first? BTW, you can ask an LLM to check your critique, before posting, if you don't want to read the text.

Yes but at best their "solution" is equally secure, not any better.
They argue, and I tend to agree, that their solution is more secure.

1. It impiles some function pointers to be writable temporarily, not all of them.

2. It doesn't hide writable pointers from a cursory glance not familiar with IFUNC.

The GOT has to be initially writable regardless of ifunc, even with relro, to apply relocations.
Would xz still have been able to alter opensshd without IFUNC?
Yes, liblzma could have used multiple routes to take over sshd. Once you're running inside the process it's game over. The exact details, like how they used ifunc and an audit hook, are very interesting, but ultimately not that important.
I've updated the post and am offering $500 if you can pull this attack off without ifunc.
$500 isn't worth my time and I don't trust you'd pay up. But for anyone who wants to attempt to get him to pay up, here are three simple approaches:

(A1) In a POSIX constructor function in liblzma, set an alarm(2) for a few seconds later (once sshd has fully loaded).

(A2) In the alarm callback locate the original function that was patched using dlfcn, and mmap a page of modified code over the top that calls the exploit.

Or:

(B1) POSIX constructor function, call clone(2) to start a background thread.

(B2) In the background thread, sleep for a little, then patch the code as in A2 above.

Or:

(C1) POSIX constructor function that completely replaces the sshd process with a workalike that contains the exploit.

In A & B, for OSes (not Linux) that deny mmap, you'll need to find a struct or stack frame used by the function and work out how to adjust the data it uses or find a function pointer and exploit that.

It is weird to limit to "the same attack". Why does it even have to be the same attack? From the moment sshd loads your modified lib, you're literally running code with root privileges on the victim machine. You can literally run _any_ attack you wanted, with zero persistence. This is worse than a OpenSSH RCE.

Even in your own talk you basically admit this, so what are you doing here? If you think there's something here that everyone is missing but you don't, why not actually explain what it is?

You can always populate .init_array section with a hidden constructor. It would work just like ifunc and would always execute at shared library load time.
I think .init_array is too late in the game. ifunc lets you hijack the loader, because it is sort of like a plugin or dynamic config for the loader itself. Everything should be loaded and resolved by the point that .init_array stuff starts getting triggered, though ELF is dark and full of terrors so who knows really.