-
Notifications
You must be signed in to change notification settings - Fork 1
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
Sensitive data shouldn't get paged out to disk if possible #10
Comments
Oh, this is far more thought out than my ticket. I really want this library pushed through and this is the sort of security critical concept that I'd love to see in an early release. It's a great point that the paging issue is somewhat significantly greater than just using Storables and mlocking—esp. w.r.t. getting the secret key into memory to begin with. It might be a good first step to at least change the representation to Data.Vector.Storable still. I'll think about it. |
Generally, memory management doesn't exist at the level of a Haskell2010 API, so it'll might look like a contextual computation, perhaps withKeyFile :: FilePath -> (Key -> IO a) -> IO ()
generateKey :: (Key -> IO a) -> IO () where I'm |
One possibility is that because the Haskell2010 standard includes the FFI, we merely make FFI calls to functions like In general I'm a fan of trying to actually write C as little as possible for two reasons: one, it's much easier to abstract over in Haskell with minimal bindings. Second, it's safer. In this case, an exception could potentially be made (that is, we just outright have a module that FFI's to a small body of C code, securely mlocking pages and writing/reading to/from disk securely. This is an open design question. It may actually be reasonable to buy into GHC features for this specific purpose alone. We could reasonbly abstract out a 'simple, but not-paging-concious' API for more portability (e.g. windows, if that day ever comes.) The simple and portable API can just use For example, a So the process would be:
For reading/writing from disk, the approach is similar, except we would want to first possibly use unbuffered IO to read directly from an This also has some complications that you would necessarily resize this set of pages when there's not enough space, etc. This module would then necessitate some local state to be able to safely generate and manipulate keys in a thread safe manner (because you'll need to manage the 'global cache of keys'.) Meaning we would need re-entrancy to be handled through a lock: module Crypto.NaCl.Key (...) where
#ifdef __GLASGOW_HASKELL__ && defined(unix)
-- safe API
-- This is never exported.
keyStoreLock :: MVar Bool
keyStoreLock = unsafePerformIO $ newMVar False
{-# NOINLINE keyStoreLock #-}
#else
-- portable API for fallback
...
#endif I think the first step is actually designing a portable, easy-to-use storage or key API that can be used for storage of keypairs and the like. It can be very simple I imagine in the general case. The necessity of keys being pinned can then be worked on in a more 'secure' implementation of the API. This implementation I have outlined is something I have had in my head, not something I have put my money on yet. If that is too complicated, or there are better references concerning such problems, I'm open to alternative approaches. |
I agree that a stable key API is critical here. The keystore management makes me think a lot about using the ST monad to control the pages in a less opaque way. This might generally be a less important component of |
I believe that strict ByteStrings are actually pinned in memory, the reason being to lessen surprises when giving pointers to the ByteString contents to FFI-code that keeps the pointer (there was a thread on reddit about this issue recently) To actually lock the key so that it isn't swapped is harder than just wrapping mlock(2) or VirtualProtect, since you need root access for mlock. You could always try to mlock, and log if you fail, but users of your library will probably not run their code as root. Application programs like GnuPG can start setuid root, lock their memory and then drop privileges, but that is hard for a library to do. That being said, Vincent Hanquez has created a securemem library. As far as I know it doesn't do any locking, but it tries to scrub allocated memory when a pointer goes out of scope. I have created a branch in my fork of salt to use this library, and I could port it to your master branch if you are interested. |
Talking with Dan Peebles, he mentioned someone else working on NaCl bindings (Michael Stone) brought up the fact that you should use something like
mlock(2)
on secret keys to ensure they're never paged to disk.This would require changing the representation of the
SecretKey
type, although it needed consolidation anyway (it's merely aliased asByteString
in every module, which is dumb.) However, it will also break any sort of compiler portability at this point I think, as the underlying runtime will need to support something like pinned arrays. In GHC, a pinned array is a kind ofByteArray#
that is never moved by the garbage collector, so foreign libraries can safely use it without fear of the pointer moving. So we can safelymlock(2)
those pages afterwords.We could possibly use a type like
vector
'sData.Vector.Storable
type, which is pinned through aForeignPtr
. Generating random keys (viacrypto_box_keypair
or whatnot) would then be the process of allocating a pinned array (and maybe garbling it,) locking it, and then stashing the secret key to it - NaCl itself does no allocation, so it'll just cleanly write to the pointer we give it.However, there are other concerns afoot in that if you were to call, say,
readFile :: FilePath -> IO SecretKey
on a secret key, it could potentially make a copy of that key somewhere in memory before returning a pinned, locked array. And that page could get paged out.There may need to be GHC support for this ticket to really be sound/doable without mass FFI bindings. It will probably need an entire API dedicated to it anyway, since secret keys should especially be secure.
The text was updated successfully, but these errors were encountered: