Top
Best
New

Posted by foltik 1 day ago

GNU IFUNC is the real culprit behind CVE-2024-3094(github.com)
126 points | 63 comments
phire 1 day ago|
This is barking up the wrong tree.

Using IFUNC to patch sshd was kind of elegant, it achieved rootkit like behaviour with a pre-existing mechanism. And sure, it might be possible for a secure daemon like sshd to drop enough privileges that it could protect itself from a malicious dynamically linked library.

But IFUNC was not required, neither was systemd. The game was lost as soon as the attacker had arbitrary code installed in a semi-common library. It doesn't have to get linked directly with sshd, it only needed to be linked into any program running as root, at least one time.

Most programs make zero effort to sandbox themselves, and as soon as one of those links with the malicious library, it could do anything. Like indirectly targeting sshd by patching its binary on disk (optionally hiding it with a rootkit), or using debug APIs to patch sshd in memory.

IFUNC, systemd, and the patched openssh are all irrelevant to the issue, that was simply the route this attacker took to leverage their foothold in libxz. There are thousands of potential routes the attacker could have taken, and we simply can't defend from all of them.

AshamedCaptain 1 day ago||
> Most programs make zero effort to sandbox themselves, and as soon as one of those links with the malicious library, it could do anything. Like indirectly targeting sshd by patching its binary on disk (optionally hiding it with a rootkit), or using debug APIs to patch sshd in memory.

I do not understand how you even expected sshd to sandbox itself. Its entire purposes is to (a) daemonize , (b) allow incoming connections in and then (c) forward (possibly-root) shell statements. All 3 things are 100% required for sshd and would have already allowed an attack like this. Any talk about sandboxing here (or dropping privileges) is wishful thinking.

zaphar 1 day ago|||
I recently tried to make something properly sandboxed and, my goodness, we have basically crafted an ecosystem where everything needs access to everything. No wonder docker, despite all it's faults, is how everyone does it. You need an entire linux distro completely accessible in your sandbox.
PaulHoule 23 hours ago||
POSIX is a millstone around the neck of the software industry.

If you wanted to do something really new in operating systems, you might think "POSIX is insecure" or "POSIX is bloated", etc. If you have a fundamentally different API though you have to write a whole new userspace. You're going to put in a POSIX personality so you can run bash and vim and nethack but once you do that you have the insecurity, bloat, etc.

jnwatson 19 hours ago||
POSIX is backwards in so many ways. It freezes in time the OS innovations of the 70s.
robertdfrench 10 minutes ago|||
I have a conspiracy theory, unsupported by facts, that Richard Stallman secretly invented POSIX as a way to get the proprietary UNIX vendors to waste time on something whose only value was to make it easier for folks to port their apps to GNUUUUUUUU/Linux.
xorcist 1 day ago|||
It was not essential to the exploit, but that does not mean it was irrelevant. More commonly used libraries are watched harder. The exploit was made much, much, worse by its indirect use by way of systemd. Approximately nobody wanted that feature and it still went in. That's something we need to be able to discuss.
AshamedCaptain 1 day ago||
But the fact that it was done indirectly from systemd has nothing to do with IFUNC.
belorn 1 day ago|||
There is always selinux if we want to add protection against arbitrary code running as root. Just because something operate as root does not mean it must have privileged access to everything.
PaulHoule 23 hours ago||
Ouch! SELinux sorta works for the software which is packed in with the operating system which you may or may not care about. If you want to get that software to do something different or have software that you really care about (like the application server that your web site runs on) controlled by SELinux it is difficult enough that the usual answer is "disable SELinux" or "don't apply it"
cenamus 1 day ago|||
Did anyone look into whether OpenBSD has privilege separated sshd enough for that exploit to not be possible?
kristjank 1 day ago||
OpenBSD exposes pledge() and unveil(), which allow programs to only access things they declare they need. So, even if the running SSH process gets exploited, it can't do anything the user it's running as can't do. sshd afaik runs as a root process which after authentication forks into another process, running as the target user.
robertdfrench 21 hours ago||
[dead]
wahern 1 day 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).

dwattttt 1 day ago||
> 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.

ajross 1 day ago|||
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.

ordu 1 day ago||
> 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.

IAmLiterallyAB 1 day ago||
Yes but at best their "solution" is equally secure, not any better.
ordu 1 day ago|||
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.

anarazel 1 day ago||
The GOT has to be initially writable regardless of ifunc, even with relro, to apply relocations.
kstrauser 1 day ago|||
Would xz still have been able to alter opensshd without IFUNC?
rwmj 1 day ago|||
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.
robertdfrench 17 hours ago||
I've updated the post and am offering $500 if you can pull this attack off without ifunc.
AshamedCaptain 2 hours ago|||
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?

