Local privilege escalation via execve()

219 points by Deeg9rie9usi a day ago on hackernews | 85 comments

rvz | a day ago

> IV. Workaround

> No workaround is available.

Oh dear.

itsthefrank | a day ago

> V. Solution

> Upgrade your vulnerable system to a supported FreeBSD stable or release / security branch (releng) dated after the correction date, and reboot the system.

Not everyone can just freebsd-update and reboot, so yes, "Oh dear." is a good response to this.

epcoa | a day ago

Anyone relying on a 30+ year old monolith kernel written in C to not have some exploitable LPEs lurking should stay in basket weaving and out of sysadmin.

itsthefrank | a day ago

Not sure why the snark but if people are running FreeBSD then they should be...basket weaving instead of using it? Yes, the correct solution is to patch and reboot but not everyone is in a place to jump and do that which is why a temp workaround, if possible, would be welcome

wswin | a day ago

I think good system should be prepared to do a reboot in a short notice. Even some long running jobs can have a pause mechanism.

cyberpunk | a day ago

Yep.

You should treat any system where non-admins regularly login as basically insecure/owned and rig your architecture appropriately.

TBH -- I don't have any of these kinds of boxes anymore. Who is really running anything like this in 2026 and for what purpose?

jmspring | a day ago

Stability of ecosystem. No systemd. Native ZFS. Jails over Docker. Been using it for 20+ years and it’s my preferred server OS.

cyberpunk | a day ago

No, I mean do you run FreeBSD boxes where users who should not ever assume root access actually login to do tasks?

My point is that if you do, you probably shouldn't run, for e.g applications which need production db credential, or hold sensitive data on these boxes, or .. whatever.

Edit: I use FreeBSD extensively, for various things -- but shell access to them is restricted to the sysadmins..

CoolCold | a day ago

Hard to tell about FreeBSD, it's basically extincted, but think of webhosting servers, wordpress, cPanel/Plesk and alike.

often it's ssh'able with things like rbash and other restrictions and almost always you, well, can run something there (as you can edit php/other files right from web management ui).

Hordes of this (in Linux world).

KAMSPioneer | 20 hours ago

I mean, where I work we offer machines to external users where they have shell access to be able to do their science, but I don't want them to have root access. Other institutes we work with (like supercomputer networks, etc) give us/users non-root access.

When things like CVE-2026-31431 or the bug that this thread is about affect our systems it causes a big headache. Yeah, we firewall off what we _can_ by having different machines doing critical things versus the ones where science users have code execution, but we don't have the resources to give every user their own machine.

jmspring | 19 hours ago

No. And hosting providers I have used usually use VM isolation (QEMU/etc) for the VPS type instances they allocate to users. The VM is vulnerable if it happens to have a kernel compiled such that allows this vuln.

jmspring | 19 hours ago

Also statements like this one - TBH -- I don't have any of these kinds of boxes anymore. Who is really running anything like this in 2026 and for what purpose?

Does not convey what your clarification attemps to state.

icedchai | a day ago

Same. I've been using it since 1996. Initially, we used it at an early ISP for DNS, SMTP, and POP3 for roughly 8K users, and it stuck with me.

tick_tock_tick | a day ago

Free root for anyone for over 20 years too.

adrian_b | 21 hours ago

Nope.

The bug appears to have been introduced in some FreeBSD 13 version.

I run FreeBSD servers that do not have this bug. In my "kern_exec.c" there is no "consume" anywhere. There is also no "memmove" at all.

That file was last patched in 2024, but whatever changes had introduced that bug, they were not back-ported to older FreeBSD versions, so those are not affected.

bch | a day ago

>> monolith kernel written in C

> Who is really running anything like this in 2026 and for what purpose?

Am I parsing your question correctly?

cyberpunk | a day ago

No, I worded it badly. See below.

mrln | a day ago

Not necessarily FreeBSD, but for Linux this applies to most universities with a CS program, I think.

The systems should be cut off from sensitive administrative data, but a malicious student would at the very least have access to the other students' data with an LPE.

yjftsjthsd-h | a day ago

...as opposed to what, exactly? Linux is a 34 y.o. monolithic kernel in C, the BSDs are all forked from the same base (386BSD) of around the same age, XNU is 29 years old (and also heavily based on BSD code while also throwing in mach code) in C and other languages,...

raddan | a day ago

The 33 year old Windows NT kernel, duh.

skydhash | a day ago

Why can't they? Upgrading and rebooting is kinda the standard response for most security issues. So I would expect something like Ansible's playbooks for this exact scenario. You might also have it setup as a staggered rollout.

paulddraper | a day ago

What prevents it?

tptacek | a day ago

