Please restore our registers when you’re done with them
randomascii.wordpress.com
randomascii.wordpress.com
Seriously, there is zero valid reason to ever inject code into another program, other than as a debugging tool on a system being debugged.
Gamescope + mangohud + steam overlay working together is very big part of SteamOS and Steam Deck's success, and it does zero runtime modification or hooking into the child process. I can imagine all the trouble you run with the hooking eventually and all the hacks it must have piled up.
It is potentially a bit fragile though - DLLs can load other DLLs, and it has happened before that in a new Windows version, Microsoft suddenly adds new implementation DLLs which get pulled in by the main system DLLs.
One approach might be to look at code-signing on all the DLLs, and ignore any unknown DLLs signed by Microsoft.
Would probably make sense to open a scary red warning at startup, telling users that their browser contains untrusted third party code, and stability/security cannot be guaranteed. Probably need to make sure there is no easy way to disable it, or else some IT will just disable it as part of installing DLL-injecting "security" software.
There are ways of doing hidden code injection, without the DLL coming up in the loaded DLLs list – https://reverseengineering.stackexchange.com/questions/2262/... – one option is for the loaded DLL to duplicate itself in memory, jump to the duplicate, then unload the original. Or, you can use VirtualAlloc2/WriteProcessMemory to load code into another process, and CreateRemoteThread to launch it.
I thought these "security" software vendors wouldn't be doing anything so fancy: but from the Chrome bug [0] it looks like some are:
> And I was able to confirm (in some of the dumps, we don't collect the right heap information in all dumps) that Trend Micro code (one region is a DLL that seems to be called ApiHookStub.x64.dll, another is not a direct DLL copy) which has been allocated on our process heap without going through the loader, presumably via something like ::VirtualProtectEx and ::WriteProcessMemory. This is a pattern I see used broadly in Edge crashes we root cause to third-party software.
However, that still can be detected – use VirtualQueryEx to iterate through process address space and find all executable memory regions – any not owned by a loaded DLL (or generated by JavaScript JIT/etc) are evidence of code injection, even if you don't know who the injector is.
[0] https://bugs.chromium.org/p/chromium/issues/detail?id=121838...
And, as you say, code injection is possible without loading a DLL, and does seem to happen.
In this case I don't understand how the state leaked from the file-system filter driver to our process, as it seems to have done. It's a mystery.
I have plans to do the address space iteration you speak of in our crash reporter.
I assume you made some Windows API call somewhere, which ended up in the filesystem filter driver, which then clobbered the register. And I'm guessing the NT kernel code and Windows DLLs never save/restore the register, because it isn't supposed to be clobbered.
Couldn't one approach be to wrap all Windows API calls with some extra code which saves and restores all the callee-save registers, so even if a buggy kernel driver clobbers them, you don't get hurt by that? I don't know, maybe that's too expensive.
Instead of restoring, one could check for the clobbering, and crash the process immediately. Or maybe Microsoft should add such a wrapping to all calls to third party kernel drivers, and blue screen?
It would be interesting to see measurements of how big the expense is.
Also, I don't think one necessarily has to do it for every Windows API call – some Windows API calls are more likely to invoke third-party code than others; some API calls are far more performance-critical than others. Maybe one could find a subset of calls to focus on which maximise the likelihood of invoking third-party code but also minimise the performance impact.
> And, this would not have caught the two errors in the assembly language code within Chrome - that would require testing at every function call, not just Windows API calls
For code you control, I think some kind of static analysis would be a better approach – parse inline assembly code and check that every register it touches is marked as clobbered to the compiler. I saw some other comments you were replying to already on that topic. I think this kind of "dynamic" approach should be reserved for third-party code with low trustworthiness.
> It would be more practical (I think) to do this checking on a special build of Chrome that is shipped to a small percentage of users, so that not everybody pays the price.
I was thinking, you could also do it using API hooking. Have some hidden setting to control it, by default off. If it is off, no impact, same as now. If the flag is on, hook (some subset of) Windows APIs with the "unexpected-register-clobber-detector". That way you don't have to produce two completely different builds.
And maybe even, automatically turn that flag on if an install starts to experience crashes–especially if the presence of certain kinds of third-party software is detected.
> But, this is an ecosystem problem and I'm not sure Chrome wants to shoulder the entire burden of finding bad software :-)
Agree. Ideally, Microsoft would take the lead there, since it is their platform. But a world in which the Chrome team does it would be better than a world in which nobody does.
Sometimes, I use software which does not work the way I would like it to work. When this software is closed source, and the problem sufficiently annoying, I inject code to make it do what I want.
(And no, once I do this I don't open bug reports, unless I can reproduce the problem without code injection.)
Because that's the great thing about owning a computer, and knowing how to really use it. It's a tool for you to command.
Now, will most people ever do this? No. I wish everyone could, but unfortunately, injecting code in a useful way requires a reasonable amount of coding knowledge.
But, this is Hacker News. I would hope most users here can come up with lots of valid reasons to inject code into other programs.
https://dblohm7.ca/blog/2016/01/11/bugs-from-hell-injected-t...
https://dblohm7.ca/blog/2016/01/11/bugs-from-hell-injected-t...
Most ABI specify some registers caller-saved, some registers callee-saved (retained unchanged from the perspective of the caller) and some registers scratch (not-saved).
In the end it depends on the architecture and on typical workload which are the fastest--and measurements can be made and it can be found out which combination is the fastest on average.
If someone is trying to use the same assembler routine on Windows as on Linux, it's likely to be wrong for one of them.
That's a waste if the callee doesn't use them.
Preemptively saving and restoring all registers in all callers would appreciably slow down every single function call on every device in the world using that ABI.
The cumulative cost would be astronomical.
It's common to say "caller cleans up registers x, y, z, callee cleans up a, b, c".
So yes, the caller could do it or not do it, the choices are all possible to do both ways, but that wouldn't be the agreed upon ABI, it'd be something different.
Historically, assemblers have been really dumb, so ABI is not a thing they'd track, especially as... I don't think they know what functions are? So while they can notice call/ret, they have no knowledge of a label being a jump or call target per-se, do they?
So you'd need an assembly-like language to encode this sort of information.
It's interesting how blurry the line is between a good, full-featured assembler and a crappy compiler!
Except you want a "macro" which:
- saves only the registers you touched
- which are callee-saved
- according to the ABI you're targeting
And you really only want that for functions, because... that's where ABIs come into play.
It's a long time since I tangled with x86 assembler; but as I recall, ENTER and LEAVE were specifically for functions, and I'm not aware of any other use for them.
The short version of it is that assemblers these days are rarely - if ever - just a zero-context stream of machine instructions. There is far more, some of it actually required.
This for obvious reasons does not work if you have separate compilation unit written in assembly, then you have to follow the ABI.
1: https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO....
The Intel platform intrinsics have names like `_mm512_4dpwssd_epi32()`. The standardized SIMD intrinsics with `simd` in the name are much newer than any of the code I'm talking about in ffmpeg/x264/dav1d. These are okay, but not being platform-specific of course means you don't get platform-specific features, which you might want when you're doing this level of optimization.
The other problem is compilers (esp. gcc) were traditionally very bad at code generation for them, although these days they're okay at it.
In many cases, you can get all the benefits of inline assembly from compiler intrinsics while letting the compiler handle all the details of register allocation and scheduling.
Note that in OP's case IIUC this was assembly code and not inline assembly. If you write functions in assembly you are solely responsible for calling conventions and ABI conformance.
Your scheme sounds like it would make your code well-behaved as the callee (with perhaps some performance penalty?). But as a caller, you couldn't trust the external code not to clobber registers.
Why the compiler didn't notice that registers were being used that weren't on the clobber list is unclear to me. Your suggestion seems totally reasonable.
https://chromium-review.googlesource.com/c/libyuv/libyuv/+/3...
PXOR XMM7 XMM7
before using XMM7 to zero anything? That way the compiler doesn't have to assume that XMM7 is zero, it can know.Yes it's an extra instruction but XORing a register with itself is such a common metaphor for zeroing that register that CPU designers try to make it fast.
Edit: Just noticed that Veliladon essentially made the same comment herein and explained the reason why it's not done this way.
The ABI is not optional, or best effort, or best practice, or any other BS that passes in the ordinary world. It is just as required as the correct operation of instructions (e.g., add should actually add things, mul should actually multiply them, and so on).
E.g. on Linux, functions should restore the values of ebx, esi, edi, ... once they're done with them. The article says (on Windows) that XMM7 needs to be restored too.
If you can't trust one register being preserved (per the ABI), then you really can't trust the values of any registers.
If you don't want to trust any callees, you could use an ABI with all registers caller saves, but I don't think any mainstream ABIs are like that.
There's a balance where having some registers be caller saved and others callee saved means a lot less saving required.
If anything has messed with any registers without permission, you crash and collect as much data as possible about any injected dll's.
Then you correlate these to find the culprits, and for each you contact the authors of the DLL and figure out a way to block the injection of any unfixed versions that cause crashes.
This means calling operator bool on your unique_ptr ought to be fine, because the unique_ptr still has a valid state (you don't know what that state is, it's unspecified, but it's guaranteed to not be radioactive on mere contact. It has to be a valid unspecified state.)
However, the bigger issue with that code is that it can easily stop working with a simple refactor. Consider:
void foo(std::unique_ptr<int> ptr) {}
void bar(std::unique_ptr<int>&& ptr) {}
int main()
{
std::unique_ptr<int> p1{new int{1}};
std::unique_ptr<int> p2{new int{2}};
foo(std::move(p1));
assert(p1 == nullptr);
bar(std::move(p2));
assert(p2 != nullptr);
}
Neither of the above asserts will fire, but from the calling site, they look exactly the same. In my opinion, the more explicit option would be to do something like `bar(std::exchange(p2, nullptr))`[0]: overload (5) https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_p...
In other words, I guess you shouldn't oughta do that generally, but I was fine with it being used there, and it did its job.
It feels like at least simple breaks of the ABI rules like this can be detected somewhat statically. The author already started with a very simple and incomplete version.
In general, I wonder, are there any (many?) static analyzers for assembled binaries.
Curiously, I found a register clobber bug in the NaCl cryptography library today. Apparently, they used a custom assembler-preprocessor (qhasm) that avoids certain classes of bugs and aids with porting, but while the tool seems to actually model the register in some way, it does not treat it as callee-saved.
What would an "actual zero" be -- a literal?
It strikes me as somehow the compiler is making assumptions that aren't being enforced by the ... OS? Language? not sure what, but it's assuming functions restore registers used but that isn't enforced by anything. From my (long ago) time there was PUSHA and POPA but I assume those take quite a bit of "oomph" and are avoided if possible.
One case of this problem was in a handwritten assembly file. The other was a compiler bug.
This is a case where the ABI requires that if you use a certain register you must save its previous value and restore it afterwords; the two independent bugs were cases of forgetting to look after a certain register.
An ABI is simply an agreement as to how things should work: what registers you are free to clobber, which you must look after when you use, how certain data must be laid out in memory, etc. ABIs are typically language specific, though there may be a lot of commonality at the very high level (i.e. how you use sections in an ELF file) and low (anybody using unboxed integers probably will do the same thing).
You are welcome to violate the ABI as you see fit in your own code. The OS doesn't care; it has its own constraints (how to make a system call, how to pass arguments to each -- though cf above when I talked about ints). So, say, a Lisp compiler can lay out stack frames differently from a C++ compiler because of the languages' different semantics) but if your Lisp program wants to call a library written in C++ it must make sure memory at the call site follows the C++ ABI because that's what the C++ compiler will have assumed.
I, for one, hate debugging asm. I do it a lot, and would prefer bugs be caught automatically, preferably soon after they are introduced.
It's not clear to me how to write such a tool as assembly code is the opposite of structured.
Adding an implicit zero may make sense for some instructions but probably not all.
MOVEQ can move more than a zero. It can move any small number (-128 to 127), so 0 is not "special" here.
Also check out the CLR instruction (though that may be what you meant by "a mnemonic for some or all of the instructions that assume zero").
The problem is really whether to indulge bad programmers who don't respect the ABI at the cost of a minimal sliver of performance (even though it's not taking up an execution port the extra instruction still takes up cache space, bandwidth, and decode). Yeah they should probably zero the register before they zero the pointer but they shouldn't have to if other people respected the ABI.
In RISC-like machines, most of the operations are register-register, and you have load/store instructions for referencing memory.
To use an immediate operand (literal constant in the code itself), you may have to load it into a register, like
move r7, #42
add r1, r1, r7 ;; ok, now we have 42 in r7, we can increment r1 by 42.
Whereas in a CISC you would have add r1, #42 ;; two operand form
or maybe add r1, r1, #42 ;; three operand form
When you need a zero, you just use the immediate operand zero, and thus you don't need to to pick some register to clear.In summary, zero registers in RISC-like instruction set architectures effectively provide a literal zero that can be used wherever a register is required, which helps because only register operands can be used in many instructions.
However, moving a zero to a register does take time. Time that would otherwise be used operating with the zero value already present in the zero register.
The second best is what moto did. As you point out, there is the instruction fetch, which could be the intended operation, rather than developing the zero itself.
On par with that is having enough registers to just hold a zero, and whether that made sense depended on the need and developer strategy.
I am a big fan of the moto CPU's, starting with the 6809. Just to be clear.
Say we are zeroing memory. No advantage there. Coupla cycles right at the start, then a ton of writes.
Say we are forming a bitmask. Could be an advantage there in that having a zero handy in a register means no fetching one. When a lot of dynamically created masks are needed, this can be a nice gain.
I'm sure we can come up with more. It's not always important, and like you mention with the moto designs, may not matter too much due to many other optimizations possible given a good instruction set.
Some people would rather have the register free for general use! I'm one of those, but if there is a zero register, I use it to get the benefit of it when I can. On the devices I've seen, there are generally a lot of registers so the marginal impact of having a zero register isn't significant. There are plenty to work with.
Maybe I should be clear here too. I personally don't care whether there is one. If it's there, I do things in ways that leverage it, and was just pointing out why devices that have one, ahem... have one! Those that don't may or may not have options that make sense. The way moto did it is very good, and there are other pretty great optimizations possible with their ISA, abusing the stack to write memory, etc...
If not, then I do other things. It's assembly language! Work the chip, right?
Macros in a macro assembler .. not very nice.
https://github.com/cisco/openh264/blob/db956674bbdfbaab5acdd...
https://github.com/cisco/openh264/blob/db956674bbdfbaab5acdd...
As a trivial example of why a register might not be clobbered even though my code touched it and it seems like I didn't restore it...
Suppose if R is divisible by 12 I branch, in the other branch I don't change R, but in that branch I do change R, XORing it with a value which is difficult to explain but has a value between 1 and 3 inclusive, sometimes more than once. At the end of the branch I also clear the bottom two bits of R.
R is actually not clobbered by this function! If the bottom two bits weren't zero before, R isn't divisible by 12, so we didn't change R, and if they were zero, we restore that, the other bits are never changed.
Having the human programmer promise they they wrote a correct clobber list means if their assembler does somehow restore/ preserve register R, the human can just say so, and needn't prove to the compiler somehow that this works. This sort of code is mostly in performance critical components, e.g. video decoding, where we are already trading reliance on fallible humans for better performance, so adding one extra promise feels OK.
No, it's literally undecidable in principle whether every bit of assembler correctly restores some register R. For any given bit of inline assembler, it's quite likely to be trivial.
In any case, we can have a useful safety feature without requiring the compiler to decide. The compiler can easily work out all the registers which get written to (right?), just not which get restored. So in addition to the clobber list, we could have a list of registers which the programmer asserts that the code restores. A register which is written to has to be on either the clobber list or the restore list (or be an output). This certainly isn't foolproof, but it would catch accidental clobbers.
Exactly. And if it's non-trivial to decide something that basic, you're doing it wrong. Saving and restoring all the registers is always an option. Only saving and restoring some of them is an optimization which must be shown to be sound.
Thanks, I was trying to think about the correct way to express this but clearly I didn't do the best job.
So, 1% of assembly code would use the manual clobber list, but the other 99% would be guaranteed (barring bugs in the compiler) to not have this bug. It seems like the right tradeoff.
Or, instead of an "auto" clobber list the compiler could have a warning if a register is used without being in the clobber list. The programmer could silence that warning in the rare cases where they need to optimize register preservation, and the bugs would be greatly reduced.
Or, put another way, writing tiny little assembly language functions is probably not worth it because the mere fact that you are using assembly language instead of (say) C/C++ means that you have missed many opportunities (code reordering, inlining, etc.) so assembly language functions _should_ be doing enough work to justify their calling cost.
But, I'm not working on an assembler or even using one so I don't think I'll even file a feature request.