It may sound naive, but packages which include data like said session related or any other that should not persist (until the next Global GC) - why don't you just scramble their value before ending your current action?
And dont get me wrong - yes that implies extra computation yada yada - but until a a solution is practical and builtin - i'd just recommend to scramble such variables with new data so no matter how long it will persist, a dump would just return your "random" scramble and nothing actually relevant.
Imagine, 3 level nesting calls where each calls another 3 methods, we are talking about 28 functions each with couple of variables, of course you can still clean them up, but imagine how clean code will look if you don't have to.
Just like garbage collection, you can free up memory yourself, but someone forgot something and we have either memory leak or security issues.
key := make([]byte, 32)
defer scramble(&key)
// do all the secret stuff
Unless I don't understand the problem correctly.1) You are almost certainly going to be passing that key material to some other functions, and those functions may allocate and copy your data around; while core crypto operations could probably be identified and given special protection in their own right, this still creates a hole for "helper" functions that sit in the middle
2) The compiler can always keep some data in registers, and most Go code can be interrupted at any time, with the registers of the running goroutine copied to somewhere in memory temporarily; this is beyond your control and cannot be patched up after the fact by you even once control returns to your goroutine
So, even with your approach, (2) is a pretty serious and fundamental issue, and (1) is a pretty serious but mostly ergonomic issue. The two APIs also illustrate a basic difference in posture: secret.Do wipes everything except what you intentionally preserve beyond its scope, while scramble wipes only what you think it is important to wipe.
While in my case i had a program in which i created an instance of such a secret , "used it" and than scrambled the variable it never left so it worked.
Tho i didn't think of (2) which is especially problematic.
Prolly still would scramble on places its viable to implement, trying to reduce the surface even if i cannot fully remove it.
As another response pointed out, its also possible that said secret data is still in the register, which no matter what we do to the curr value could exist.
Thanks for pointing it out!
secretStash := NewSecretStash()
pString := secretStash.NewString()
....
....
secretStash.Thrash()
yes, you now have to deal in pointers, but that's not too ugly, and everything is stored in secretStash so can iterate over all the types it supports and thrash them to make them unusable, even without the gc running.If you had to prompt a user for a password, you’d read it in, use it, then thrash the value.
read -p “Password: “ PASSWD
# do something with $PASSWD
PASSWD=“XXXXXXXXXXXXXXXXXX”
It’s not pretty, but a similar concept. (I also don't know how helpful it actually is, but that's another question...)And an in-progress proposal to make these various "bubble" functions have consistent semantics: https://github.com/golang/go/issues/76477
(As an aside, the linked blog series is great, but if you're interested in new Go features, I've found it really helpful to also subscribe to https://go.dev/issue/33502 to get the weekly proposal updates straight from the source. Reading the debates on some of these proposals provides a huge level of insight into the evolution of Go.)
I mean, if you're worried about ensuring data gets zeroed out, you probably also don't want to leak it via side channels, either.
Go has the best support for cryptography of any language
// Enabled reports whether Do appears anywhere on the call stack.
In other words, it is just a way of checking that you are indeed running inside the context of some secret.Do call; it doesn't guarantee that secret.Do is actually offering the protection you may desire.1. Well-supported standard libraries generally written by Google
2. Major projects like Vault and K8s that use those implementations and publish new stuff
3. Primary client language for many blockchains, bringing cryptography contributions from the likes of Ethereum Foundation, Tendermint, Algorand, ZK rollups, etc
Because there is tremendous support for cryptography in, say, the C/C++ ecosystem, which has traditionally been the default language of cryptographers.
Edit: also, the supported platforms are ARM and x86. If your code isn’t running on one of those platforms, you probably know what you’re doing.
Windows and MacOS?
Go is supposed to be cross-platform. I guess it's cross-platform until it isn't, and will silently change the semantics of security-critical operations (yes, every library builder will definitely remember to check if it's enabled.)
Which is exactly why it should fail explicitly on unsupported platforms unless the developer says otherwise. I'm not sure how Go developers make things obvious, but presumably you have an ugly method or configuration option like:
dangerousAllowSecretsToLeak()
...for when a developer understands the risk and doesn't want to panic.I don't understand. Why do you need it in a garbage-collected language?
My impression was that you are not able to access any register in these language. It is handled by the compiler instead.
If you are concerned about secrets being zeroed out in almost any language, you need some sort of support for it. Non-GC'd languages are prone to optimize away zeroing out of memory before deallocation, because under normal circumstances a write to a value just before deallocation that is never effectfully read can be dropped without visible consequence to the rest of the program. And as compilers get smarter it can be harder to fool them with code, like, simply reading afterwards with no further visible effect might have been enough to fool 20th century compilers but nowadays I wouldn't count on my compiler being that stupid.
There are also plenty of languages where you may want to use values that are immutable within the context of the language, so there isn't even a way to express "let's zero out this RAM".
Basically, if you don't build this in as a language feature, you have a whole lot of pressures constantly pushing you in the other direction, because why wouldn't you want to avoid the cost of zeroing memory if you can? All kinds of reasons to try to avoid that.
In practice it provides a straightforward path to complying with government crypto certification requirements like FIPS 140 that were written with languages in mind where this is an issue.
And any language which can call C code that is resident in the same virtual memory space can have its own restrictions bypassed by said C code. This even applies to more restrictive runtimes like the JVM or Python.
Since "safety" is an encompassing term, it's easy to find more rigorous definitions of the term that Go would flunk; for instance, it relies on explicit synchronization for shared memory variables. People aren't wrong for calling out that other languages have stronger correctness stories, especially regarding concurrency. But they are wrong for extending those claims to "Go isn't memory safe".
It is true that go is only memory unsafe in a specific scenario, but such things aren’t possible in true memory safe languages like c# or Java. That it only occurs in multithreaded scenarios matters little especially since concurrency is a huge selling point of the language and baked in.
Java can have data races, but those data races cannot be directly exploited into memory safety issues like you can with Go. I’m tired of Go fans treating memory safety as some continuum just because there are many specific classes of how memory safety can be violated and Go protecting against most is somehow the same as protecting against all (which is what being a memory safe language means whether you like it or not).
I’m not aware of any other major language claiming memory safety that is susceptible to segfaults.