Does this vulnerability not rely on SUID binaries?

cperciva | a day ago

I don't think so? It's a buffer overflow in the system call.

tptacek | a day ago

I just read that it was spilling into argv or something and assumed the vector was somehow injecting arguments or something.

cperciva | a day ago

The exploit is injecting environment variables, but yes, close enough. You need someone to call execve as root in order to become root, but you don't need a setuid binary.

rsync | 15 hours ago

I am reading:

"When the timing aligns, the trigger's buggy memmove causes K+1 to self-overwrite, replacing sshd-session's real environment with the preseed payload. sshd-session's exec_copyout_strings copies LD_PRELOAD=/tmp/evil.so to the new process's stack, the runtime linker loads evil.so, and its constructor copies /bin/sh to /tmp/rootsh and sets it suid root. My human's unprivileged user runs /tmp/rootsh -p and gets a root shell."

... so at the very end of the exploit chain, is /tmp/rootsh required to be suid root before it is finally run to get the root shell ?

... or is the exploit already achieved and /tmp/rootsh is just an arbitrary indicator ?

tptacek | 13 hours ago

One of the authors is on this subthread correcting me. :)

cperciva | 8 hours ago

The exploit already succeeded at that point, creating the setuid /tmp/rootsh is just a way of making it permanent.

cryptbe | a day ago

Nope. Try the PoC on macOS:

https://github.com/califio/publications/blob/main/MADBugs/fr...

The script downloads and sets up FreeBSD on QEMU, then runs the exploit.

The exploit is very smart: https://github.com/califio/publications/blob/main/MADBugs/fr.... It basically backdoors sshd.

wolvoleo | a day ago

Why? Just update.

ActorNightly | a day ago

I really am starting to think that the level of technical understanding on HN is so low that when readers see an exploit like this, they imagine basically the cult classic movie "Hackers" in their heads where some guy hacks into any machine of their choosing.

jeffrallen | a day ago

IV. Workaround

Accept that everything is broken and terrible and yet somehow find a way to keep a sense of humor and smile about it.

doublerabbit | a day ago

Linux is on their second and FreeBSD is on their first. How many is Windows on?

pjmlp | a day ago

Plenty, Microsoft has security teams whose job is to attack Windows.

Naturally they don't do blog posts about what they find.

hnlmorg | a day ago

You talk as if Windows is the only OS that has red teams attacking the system when clearly that isn’t even remotely true.

asveikau | a day ago

No, they're saying security work happens in the Windows world but not as much in the open, due to the closed source nature.

pjmlp | a day ago

I talk about that because it is public, and the OP mentioned Windows.

It he talked about Android, I would have mentioned Project Zero.

Don't twist the meaning of posts.

murderfs | a day ago

Local privilege escalation is largely irrelevant on Windows because basically no one uses it in a multi-user system, and application sandboxing is effectively nonexistent.

TZubiri | a day ago

I get that multiple human users on a same machine is rare nowadays, and that per-app users were never a thing.

But windows still has a root and a lower privilege user. You typically need to click on "run as admin" to elevate privileges to, for example, alter system binaries.

asveikau | a day ago

I know that Chrome on Windows tries to lower its privileges to mitigate exploits, and although it's not very popular, the MS Store app platform does try to do full isolation of apps. So actually, per-app separation of users kinda does happen, or is attempted on Windows.

murderfs | a day ago

Sure, but that's mostly academic: compromise of the user account is game over for any real user. Not actually being Administrator isn't much consolation when the regular user account can extract your cookie jar, record all of your keystrokes and mouse movements, record all desktop video (except for DRM-protected content, heh) etc.

dwattttt | a day ago

If you think Linux is on their first or second, I'm not sure how or what you're counting.

doublerabbit | a day ago

> I'm not sure how or what you're counting.

The recent two. FailCopy and DirtyFrag and FreeBSD with Execve.

2 - Linux 1 - FreeBSD.

Of course, all OS have had past-time exploits. Three now have made the news.

dwattttt | a day ago

Your question was "how many high profile privilege escalations Windows has had recently" then? I can't think of any, 0?

gdgghhhhh | a day ago

doublerabbit | a day ago

It was a sarcastic joke, never mind.

nubinetwork | a day ago

> 2 Linux

Three. I don't know if this has a name yet... https://news.ycombinator.com/item?id=48067734

cyberpunk | a day ago

This is from April 28th, it was patched in 15.0R-p7.

itsthefrank | a day ago

-p8 is the current patch level for 15.0-RELEASE so if people have been keeping on top of patching this is already two reboots in the past.

cryptbe | a day ago

Nice to randomly encounter our own work here.

