7 ms·
Don't Clobber the Frame Pointer
- malkia 2y agoGoLang assembly boggles my mind - I understand why it's there, but having looked at it few times makes me wonder if it could've been prevented somehow (I guess not, cryptographic primitives would be way too slow, redirecting them through some kind of ffi would require a shared lib, yada yada yada)...
- nimish 2y agoThey could have added crypto primitives via intrinsic, or had some other way of including the edge case functionality it solves. But it's good enough and I guess it compiles quick which was a major goal for golang.
- tptacek 2y agoMajor Rust cryptography libraries (see for instance Ring) use assembly, too. It's a pretty normal thing to do.
- kibwen 2y agoIt's kinda weird that languages (or at least languages with pretensions to cryptography) are still forcing people to resort to asm directly rather than offering some sort of first-class support for constant time operations and not leaving secrets lying around in memory. It doesn't need to be super high level, it just needs to clear the infinitely low bar of assembly language. Does any language offer such a dedicated facility?
- dpifke 2y agoGo does have first class support for this in the standard library, e.g. https://pkg.go.dev/crypto/subtle https://pkg.go.dev/crypto/subtle. It's not at all weird that the language authors needed assembly to implement such a thing. They figured out the tricky bits so you don't have to.
- matheusmoreira 2y agoAs someone who's made a very simple language, I would say there are far too many moving parts involved to guarantee anything of the sort. It's probably better to just integrate libsodium. Interpreters will literally switch on the type of things in order to figure out what to do with the value. They've lost the side channel battle before it even began. Compilers? Who knows what sort of code they will generate? Who knows how many of your precautions they will delete in an effort to "optimize"? Libsodium has its own memory zeroing function because compilers were "optimizing" the usage of the standard ones. If you're writing anything cryptography related, you probably want to be talking directly to the processor which will be running your code. And only after you've studied the entire manual. Because even CPUs have significant gaps in the properties they guarantee and the conditions they guarantee them in. Cryptographers might even consider lowering the level even further. They might want to consider building their own cryptoprocessor that works exactly like they want it to work. Especially if you need to guarantee things like "it's impossible to copy keys and secrets". I own three yubikeys for the sole purpose of guaranteeing this.
- wbl 2y agoInterpreters don't need to have dynamic typing: for example the JVM and the interpreters before JIT. Even with dynamic types there are some spectacularly clever tricks people use: Smalltalk VMs are where they were invented and practiced a bunch. In crypto code branching is exactly what you don't want to do to guarantee security. Branches go both ways if an attacker can force a mispeculate and microarchitectural state is not rolled back because it can't be.
- wbl 2y agoThat's not why cryptographers use assembly. We use assembly because performance often requires instructions the complier will never use that the CPU maker makes for us. Intrinsics invite all sorts of spilling issues and aren't quite as good.
- the8472 2y agoMost crypto wants constant-time execution, optimizing compilers are not designed with that in mind. They have optimization passes that will happily turn your carefully crafted constant-time code back into branches when their heuristics deem that profitable. Currently the most reliable way to get exactly the assembly you want is to write the assembly you want.
- nimish 2y agoSure but golang has its own special assembly flavor rather than using standard gcc flavor inline assembly. Probably because it's a soup to nuts compiler but still.
- tptacek 2y agoThe point of this article is that Go-specific assembler generators (Avo in particular) are better than standard assembly for this purpose.
- nimish 2y agoThat doesn't preclude syntactic compatibility, does it?
- citizenpaul 2y ago>soup to nuts compiler Any chance you can explain that to rubes like me?
- tptacek 2y agoGo isn't built on an existing compiler framework like LLVM. It does its own code generation, has its own assembler.
- nwokon 2y agoThere is an accident of history here. Go was developed with the plan 9 C compiler suite as a starting point. Most notably those compilers did not generate assembler -- they emitted object code directly. This is described here: https://9p.io/sys/doc/compiler.html https://9p.io/sys/doc/compiler.html. The assembler facilitated transforming hand-written assembly to object code. And here the plan 9 folks chose a new syntax, probably because it was simpler to start afresh over using the existing "AT&T" or "Intel" syntax.
- nimish 2y ago
- deleted 2y ago[deleted]
- kristianp 2y agoIntrinsics would be a great quality of life improvement for low-level optimisations. They don't require understanding register allocation, but obviously they would add complexity to the compiler and they aren't cross-architecture. I have tried some tools that convert a C function with intrinsics to Go assembly, but they were buggy for my use case [1],[2]. [1] github.com/minio/c2goasm (no longer updated) [2] https://github.com/gorse-io/goat https://github.com/gorse-io/goat
- userbinator 2y agoSpeaking as an Asm programmer for several decades: Calling conventions are stupid. They are the results of mindless stupid-compiler-oriented thinking from a time when compilers produced horrible copy-paste-replace code. The CPU itself couldn't care less which registers you use for what. So many wasted bytes on moving values between registers, just because the calling convention wanted it there, and no other reason. The only need to pay attention to calling conventions is when you're interfacing with compiler-generated code. Modern CPUs are fast, but there's still tons of inefficiency in compiler output.
- lmz 2y ago> The only need to pay attention to calling conventions is when you're interfacing with compiler-generated code. So, the vast majority of code out there in the wild?
- almostgotcaught 2y agovast majority doesn't even begin to describe it - i would wager 10 years of my salary that the fraction of all currently running CPU instructions that were handwritten is so small that it's within the margin of error (i.e., random bit flips) for whatever computer you use to perform the count.
- RandomBK 2y agoDepending on how you count, the ratio might not be that small. A lot of hot code are written in hand-coded inline assembly, so in terms of CPU cycles run it's probably non-negligible. i.e. take a look at the glibc implementation of 'strcmp` [0] [0] https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/multiarch/strcmp-avx2.S#L204 https://github.com/bminor/glibc/blob/master/sysdeps/x86_64/m...
- almostgotcaught 2y ago> A lot of hot code are written in hand-coded inline assembly I know... I write GPU assembly for a living... And still I make that wager. It's not a lot. It's not even a little. It's an epsilon (overall). And it gets smaller over time.
- nj5rq 2y agoThat color scheme is nice for my eyes.