New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: overload allocator within specified namespaces #34586
Comments
Is this allocator only used for Delegating general allocations are hard, as the runtime needs to keep track of which parts of the object are pointers. Can the allocator return Go heap memory, or must it use outside-of-heap memory? The runtime will only call In general I'm against this proposal. I think it is a much better design to require crypto code that wants this guarantee to code it explicitly. For example, your
That sounds like the thing we should fix. If you want to limit exposure of sensitive state, you must write code to do that. If the stdlib doesn't do that, you have to write your own (or update the stdlib to do what you want). |
data := []byte("yellow submarine")
str := *(*string)(unsafe.Pointer(&data))
// both reference the same region of memory
It would return non-go heap memory, probably attained from
In memguard, a reference to every currently existing allocation is kept, and there are convenience functions to destroy these securely. In a situation where the library is providing an allocator to the runtime, it can keep track of the allocations itself and wipe out only those ones once the library has finished its work and returned a value.
Good point. Even so, rewriting functions to take an output buffer as an argument or return the data as an ordinary slice is a lot easier than trying to re-write the code to explicitly use a different allocator. |
Then why would the runtime also do that? Just as a backstop?
I'm not sure i agree. Changing code to use a different allocator just involves passing a |
Yes, mostly. Attaching a finaliser is free so might as well do it.
I've tried, it's really not very easy. When you have something like
a := getAlloc()
unsafe.OverloadAlloc("crypto", a)
crypto.Work(secret, state, &wow)
a.Destroy() // destroys everything that the imported package used |
Attaching a finalizer to non-Go-heap memory is not free. That would be tricky to implement in the runtime. A finalizer on the Allocator is fine (presumably the Allocator is in the Go heap). So I'm not sure whether Free is necessary in this proposal. |
We already attach a finaliser on allocations in memguard: https://github.com/awnumar/memguard/blob/11ddc2241fddbb1ddc7d8ffcbd9051d6c118428f/buffer.go#L28-L35 Is this different since it's attached to a struct object instead of a slice? I guess the allocator itself could attach the finaliser if the runtime cannot.
I thought about this but |
The type of object doesn't matter. What matters is whether the object is in the Go heap or not. |
I guess the slice itself is still a go object, just the ptr value points at some memory not in the go heap. The runtime doesn't need to concern itself with that location. |
The allocator as spec'd returns a []byte. You cannot set a finalizer on a []byte. You can only set a finalizer on something that is a pointer.
You can't set a finalizer on
You could then set a finalizer on |
Closing since #21865 has developed into a proposal that has been accepted |
This proposal is aimed at tackling the problem of leaking sensitive data when using Go code that was written by someone else, i.e. from the standard library or elsewhere.
In your own code you can use a special allocator (such as one provided by memguard or using syscalls directly) to hold sensitive data.
However, the vast majority of people are not cryptographers, experts at implementing cryptographic code, or capable of vetting code for soundness. For this reason they must rely on standard implementations provided by the standard library or trustworthy third-parties (libsodium, nacl, etc.)
The problem is that these implementations are often written with little consideration towards limiting the exposure of sensitive state. Take this example of the blake2b-256 digest function from the
crypto
package:The digest is computed and then copied to a new fixed-sized array which is returned to the caller. There is no way for the caller to ensure that the state remains within specified memory regions. Issues like this are everywhere.
The most straightforward solution is to re-write parts of the implementation yourself, ensuring that they use a specified allocator and clean up state when they are done. However this is non-trivial and cryptographic code is really tricky.
None of the previous proposals (#21374 and #21865) manage to resolve this issue so here I propose something different: the ability to provide a custom allocator within specified namespaces, such as an imported package. I'm not certain yet on the specifics so I very much am looking for discussion at this time to see if this idea is feasible.
We can define an allocator with some bare-minimum required functionality:
(Perhaps a
realloc
orresize
method could also be included, although this can be emulated by allocating a new mapping of the required size, copying the data, and destroying the old mapping. From a performance point of view this would only be useful if the realloc function dynamically resized the memory region without copying data, although this may not be possible when the new size is greater.)Ignoring syntax for the moment, imagine the runtime knows that when
packageA
requires memory it should be using this black-box allocator.packageA
executes an instruction requiring a new heap allocation of 32 bytes.a.Alloc(32)
and is essentially given a pointer to a 32 byte region of memory on the heap. The semantics of how the allocator did this is irrelevant, and the only assumption is that this (and only this) region of memory is safe to use.packageA
.Free
when it detects no references to the memory.Using memguard to implement this is fairly trivial. An added bonus is that memguard keeps a reference to every active allocation that has not yet been destroyed, so the original caller (whoever is importing
packageA
) may callmemguard.Purge()
in order to wipe all state, removing reliance on the garbage collector calling the finaliser.Another bonus of this design is that the interface is very minimal, allowing many different implementations of the allocator to "compete" for the "best" solution. AFAIK the only one at the moment is memguard, API reference here.
Now on to the syntax.
The solution I'm toying with at the moment is some semantics added to the
unsafe
package, allowing the caller to specify a scope somehow. This function would be called before the library is used to perform any sensitive operation.This preserves backwards-compatibility, requiring no changes to the Go language syntax or specification.
Another way could be to specify the allocator at import-time using some special syntax.
This method would require changes to the language but it is backwards-compatible in that code that worked before would continue working and code that was broken before would almost certainly still be broken, albeit in a different way. (Unless old code happened to accidentally implement the correct interface and also guess the syntax for using it.)
Another solution still would be to overload not packages but something else like a function's namespace and everything it calls, although this might be more tricky. Or perhaps overload the allocator for the entire program but this could be undesirable if the program allocates a lot of memory, most of which is not sensitive. (This could be accomplished anyway with a function in
unsafe
by passing it the name of the current package, ormain
).Anyways, looking for insights.
/cc @FiloSottile @randall77 @ianlancetaylor @agl
The text was updated successfully, but these errors were encountered: