Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

[CSM-25] Wallet secret key file mode #98

Merged
merged 24 commits into from
Mar 29, 2017

Conversation

dniku
Copy link
Contributor

@dniku dniku commented Mar 13, 2017

No description provided.

@dniku dniku self-assigned this Mar 13, 2017
@dniku dniku requested a review from akegalj March 13, 2017 12:55
@mention-bot
Copy link

@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.

@dniku dniku requested a review from flyingleafe March 13, 2017 12:59

-- | Check whether a given file has mode 600.
failIfModeNot600 :: (MonadIO m, MonadFail m) => FilePath -> m ()
failIfModeNot600 path = do
Copy link
Contributor

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

?

liftIO $
#ifdef POSIX
if exists
then failIfModeNot600 path
Copy link
Contributor

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:

  1. 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)
  2. Write the key into the file
  3. Force-move the new file over the old one (like mv -f)

why are we failing at all?

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)

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.

Copy link
Contributor

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:

SSH fails in this situation. Should we do the same?

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?

Copy link
Contributor Author

@dniku dniku Mar 13, 2017

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.

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?

Copy link
Contributor

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

@Pastafarianist

Copy link
Member

@vincenthz vincenthz Mar 14, 2017

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)
...

Copy link
Contributor Author

@dniku dniku Mar 14, 2017

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.

@@ -109,6 +158,7 @@ peekUserSecret path = takeReadLock path $ do
-- 'writeUserSecretRelease'.
takeUserSecret :: (MonadIO m) => FilePath -> m UserSecret
takeUserSecret path = liftIO $ do
initializeUserSecret path

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.

Copy link
Contributor

@akegalj akegalj Mar 14, 2017

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?

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Contributor Author

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).

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.

@akegalj akegalj requested a review from vincenthz March 14, 2017 12:05
writeRaw u = do
let path = u ^. usPath
-- On POSIX platforms, openTempFile guarantees that the file
-- will be created with mode 600.
Copy link
Member

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.

Copy link
Contributor Author

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.

{-# LANGUAGE TemplateHaskell #-}

-- | Secret key file storage and management functions based on file
-- locking.

#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

"UserSecret { _usKeys = " ++ show _usKeys ++
", _usVss = " ++ show _usVss ++
", _usPath = " ++ show _usPath
"UserSecret { _usKeys = " <> show _usKeys <>
Copy link
Contributor

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)

@@ -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.
Copy link
Contributor

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”)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

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 "")
Copy link
Contributor

@neongreen neongreen Mar 19, 2017

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

-- will be created with mode 600.
bracket
(openBinaryTempFile (takeDirectory path) (takeFileName path))
(\(tempPath, tempHandle) -> do
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@neongreen neongreen left a 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.

Copy link
Member

@vincenthz vincenthz left a 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
Copy link
Contributor

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
Copy link
Contributor

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

@domenkozar
Copy link
Contributor

domenkozar commented Mar 28, 2017

@akegalj we are using -Werror now:

C:\projects\cardano-sl\srcPosUtilUserSecret.hs:31:1: warning: [-Wunused-imports]
        The import of `oct, sformat' from module `Formatting' is redundant
    
    <no location info>: error:

@akegalj
Copy link
Contributor

akegalj commented Mar 28, 2017

@domenkozar right. I will remove warnings now

@akegalj akegalj merged commit 7fd8046 into master Mar 29, 2017
@akegalj akegalj deleted the feature/csm25-wallet-key-permissions branch March 29, 2017 14:18
@dniku dniku changed the title WIP [CSM-25] Wallet secret key file mode [CSM-25] Wallet secret key file mode Apr 10, 2017
ksaric added a commit that referenced this pull request Jul 12, 2017
volhovm pushed a commit that referenced this pull request Dec 7, 2017
* 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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants