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,
linkToAnnex,
linkFromAnnex,
linkFromAnnex',
LinkAnnexResult(..),
unlinkAnnex,
checkedCopyFile,
@ -78,6 +79,7 @@ import Annex.Link
import Annex.LockPool
import Annex.UUID
import Annex.InodeSentinal
import Annex.ReplaceFile
import Annex.AdjustedBranch (adjustedBranchRefresh)
import Messages.Progress
import Types.Remote (RetrievalSecurityPolicy(..))
@ -382,9 +384,23 @@ linkToAnnex key src srcic = ifM (checkSecureHashes' key)
, 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 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)
srcic <- withTSDelta (liftIO . genInodeCache src)
linkAnnex From key src srcic dest destmode

View file

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

View file

@ -85,7 +85,7 @@ makeHardLink :: RawFilePath -> Key -> CommandPerform
makeHardLink file key = do
replaceWorkTreeFile (fromRawFilePath file) $ \tmp -> do
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"
_ -> noop
next $ return True

View file

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

View file

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