20 ms·
Mandatory enforcement of indirect branch targets
- messe 3y agoFor anybody unfamiliar with this, as I was, this appears to refer to Intel's Indirect Branch Tracking feature[1] (and the equivalent on ARM, BTI). The idea is that an indirect branch can only pass control to a location that starts with an "end branch" instruction. An indirect branch is one that jumps to a location whose value is loaded or computed from either a register or memory address: think calling a function pointer in C. Without IBT, you'd have this equivalence between C and assembly: main() { void (*f)(); f = foo; f(); } void foo() { } --- main: movl $foo, %edx call *%edx ret foo: ret If IBT is enabled, the above code triggers an exception because foo doesn't begin with an "end branch" instruction. When IBT is enabled by the compiler, the above code gets assembled as: main: endbr64 movl $foo, %edx call *%edx ret foo: endbr64 ret Now the compiler inserts endbr64 at the start of each function prologue. The reason for this feature, is to use as a defense in depth against JOP, and COP attacks, as it means that the only "widgets" available to you are entire functions, which can be far harder to exploit and chain. [1]: https://www.intel.com/content/dam/develop/external/us/en/documents/catc17-introduction-intel-cet-844137.pdf https://www.intel.com/content/dam/develop/external/us/en/doc...
- rwmj 3y agoThe fun fact being that older CPUs decode ENDBR64 as a slightly weird NOP (with no architectural effects), but it'll fault on original Pentiums: https://stackoverflow.com/questions/56120231/how-do-old-cpus-execute-the-new-endbr64-and-endbr32-instructions https://stackoverflow.com/questions/56120231/how-do-old-cpus...
- SomeRndName11 3y agoNOP on intels is in fact xchg eax, eax
- mattgreenrocks 3y agoThat’s really clever use of the opcode space. Thanks for passing that along.
- rollcat 3y agoVarious architectures do other interesting things with NOPs, IIRC one convention on PowerPC had something vaguely related to debugging or tracing (I can't remember the details or find any references right now).
- messe 3y agoNot just architectures, but different OSes and ABIs have found ways to repurpose no-ops. One example[1] is Windows using the 2-byte "MOV EDI, EDI" as a hot-patch point: it gets replaced by a "JMP $-5" instruction which jumps 5 bytes before the start of a function into a spot reserved for patching. That 5 bytes is enough to contain a full jump instruction that can then jump wherever you need it to. ## Why do Windows functions all begin with a pointless MOV EDI, EDI instruction? [1]: https://devblogs.microsoft.com/oldnewthing/20110921-00/?p=9583 https://devblogs.microsoft.com/oldnewthing/20110921-00/?p=95...
- gcoakes 3y agoGood read. Thank you. This just worsens my fear of changing "unnecessary" code when I don't know the original motivation for it.
- jchw 3y agoI wonder if this is still true. Whenever I go to hook Win32 API functions, I use an off-the-shelf length disassembler to create a trampoline with the first n bytes of instructions and a jmp back, and then just patch in a jmp to my hook, but if this hot-patch point exists it'd be a lot less painful since you can avoid basically all of that. Though, I guess even if it was, it'd be silly to rely on it even on x86 only. Maybe it would still make for a nice fast-path? Dunno.
- jeffbee 3y agoInteresting, thanks for pointing this out! Just yesterday I was gazing at some program containing two consecutive xor rax, rax. I thought what’s the point? But as you point out it might be a NOP sled designed to be that specific length.
- dataflow 3y agoThere's a good question in the comments there that I still don't see the answer to. How does this work if there's an interrupt between the branch and the endbranch? Does the OS need to save/restore the "branchness" bit?
- drdrey 3y agothere is no branchness bit, if there's an endbranch you can jump to it
- dataflow 3y agoAh so when you return from an interrupt, the check is no longer done?
- simcop2387 3y agoI'd assume so since it wouldn't be a call/jmp coming from a computed address in a register. That said I haven't read the documentation for any of this. But interrupts should be having a stack pointer change and other things happening that would be different, which is why they use the IRET instruction and not the RET one.
- muricula 3y agoYes, on arm the branch type is saved in SPSR_EL1 in the BTYPE field. That stands for Saved Program State Register for Kernel Mode (Exception Level 1) and Branch Type. https://developer.arm.com/documentation/ddi0595/2021-12/AArch64-Registers/SPSR-EL1--Saved-Program-Status-Register--EL1- https://developer.arm.com/documentation/ddi0595/2021-12/AArc...
- __failbit 3y agoThank you for the explanation!
- haberman 3y agoInteresting. I was able to get Clang to generate this using `-fcf-protection=branch`: https://godbolt.org/z/rooP8vPsM https://godbolt.org/z/rooP8vPsM It looks like endbr64 is a 4-byte instruction. That could be a significant code size overhead for jump tables with lots of targets: https://godbolt.org/z/xTPToaddh https://godbolt.org/z/xTPToaddh
- notaplumber1 3y agoOpenBSD disables jump tables in Clang on amd64 due to IBT, some architectures also had jump tables disabled as part of the switch to --execute-only ("xonly") binaries by default, e.g: powerpc64/sparc64/hppa. https://marc.info/?l=openbsd-cvs&m=168254711511764&w=2 https://marc.info/?l=openbsd-cvs&m=168254711511764&w=2 E.g: https://marc.info/?l=openbsd-cvs&m=167337396024167&w=2 https://marc.info/?l=openbsd-cvs&m=167337396024167&w=2
- saagarjha 3y agoAny idea what the performance impact is?
- codedokode 3y agoWhy should every function start with endbr64 command? Aren't functions usually called directly? Also, is it required to insert endbr64 command after function calls (for return address)?
- messe 3y agoC allows for any function to be called via a function pointer, and functions can be in different translation units, so the compiler can't simply assume that a function will never be called indirectly and has to pessimistically insert endbr64 in order to maintain a reasonable ABI. And no, as I understand it, this is only for branch/calls not returns.
- Joker_vD 3y agoWell, if the function is marked "static", the compiler can actually check whether the function's address is taken in the current compilation unit or not and omit/emit ENDBR64 accordingly (passing pointers to static functions to code in another compilation units is legal, and should still work).
- messe 3y agoGood catch. Yeah, as long as the functions address is never taken the compiler has a lot of leeway with static functions; it can even avoid emitting code for them entirely if it can prove they're never called or if it's able to compute their results at compile-time.
- josephg 3y agoYep. Or inline them at every call site if that makes sense to do based on the optimization level and flags.
- MobiusHorizons 3y agoIs this theoretically something lto could remove?
- wongarsu 3y agoInteresting. Seems like enforcement on Intel CPUs is supported since Tiger Lake (so ~2020). Windows has basically the same feature implemented in software since 2015, called Control Flow Guard [1]. I wonder what the story there is, and if Windows has any plans to (get everyone to) switch to the hardware version once those CPUs have sufficient market share. 1: https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard https://learn.microsoft.com/en-us/windows/win32/secbp/contro...
- andersa 3y agoWindows also recently implemented a far better version of this called Extended Flow Guard (XFG) that not only checks whether the location is a valid destination, but also whether it's a valid destination for that specific source. For example, for any virtual function call or function pointer call, the destination must have a correct tag with the hash of the arguments. It's much more secure, and also faster, since loading the tag from memory can be merged with loading the actual code after it. I wish this was the one implemented in hardware..
- simcop2387 3y agoThat does sound like it would be more robust, but definitely sounds like it'd require a lot more silicon than the IBT that they did implement. Something like it might be something that comes in some future revisions.
- saagarjha 3y agoARM does it!
- ComputerGuru 3y agoThere’s a great article on XFG here [0] but it observed that a failed XFG check downgrades to a regular CFG check instead of a denial.. meaning it adds zero extra protection? Perhaps this behavior has changed since the preview they tested, though! [0]: https://www.offsec.com/offsec/extended-flow-guard/ https://www.offsec.com/offsec/extended-flow-guard/
- asveikau 3y agoIt was an old joke that the opposite of "goto" is "come from", or that if goto is considered harmful, nobody said anything about a "come from". Marking something as a branch target reminds me of this. https://en.m.wikipedia.org/wiki/COMEFROM https://en.m.wikipedia.org/wiki/COMEFROM
- dejj 3y ago> GOTO considered harmful COMEFROM considered harm-mitigating It ingeniously makes Return Oriented Programming (ROP) a lot harder.
- messe 3y ago> COMEFROM considered harm-mitigating You know, that’d be a fantastic OpenBSD release name. Here’s hoping a dev sees this comment; there’s already been a few commenting in this thread.
- cratermoon 3y agoIn case anyone wants a very simple introduction to JOP/COP exploits and mitigations of this type: <https://www.theregister.com/2020/06/15/intel_cet_tiger_lake/ https://www.theregister.com/2020/06/15/intel_cet_tiger_lake/>
- deleted 3y ago[deleted]
- SoftTalker 3y agoTheo had to get his digs in against Linux in that announcement. Why not just focus on what OpenBSD is doing, and maybe contrast it to what Linux does without the speculation that they will still be doing the same thing in 20 years. He's unquestionably brilliant, but I've had a few encounters with him on the mailing lists and he is so quick to take offense where none was meant and drop into name-calling and insults. I don't really get it. He may have some deep insecurities.
- insanitybit 3y agoI think it's great that he's calling Linux's choices out. TBH Theo's attitude is borne of Linux's culture - most older school sec people have learned that this is the only way to get things to improve.
- redundantly 3y agoI wouldn't have it any other way. I love the OpenBSD mailing lists. Always an entertaining read when Theo gets involved.
- teknopurge 3y agoupvoted and +1. Theo has been an important leader in OSS for decades: his brevity and impatience is a net positive. also he is usually correct.
- Joker_vD 3y agoThat's the problem with many brilliant people: what they perceive as their interlocutors being deliberately obtuse on some completely obvious point is actually their interlocutors being just as smart as they always are on some point that is not obvious at all to them.
- rkangel 3y agoPerception of relative intelligence or sensible decision making is irrelevant. Just because you think you're doing a better job doesn't mean you need to shit on the other person. You could not mention Linux at all, or you could even say "we think this is better than Linux's approach because of X" and it would be a great improvement. I have always found it interesting that Rust purposefully avoided doing language comparisons - "we're better than Python like this and better than C like that". Their message purposefully avoided any positioning of it as a competition, instead focusing just on articulating Rust's value. It was an eye opening approach given our instinct is normally to pit things against each other.
- binkHN 3y agoI still run OpenBSD where I can, especially where security is more important. Yes, it's still missing A LOT of functionally compared to other UNIX-like systems, but security bases tend to be well covered.
- PrimeMcFly 3y agoI don't really buy their approach to security honestly. Trying to fix all bugs is great, but they provide little to prevent unknown bugs bing exploited (pledge is nice for software that opts in to use it, but otherwise not so much). I'd love to see them implement something like AppArmor with their approach, it would probably be amazing. I actually think NetBSD is a pretty interesting alternative, it has some nice security features like veriexec that don't get talked about much.
- deleted 3y ago[deleted]
- binkHN 3y agoI think in the past they tried to fix all the bugs, and realized they couldn't, so they started to build all sorts of mitigations in the same vein as the one you see posted here today. As for pledge, and the related mitigations, yes, they're not useful if you don't use them, but I see this as them innovating in the space and giving application developers more tools to build hardened applications. I see tools like AppArmor as band-aids to fix problems that shouldn't exist in the first place. The problem with these approaches are the band-aids tend to break things in unexpected ways and when that happens they simply get removed and unused.
- PrimeMcFly 3y ago> I see tools like AppArmor as band-aids to fix problems that shouldn't exist in the first place. I fundamentally disagree on that. I think tools like that are amazing at protecting against unknown threats/exploits. They let you lock down software and protect against future unknown exploits, badly behaving software, malicious employees etc. I think something similar should be a part of any OS claiming to be security focused. Basic DAC is woefully insufficient. On the other hand, the industry has largely found other solutions like sandboxing, but I still think MAC or RBAC or whichever has a place, certainly as art of a defense in depth strategy.
- ntfAX 3y agoA software solution provided by the OS or language can make this hardware solution irrelevant.
- josephcsible 3y agoYou've got it backwards: this hardware solution makes the software solutions irrelevant.
- tremon 3y agoIt's only irrelevant if the hardware solution is available on all the supported architectures/systems. As long as it's not, the software version must be maintained anyway, and might suffer from bitrot if it's no longer exercised on the major architectures.
- tialaramex 3y agoNope. Here's the actual problem, in these crappy languages it's really easy for mistakes to result in a stack smash, so, these types of hacks aim to make it harder for the bad guys to turn that into arbitrary remote code execution. Not impossible, just harder. Specifically in this case the idea is that they won't be able to abuse arbitrary bits of function without calling the whole function, at a cost of some hardware changes and emitting unnecessary code. So maybe they can't find a whole function which works for them and they give up. Using better languages makes the entire problem disappear. You don't get a stack smash, the resulting opportunities for remote code execution disappear. It suggests that maybe the "C magically shouldn't have Undefined Behaviour" people were onto something after all. Maybe C programmers really are so wedded to this awful language that just being much slower than Python wouldn't deter them. There is still the problem that none of them can agree how this should work, but if they'll fund it maybe it's worth pursuing to find out how much they will put up with to keep writing C.
- dundarious 3y agoI think one could argue that all the software mitigations that aren't based on compile time proofs result in quite a bit more "emitting unnecessary code", if "unnecessary" is taken to mean "not strictly intrinsic to the task of the program". And undefined behavior is bad, but getting rid of it wouldn't be a silver bullet for this problem in C, I think. All undefined behavior could become "implementation defined" tomorrow, where the C compiler becomes more like a high-level assembler (again), and you could still jump the instruction pointer into arbitrary program text.
- WalterBright 3y agoI'm working on adding ENDBR support to the DMD D compiler backend.
- e12e 3y agoThat's good to hear. Is this ENDBR stuff new? Always been there in amd64? i686 etc?
- WalterBright 3y agoI don't know when it arrived.
- nullc 3y agoIs this protection really all that helpful? Surely there are functions you can call into the top of to do your diabolical deeds for you. It would be more helpful if callers would store some machine specific hash of the function prototype and the function itself would check the hash, so that you could only redirect to calling a function with the right signature. But that would also increase the overhead further. Already this is bad enough that it makes jump tables unattractive (which is too bad, considering the usually jump tables have little to no risk of control flow redirection).
- tedunangst 3y agoThe entire field of ROP exploits would basically never have been developed if it were as simple as just calling the function you want.