Check out our blog post for a fun walkthrough: https://blog.calif.io/p/cve-2026-7270-how-i-get-root-on-free...

AI-generated working exploit, write-up and prompts: https://github.com/califio/publications/tree/main/MADBugs/fr...

j16sdiz | 21 hours ago

Your bot's blog is above average bot level.

I am sure you have spend lots of time to make your bot works that great.

i-LINK | 19 hours ago

"our" is a stretch. Not only because you're giving an algorithm personhood, but because you didn't do any of the real work. So you could instead say that it's "nice to randomly encounter what I prompted an instance of [brand of artificially intelligent dowsing rod] to do". There's your chance to claim ownership; you can plainly state that you're the person who pressed the button.

yunnpp | 19 hours ago

They are fixing bugs in open source projects. What are you doing? You're not even tapping that button.

tptacek | a day ago

Calif is just killing it these past couple months. Reminder that Calif is Thai Duong's new firm.

cryptbe | a day ago

You're always super kind to me :)

tptacek | a day ago

Everyone's got a list of people they're proud to have worked with, you're on mine.

wolvoleo | a day ago

Oof that's a pretty big one, I didn't realise but I had already updated anyway.

0xbadcafebee | a day ago

  memmove(args->begin_argv + extend, args->begin_argv + consume,
      args->endp - args->begin_argv + consume);   // ← bug
C code like this is why we can't have nice things. Arithmetic operation in the arguments of a dangerous function call with no explicit bounds check.

sethops1 | a day ago

"I just don't write bugs"

Yeah.

Groxx | a day ago

    -     args->endp - args->begin_argv + consume);
    +     args->endp - (args->begin_argv + consume));
tbh I've considered simply banning math-operator-precedence in projects I work on, and requiring all mixed-operator code to use parenthesis or split to multiple statements. I do that myself, at least.

I've seen so many mistakes from it, and seen people spend so much pointless and avoidable time deciphering and verifying it, it really doesn't seem worth it (in most code) for the extremely minor character savings.

om2 | a day ago

- and + operators have the same precedence. And a similar bug is possible if the operators were the same (both -). So I’m not sure it’s right to blame this on operator precedence or mixed operators. It’s just that, ultimately, the “consume” needs to be subtracted, not added.

Groxx | a day ago

Non-mixed always goes strictly left to right, regardless of the operator, which I haven't seen anywhere near as much struggling with.

But yes, I personally parenthesize `a-b-c` explicitly, because it's not worth it for me to read and wonder if parenthesizing order matters later. Costs less than a second to write, saves a second or ten each time I read it - that's an excellent tradeoff imo, and is a trivial pattern to follow.

(Associative operators are fine, obviously)

simonreiff | a day ago

I agree with explicit parentheses but please be careful about assuming associativity! The risk when handling floating-point arithmetic in particular is that associativity breaks, and suddenly a + (b + c) does NOT equal (a + b) + c. Not only can these lead to unexpected and hard-to-trace failure patterns, but depending on the details, they also can introduce memory overflow/underflow vulnerabilities.

chuckadams | 20 hours ago

If you're going for bit-for-bit equivalence of float values, then even with a single operation you're relying on compiler flags, architecture, the phase of the moon... I'm hard-pressed to think of any memory safety issues though.

Groxx | 18 hours ago

Yea, you're in a fairly special niche of programming if you're somewhere that truly matters, and you can't accept any valid order's output. In most general code, if that kind of precision matters, float is the wrong choice: use a bignum object and be exactly correct regardless of how you organized your code.

Which is a niche that exists, obviously. So it is absolutely true for some cases. But I would hope that any code that requires this is extremely clear about requiring it.

genxy | a day ago

Didn't you just suffer from the same trap the parent was trying to avoid?

kstrauser | a day ago

I think I’d generalize that rule to require parentheses in any situation where adding parentheses could change the interpretation. I think that’d leave int addition and multiplication, and I don’t think there’s anything else offhand. Other than those, require parentheses.

  a - b - c
is order dependent, even if its deterministic and knowable. When I’m scanning the code to look for a pesky bug, I don’t wanna have to take extra seconds to convince myself that it’s doing what I expect. It steals time and my limited attention from more interesting sections of code.

masklinn | a day ago

> I think that’d leave int addition and multiplication, and I don’t think there’s anything else offhand. Other than those, require parentheses.

At this point you just require every compound infix expression to be parenthesised, the terseness isn't worth the inconsistency. Especially as, as others have noted, these operations are only associative when working in some classes (notably not necessarily when dealing with floats).

And then you do automatic parens insertion in the LSP, so you write

    a - b - c
and when you save the lsp fixed it up to

    (a - b) - c

riffraff | a day ago

