fix potential race in updating inode cache

Some uses of linkFromAnnex are inside replaceWorkTreeFile, which was
already safe, but others use it directly on the work tree file, which
was race-prone. Eg, if the work tree file was first removed, then
linkFromAnnex called to populate it, the user could have re-written it in
the interim.

This came to light during an audit of all calls of addInodeCaches,
looking for such races. All the other uses of it seem ok.

Sponsored-by: Brett Eisenberg on Patreon
This commit is contained in:
Joey Hess 2021-07-27 13:01:30 -04:00
parent e4b2a067e0
commit 14683da9eb
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
5 changed files with 22 additions and 6 deletions

View file

@ -30,6 +30,7 @@ module Annex.Content (
populatePointerFile, populatePointerFile,
linkToAnnex, linkToAnnex,
linkFromAnnex, linkFromAnnex,
linkFromAnnex',
LinkAnnexResult(..), LinkAnnexResult(..),
unlinkAnnex, unlinkAnnex,
checkedCopyFile, checkedCopyFile,
@ -78,6 +79,7 @@ import Annex.Link
import Annex.LockPool import Annex.LockPool
import Annex.UUID import Annex.UUID
import Annex.InodeSentinal import Annex.InodeSentinal
import Annex.ReplaceFile
import Annex.AdjustedBranch (adjustedBranchRefresh) import Annex.AdjustedBranch (adjustedBranchRefresh)
import Messages.Progress import Messages.Progress
import Types.Remote (RetrievalSecurityPolicy(..)) import Types.Remote (RetrievalSecurityPolicy(..))
@ -382,9 +384,23 @@ linkToAnnex key src srcic = ifM (checkSecureHashes' key)
, return LinkAnnexFailed , return LinkAnnexFailed
) )
{- Makes a destination file be a link or copy from the annex object. -} {- Makes a destination file be a link or copy from the annex object.
-
- linkAnnex stats the file after copying it to add to the inode
- cache. But dest may be a file in the working tree, which could
- get modified immediately after being populated. To avoid such a
- race, call linkAnnex on a temporary file and move it into place
- afterwards. Note that a consequence of this is that, if the file
- already exists, it will be overwritten.
-}
linkFromAnnex :: Key -> RawFilePath -> Maybe FileMode -> Annex LinkAnnexResult linkFromAnnex :: Key -> RawFilePath -> Maybe FileMode -> Annex LinkAnnexResult
linkFromAnnex key dest destmode = do linkFromAnnex key dest destmode =
replaceFile (const noop) (fromRawFilePath dest) $ \tmp ->
linkFromAnnex' key (toRawFilePath tmp) destmode
{- This is only safe to use when dest is not a worktree file. -}
linkFromAnnex' :: Key -> RawFilePath -> Maybe FileMode -> Annex LinkAnnexResult
linkFromAnnex' key dest destmode = do
src <- calcRepo (gitAnnexLocation key) src <- calcRepo (gitAnnexLocation key)
srcic <- withTSDelta (liftIO . genInodeCache src) srcic <- withTSDelta (liftIO . genInodeCache src)
linkAnnex From key src srcic dest destmode linkAnnex From key src srcic dest destmode

View file

@ -131,7 +131,7 @@ scanAnnexedFiles initscan = do
fileMode <$> R.getFileStatus f fileMode <$> R.getFileStatus f
ic <- replaceWorkTreeFile (fromRawFilePath f) $ \tmp -> do ic <- replaceWorkTreeFile (fromRawFilePath f) $ \tmp -> do
let tmp' = toRawFilePath tmp let tmp' = toRawFilePath tmp
linkFromAnnex k tmp' destmode >>= \case linkFromAnnex' k tmp' destmode >>= \case
LinkAnnexOk -> LinkAnnexOk ->
withTSDelta (liftIO . genInodeCache tmp') withTSDelta (liftIO . genInodeCache tmp')
LinkAnnexNoop -> return Nothing LinkAnnexNoop -> return Nothing

View file

@ -85,7 +85,7 @@ makeHardLink :: RawFilePath -> Key -> CommandPerform
makeHardLink file key = do makeHardLink file key = do
replaceWorkTreeFile (fromRawFilePath file) $ \tmp -> do replaceWorkTreeFile (fromRawFilePath file) $ \tmp -> do
mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file
linkFromAnnex key (toRawFilePath tmp) mode >>= \case linkFromAnnex' key (toRawFilePath tmp) mode >>= \case
LinkAnnexFailed -> error "unable to make hard link" LinkAnnexFailed -> error "unable to make hard link"
_ -> noop _ -> noop
next $ return True next $ return True

View file

@ -357,7 +357,7 @@ verifyWorkTree key file = do
let tmp' = toRawFilePath tmp let tmp' = toRawFilePath tmp
mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file mode <- liftIO $ catchMaybeIO $ fileMode <$> R.getFileStatus file
ifM (annexThin <$> Annex.getGitConfig) ifM (annexThin <$> Annex.getGitConfig)
( void $ linkFromAnnex key tmp' mode ( void $ linkFromAnnex' key tmp' mode
, do , do
obj <- calcRepo (gitAnnexLocation key) obj <- calcRepo (gitAnnexLocation key)
void $ checkedCopyFile key obj tmp' mode void $ checkedCopyFile key obj tmp' mode

View file

@ -50,7 +50,7 @@ perform dest key = do
replaceWorkTreeFile (fromRawFilePath dest) $ \tmp -> replaceWorkTreeFile (fromRawFilePath dest) $ \tmp ->
ifM (inAnnex key) ifM (inAnnex key)
( do ( do
r <- linkFromAnnex key (toRawFilePath tmp) destmode r <- linkFromAnnex' key (toRawFilePath tmp) destmode
case r of case r of
LinkAnnexOk -> return () LinkAnnexOk -> return ()
LinkAnnexNoop -> return () LinkAnnexNoop -> return ()