-
Notifications
You must be signed in to change notification settings - Fork 629
Conversation
@Pastafarianist, thanks for your PR! By analyzing the history of the files in this pull request, we identified @gromakovsky, @neongreen and @flyingleafe to be potential reviewers. |
src/Pos/Util/UserSecret.hs
Outdated
|
||
-- | Check whether a given file has mode 600. | ||
failIfModeNot600 :: (MonadIO m, MonadFail m) => FilePath -> m () | ||
failIfModeNot600 path = do |
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 wouldn't fail. Why don't we just have:
isFileMode600 :: (MonadIO m, MonadFail m) => FilePath -> m Bool
?
src/Pos/Util/UserSecret.hs
Outdated
liftIO $ | ||
#ifdef POSIX | ||
if exists | ||
then failIfModeNot600 path |
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.
if file mode isn't 600 we should:
- Create a temporary file with the right permissions (under
/tmp
or something, I hope there is a library or a library function for that; I have no idea how to do this on Mac OS, for example) - Write the key into the file
- Force-move the new file over the old one (like
mv -f
)
why are we failing at all?
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.
(/tmp works on OS X)
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.
Writing to a temporary file and then moving also makes this atomic (sometimes). Not sure whether that matters here, but it could make sense to always do that.
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.
@startling do you think it is ok to fail? This is our concern. For example user wants to run cardano-node with a key which is on disk, but it has wrong permissions. We can:
- fail and notify user that he should manually change permissions
- using steps described above ([CSM-25] Wallet secret key file mode #98 (comment)) just silently fix permissions
SSH fails in this situation. Should we do the same?
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.
is this the only place a key would be stored?
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.
@startling I think so, but I'm not sure. We should ask someone who participated in designing this part. @flyingleafe?
UPD: @flyingleafe says that the *.key
files in the cardano-sl
directory are the only place where private keys are stored, not counting RAM during execution.
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 would tend towards exiting and having the user fix it, since this should never happen in normal operation?
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.
Yes. In normal operation we will create keys with the correct permissions. Ok, we will leave this as it is
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 is quite racy, for something that could be done as one operation; you can create the file (O_CREAT) and fail if it already exist (O_EXCL), and the mode are automatically set to be the right one with the 3rd parameters of open
.
It's probably possible with unix
already, but using the handle
branch of foundation
, you can do that with:
import Foundation.System.Bindings.Posix
withFilepath filepath $ \fp -> do
fd <- sysPosixOpen fp (sysPosix_O_WRONLY .|. sysPosix_O_CREAT .|. sysPosix_O_EXCL .|. sysPosix_O_NONBLOCK) (CMode 0o600)
...
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.
@vincenthz Apparently, unix
doesn't support O_CREAT
. There is an open bug about that and a pull request which claims to fix it. It has not been merged in almost a year though. Seems like the library's authors don't have a lot of interest in it. We could fork it.
src/Pos/Util/UserSecret.hs
Outdated
@@ -109,6 +158,7 @@ peekUserSecret path = takeReadLock path $ do | |||
-- 'writeUserSecretRelease'. | |||
takeUserSecret :: (MonadIO m) => FilePath -> m UserSecret | |||
takeUserSecret path = liftIO $ do | |||
initializeUserSecret path |
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 can race under certain circumstances.
- this code creates the file with mode 600
- Attacker (assuming there's write access to the directory) swaps it out with one that doesn't have the same permissions.
- this code finds it from the path and then writes to it, exposing secrets
I think this is marginal, since probably an attacker can't write to this directory. But it's much better to use the same file handle throughout.
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.
@startling I don't see where this:
Attacker (assuming there's write access to the directory) swaps it out with one that doesn't have the same permissions.
this code finds it from the path and then writes to it, exposing secrets
can happen. Can you point to the code?
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.
@startling could you elaborate how this is a problem? writeRaw
always ensures that the key is written with the correct permissions, so the secret should never be exposed.
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.
If it's a shared directory (like /tmp without per-user tmp), then you defintely need to keep your fd after creation (specially relevant for tmp file). otherwise for a user directory, that is presumably not readable/writable by anyone else that probably doesn't matter much
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.
@vincenthz AFAIU in the current implementation it's a user directory (for me, the *.key
files are created in the root of the cloned cardano-sl
repo).
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 see, I think I misunderstood.
src/Pos/Util/UserSecret.hs
Outdated
writeRaw u = do | ||
let path = u ^. usPath | ||
-- On POSIX platforms, openTempFile guarantees that the file | ||
-- will be created with mode 600. |
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.
you probably mean openBinaryTempFile, and openTempFile is actually using 0o666 as default mask + whatever the process umask is.
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.
@vincenthz you're right about openBinaryTempFile
, I'll fix that. Also, please see the docs for openTempFile
:
The file is created with permissions such that only the current user can read/write it.
src/Pos/Util/UserSecret.hs
Outdated
{-# LANGUAGE TemplateHaskell #-} | ||
|
||
-- | Secret key file storage and management functions based on file | ||
-- locking. | ||
|
||
#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__) |
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 turned out that just #ifndef mingw32_HOST_OS
is enough: https://ghc.haskell.org/trac/ghc/ticket/9945#comment:15
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.
Fixed.
src/Pos/Util/UserSecret.hs
Outdated
"UserSecret { _usKeys = " ++ show _usKeys ++ | ||
", _usVss = " ++ show _usVss ++ | ||
", _usPath = " ++ show _usPath | ||
"UserSecret { _usKeys = " <> show _usKeys <> |
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.
We usually use Formatting
for this (grep for sformat
)
src/Pos/Util/UserSecret.hs
Outdated
@@ -81,34 +99,70 @@ instance Bi UserSecret where | |||
keys <- get | |||
return $ def & usVss .~ vss & usPrimKey .~ pkey & usKeys .~ keys | |||
|
|||
#ifdef POSIX | |||
-- | Constant that defines file mode 600. |
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'd be nice if there was a comment saying what mode 600
corresponds to (afaik, “only owner can read and write”)
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.
Fixed.
src/Pos/Util/UserSecret.hs
Outdated
throwM $ Internal $ | ||
"Key file access mode is incorrect. Set it to 600 and try again." <> | ||
" Key file path: " <> show path <> | ||
" Current mode: " <> toText (showIntAtBase 8 intToDigit accessMode "") |
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.
Such things can be done with Formatting
too, using e.g. the oct
formatter
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.
Fixed.
src/Pos/Util/UserSecret.hs
Outdated
-- will be created with mode 600. | ||
bracket | ||
(openBinaryTempFile (takeDirectory path) (takeFileName path)) | ||
(\(tempPath, tempHandle) -> do |
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.
So you want the file to be created even if BSL.hPut tempHandle $ encode u
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.
Uhh... I've fixed this to a more stable version, but the code has become significantly uglier, and I would welcome any suggestions on how to improve it.
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 can't say much about the actual content of this PR – it looks okay to me but keep in mind that I'm not a systems guy. I've requested some stylistic changes, though.
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.
considering that it's use in a user directory, it seems fine.
{-# LANGUAGE TemplateHaskell #-} | ||
|
||
-- | Secret key file storage and management functions based on file | ||
-- locking. | ||
|
||
#if !defined(mingw32_HOST_OS) | ||
#define POSIX | ||
#endif |
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.
Such redefinitions only introduce confusion in the code, are they really necessary?
#ifdef POSIX | ||
failIfModeNot600 path | ||
#endif | ||
content <- either (throwM . Internal . toText) pure . decodeFull =<< BSL.readFile path |
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.
Internal
is not defined according to appveyor
@akegalj we are using
|
@domenkozar right. I will remove warnings now |
* Add preliminar tests trying to reproduce the bug * Successfully replicate the bug * [TW-162] Fix the bug * Add OutBoundQueueSpec to hspec-discovery * [TW-162] mark more aggressively currentlyInFlight as being a debug function * Try to avoid conflict on .gitignore * [TW-162] Address review's feedback
No description provided.