Smalltalk didn't have math operator precedence, and I thought it was very annoying but I've come to believe it was a good idea.

adrian_b | 21 hours ago

A much older language that does not have operator precedence is APL.

This is the right choice for a language with a great number of operators.

In C they have tried to minimize the number of parentheses in expressions, but for this they have created far too many levels of precedence between operators, which had the opposite effect to that intended, since people now prefer to insert superfluous parentheses, to avoid having to remember all those levels of precedence.

0xbadcafebee | a day ago

IIRC several industry and government coding standards don't permit evaluations in arguments to functions, as the compiler can end up doing wonky things, to say nothing of the likely human error. These are the kind of standards we should be adapting into a software building code to avoid security holes like this one.

masklinn | a day ago

These standards are that way because older languages (specifically C and C++) have unspecified evaluation orders for arguments, so multiple argument expressions with conflicting side-effects are non-portable.

Here the expressions are pure, OooE has nothing whatsoever to do with the issue.

kibwen | 22 hours ago

Or just use a language with a sane (read: defined) argument evaluation order, which is to say, every language that isn't C or C++.

rurban | a day ago

That's what pony did also. Operator preceding rules are too arcane, such as the need for manual memory management.

Groxx | a day ago

Nice: https://tutorial.ponylang.io/expressions/ops#precedence

Yeah that's pretty much exactly what I do by hand. I should really give Pony a try some time... there's a lot of stuff in it that I like.

bjackman | 22 hours ago

I once had a job interview where they wanted to evaluate my C knowledge. They showed me a printout of some pointer arithmetic and said spot the bug. (It may actually have been the old puzzle where it turns out that /* is always a comment opener and never a division by the referent of a pointer).

I said "well first, this is a mess, I'm putting parentheses here, here, here and here". They said "well you've fixed the bug but can you tell us where it was?"

I gave them a hypothesis but I said my "real answer" was that it's not worth our brain cycles to figure it out, you just shouldn't write code that requires knowing operator precedence. It's just such desperately boring information that I can't hold it in my head.

Interviewing such an insufferable smartarse was probably quite annoying but they did give me the job and I do stand by the underlying principle!

SoftTalker | 19 hours ago

I've never had to write or read code in an interview. I wonder how common that is?

r_lee | 18 hours ago

> I gave them a hypothesis but I said my "real answer" was that it's not worth our brain cycles to figure it out, you just shouldn't write code that requires knowing operator precedence. It's just such desperately boring information that I can't hold it in my head.

this is exactly how I think. and it goes for a lot of stuff in general, we have limited bandwidth and wasting it on useless stuff like this has no real purpose.

yet sometimes I see people show off about how they know how to deal with it but I just don't see the point.

oleganza | 13 hours ago

Your response was more correct in a professional sense than producing the piece of knowledge you've been asked for. I'd prefer to work with people who value everyone's time and write programs accordingly. If the interviewer was looking for a valuable expert, they were lucky to get you on board.

tetha | 17 hours ago

I've also grown somewhat sensitive to duplication, maybe to a painful level. But, the memmove-call from the AI writeup has duplication in there:

    memmove(args->begin_argv + extend,
            args->begin_argv + consume,
            args->endp - args->begin_argv + consume);   // ← bug
If both `args->begin_argv + consume` are supposed to be the same concept and thus the same value, I'd have a variable for it by now. Some people hate it with a passion, but something like this removes the precendence thinking, prevents modification of one and not the other and makes it easier to follow, for me at least:

    retained_tail_begin = args->begin_argv + consume
    memmove(args->begin_argv + extend,
            retained_tail_begin,
            args->endp - retained_tail_begin);
Though at that point one might also encode the entire intent (as far as I understand it) in variables as well:

    space_to_replace_end = args->begin_argv + extend
    retained_tail_begin = args->begin_argv + consume
    memmove(space_to_replace_end,
            retained_tail_begin,
            args->endp - retained_tail_begin);
Sure we can golf the names somewhat, but that code has my head spin a lot less about math and precedence.

Groxx | 16 hours ago

Yeah - sharing a variable there is a pretty strong signal that they are the same concept and can't be allowed to be different, not just "maybe the same value". That's useful to know.

If there's a ton of it in a dense bit of code, 1) it might be too complex, try making it clearer, and 2) it unfortunately makes for a lot of indirection and that can make it harder to follow, which is generally why I see people dislike it. In non-critical code I can kinda agree with inlining it. Pointer arithmetic is imo never non-trivial tho, paranoia is warranted. Especially in a kernel.

dnw | a day ago

A CVE for exeCVE()

jeffrallen | a day ago

Puttin' the CVE in execve.

kkyktkrkekk | 20 hours ago

Who would have thought.