robertdfrench 39 minutes ago||
I don't have any secret information! Folks were giving me a hard time about claiming that ifunc is central to this attack, and I would genuinely find it valuable to know that Jia Tan could have (for example) performed this attack against a musl-based distro.
rwmj 6 hours ago|||
$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.

robertdfrench 18 minutes ago||
You may well be right about this! What I genuinely don't understand then, is why Jia Tan relied on ifunc rather than POSIX constructors. Seems like that would have been easier and more widely applicable, right?
okanat 22 hours ago|||
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.
robertdfrench 14 minutes ago||
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.
somat 1 day ago||
Isn't it the same problem with pam.

Some solaris engineer got a little too clever and decided that the modular part of the the auth system needed to be dynamic libs. Now it's all in one process space, hard to understand, hard to debug and fragile.

I really like openbsd's bsdauth, I don't know if it is actually any better than pam but because it is moduler at the process level it is possible for mere mortals to debug and make custom auth plugins. Sadly only obsd actually uses it. https://man.openbsd.org/login.conf.5#AUTHENTICATION

countWSS 1 day ago||
IFUNC should be implemented by software itself, like switching functions on runtime/compile checks. Why bother having a slower, insecure version that is less flexible than a function pointer? I have to agree with author. Glibc is filled with even more nasty hacks ripe for new exploits.
throwawayqqq11 1 day ago||
I agree so much and wished this was the main focus of the debate. It's more a question of why does this exist in the first place and not of how did they abuse it. Building only from source is the minimum required transparency and a CI/CD pipeline able to manipulate the artifact before release takes this away. I remember the outrage, when serde (i think it was) wanted to ship parts as pre-compiled binaries for build performance reasons...
pocksuppet 1 day ago||
Less indirection means faster code. If the dynamic loader is already using a level of indirection and you patch into that same indirection instead of adding another, you're not making it slower.
warmdarksea 1 day ago||
seeing LD_PRELOAD in the "less-exploitable alternatives to GNU IFUNC" section was kind of funny
stabbles 1 day ago|
Yeah, that suggestion made me roll my eyes. It's the wrong granularity, there's no build system support, it's inconvenient (executable wrappers? require the user to understand all transitive deps?).

It also fails to mention glibc-hwcaps, which would've been a cleaner solution in the context.

robertdfrench 43 seconds ago||
Ohhh I had no idea about hwcaps! This is great! That is the way to solve this problem.
AshamedCaptain 1 day ago||
The entire argumentation here is ridiculous. There's a big jump from "IFUNC slightly undermines RELRO" to "IFUNC is the real culprit". You could have gotten all but the same effect spawning a thread from a plain init or C++ constructor. No one should think that any relro, r^x or aslr or anything like this is going to deter anyone who can literally control the contents of the libraries which are linked in. They could, literally, exec a copy of sshd with a patched config if necessary.

The title is just clickbait.

chuckadams 1 day ago|
I think I triggered the posting of this article in a reply elsewhere, to which you gave the same comment. And dammit, you're totally right, there's lots of other ways to stick arbitrary code into the initialization process at load time. Maybe the real problem is linking dependencies by shoving them into the same address space as whatever is using them and calling it a day. Memory-safe languages can help protect that model against accident, but it still takes just one evil insider to steal the keys to the kingdom.
bla3 23 hours ago||
I love the recently added "In Response to Hacker News" section.
robertdfrench 17 hours ago|
Take my money!!
esbranson 10 hours ago||
IDK I do find it funny the linked project readme is dunking on this thread.

And I am again going to mention the book Linkers and Loaders by John R. Levine from 1999, I'm not sure if there's anything comparable to it. How else does anyone know anything about this stuff?

kristjank 23 hours ago||
I will still hold the decision to link the biggest possible target on every server against the biggest, most privileged daemon on every server, as not very smart indeed.
Panzerschrek 1 day ago|
> By letting the linker run arbitrary code before main

As I know C++ allows running arbitrary code before main too - for constructors of global variables. Does it bring security risks too?

bonzini 1 day ago|
It does not, it's actually arbitrary code running during the dynamic loading process, i.e. before _start.
Panzerschrek 1 day ago||
But what if I have a C++ dynamic library? Does it call constructors for global variables before _start function in the main program starts?
bonzini 1 day ago||
_start takes care of calling the global initializers and register the atexit callback for the finalizers.

(In practice _start calls __libc_start_main, a libc function that handles all of that).

More comments...