-
Notifications
You must be signed in to change notification settings - Fork 15
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
argon2: Add helpers for running the KDF remotely #328
base: master
Are you sure you want to change the base?
Conversation
As Argon2 is memory intensive, it's not suitable for multiple invocations in long-lived garbage collected processes. For this reason, Argon2 is abstracted with an interface (Argon2KDF), of which the application sets a global version of this which is intended to proxy KDF requests to a short-lived remote process which uses the real InProcessArgon2KDF. This adds some functionality to facilitate this. First of all, InProcessArgon2KDF is no longer a variable - it's a function. By default, it's methods return an error unless the application code has called SetIsArgon2RemoteProcess, which unlocks the real in-process KDF. Then there are JSON serializable types "Argon2RemoteInput" and "Argon2RemoteOutput". The input can be fed directly to RunArgon2RequestInRemoteProcess on the remote side, but this is a fairly low-level API. There is a higher level API - NewRemoteArgon2KDF, for use in the application process, and which returns an implementation of Argon2KDF which proxies requests to a short-lived remote helper process. The caller supplied a function to construct an appropriate exec.Cmd instance for this. This function is configured so that the remote process recieves a request on stdin and it expects a response on stdout. The remote process passes both os.Stdin and os.Stdout to WaitAndRunArgon2RequestInRemoteProcess, although it doesn't hardcode these descriptors for implementations that want to construct their own transport that doesn't rely on stdin and stdout. Once a remote process has completed a request, it should exit cleanly. Neither RunArgon2RequestInRemoteProcess or WaitAndRunArgon2RequestInRemoteProcess support being called more than once in the same process. The code in cmd/run_argon2 provides an example remote process, although this is mainly useful for unit testing (where it is currently used). It is envisaged that the remote process will be a special mode of snapd and snap-bootstrap in order to avoid adding an additional new go binary just for this.
4cfd159
to
017287b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did a first pass
argon2_remote_support.go
Outdated
"github.com/snapcore/secboot/internal/argon2" | ||
) | ||
|
||
type nullInProcessArgon2KDFImpl struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null is a bit of a strange name for something that actually fails
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type has gone, although there is still a nullArgon2KDFImpl
that existed previously.
argon2_remote_support.go
Outdated
defer func() { | ||
// Run Cmd.Wait in a defer so that we shut down on error paths too, | ||
// and we capture the Wait error if there was no other error. | ||
waitErr := cmd.Wait() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need a timeout mechanism around this?
argon2_remote_support.go
Outdated
// os.Stdout when using - certainly when using [NewRemoteArgon2KDF] on the parent side. | ||
// This function can only be called once in a process. Subsequent calls in the same | ||
// process will result in an error response being returned. | ||
func WaitAndRunArgon2RequestInRemoteProcess(in io.Reader, out io.Writer) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function code does not seem to be exercised
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It gets executed in a sub-process (cmd/run_argon2
) by any test that makes use of NewOutOfProcessArgon2KDF
, which might be why it doesn't appear in the test coverage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an in-process test of this function so that it appears in the test coverage.
@@ -148,6 +150,11 @@ func MockHashAlgAvailable() (restore func()) { | |||
} | |||
} | |||
|
|||
func ClearIsArgon2RemoteProcess() { | |||
atomic.StoreUint32(&argon2RemoteProcessStatus, notArgon2RemoteProcess) | |||
runtime.GC() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the GC here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a comment to this function to explain why.
argon2_remote_support.go
Outdated
} | ||
} | ||
|
||
switch input.Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a bit more of the error cases below should be tested
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added some more test cases.
@chrisccoulson Also OutOfProcess might be a clearer term than Remote, if a bit longer |
I've renamed things to |
This includes some other small refactorigns, eg, the handler process makes use of the global Argon2KDF implementation now in the same way that the parent process does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the changes, maybe I missed it but it doesn't seem you replied to my question about what to do if the subprocess takes way too long/gets stuck for some reason
Aha, I'll think of a way to address that. As the process is meant to block for some time, I wonder whether creating some sort of regular watchdog or ping would be more appropriate here. It shouldn't be that hard to add to the protocol. |
As Argon2 is memory intensive, it's not suitable for multiple invocations in long-lived
garbage collected processes where multiple invocations may result in the process being
unable to perform new allocations, or even worse, triggering the kernel’s OOM killer.
For this reason, Argon2 is currently abstracted with an interface (
Argon2KDF
), of whichthe application sets a global version of this which is intended to proxy KDF requests to
a short-lived remote process which uses the real
InProcessArgon2KDF
.This adds some functionality to facilitate this.
Then there are JSON serializable types
Argon2OutOfProcessRequest
andArgon2OutOfProcessResponse
. The request can be fed directly toRunArgon2OutOfProcessRequest
on the remote side, but this is a fairly low-level API - the application still has to
deal with the transport.
There is a higher level API -
NewOutOfProcessArgon2KDF
, for use in the application process,and which returns an implementation of
Argon2KDF
which proxies requests to a short-livedremote handler process. The caller supplies a function to construct an appropriate
exec.Cmd
instance for this. This function configures the
exec.Cmd
so that the handler process receivesa request on stdin and it expects a response on stdout. The handler process is expected to pass
both
os.Stdin
andos.Stdout
toWaitForAndRunArgon2OutOfProcessRequest
, although it doesn'thardcode these descriptors for implementations that want to construct their own processes with
transports that don't rely on stdin and stdout.
Once a handler process has completed a request, it should exit cleanly. Neither
RunArgon2OutOfProcessRequest
orWaitForAndRunArgon2OutOfProcessRequest
support being calledmore than once in the same process, which is enforced by an unexported
Argon2KDF
implementationthat becomes the global
Argon2KDF
implementation in the remote handler process after callingSetIsArgon2HandlerProcess
. Note that calling this function is required before usingRunArgon2OutOfProcessRequest
andWaitForAndRunArgon2OutOfProcessRequest
, else they will justrespond with an error.
The code in cmd/run_argon2 provides an example handler process, although this is mainly useful for
unit testing (where it is currently used). It is envisaged that the handler process will be a
special mode of snapd and snap-bootstrap in order to avoid adding an additional go binary just for
this.