Skip to content

Commit 71a24d6

Browse files
committedJan 25, 2018
Make incremental update of uncompressed index.tar more robust
The current code blindly assumes that the pre-existing index.tar is a prefix to the new uncompressed index.tar; Besides the possibility of data corruption, the incremental index.tar.gz update logic however supports update transactions which can violate this assumption, resulting in a state where the index.tar doesn't get updated or alternative gets corrupted for real. This patch makes the incremental update of the uncompressed index.tar more robust by verifying that the prefix of the old index.tar is in fact contained in the new index.tar data stream it is to be updated with. If this validation fails, the code falls back to the (slower) non-incremental full decompression codepath. This guarantees that the uncompressed index.tar will be consistent with the compressed index.tar.gz. This fixes #196.
1 parent afd5072 commit 71a24d6

File tree

1 file changed

+40
-10
lines changed
  • hackage-security/src/Hackage/Security/Client/Repository

1 file changed

+40
-10
lines changed
 

Diff for: ‎hackage-security/src/Hackage/Security/Client/Repository/Cache.hs

+40-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Hackage.Security.Client.Repository.Cache (
1616

1717
import Control.Exception
1818
import Control.Monad
19+
import Control.Monad.IO.Class
1920
import Data.Maybe
2021
import Codec.Archive.Tar (Entries(..))
2122
import Codec.Archive.Tar.Index (TarIndex, IndexBuilder, TarEntryOffset)
@@ -29,6 +30,7 @@ import Hackage.Security.Client.Repository
2930
import Hackage.Security.Client.Formats
3031
import Hackage.Security.TUF
3132
import Hackage.Security.Util.Checked
33+
import Hackage.Security.Util.Exit
3234
import Hackage.Security.Util.IO
3335
import Hackage.Security.Util.Path
3436

@@ -65,21 +67,47 @@ cacheRemoteFile cache downloaded f isCached = do
6567
unzipIndex :: IO ()
6668
unzipIndex = do
6769
createDirectoryIfMissing True (takeDirectory indexUn)
68-
shouldTryIncremenal <- cachedIndexProbablyValid
69-
if shouldTryIncremenal
70-
then unzipIncremenal
71-
else unzipNonIncremenal
70+
shouldTryIncremental <- cachedIndexProbablyValid
71+
if shouldTryIncremental
72+
then do
73+
success <- unzipIncremental
74+
unless success unzipNonIncremental
75+
else unzipNonIncremental
7276
where
73-
unzipIncremenal = do
77+
unzipIncremental = do
7478
compressed <- readLazyByteString indexGz
7579
let uncompressed = GZip.decompress compressed
76-
withFile indexUn ReadWriteMode $ \h -> do
77-
currentSize <- hFileSize h
80+
81+
-- compare prefix of old index with prefix of new index to
82+
-- ensure that it's safe to incrementally append
83+
(seekTo',newTail') <- withFile indexUn ReadMode $ \h ->
84+
multipleExitPoints $ do
85+
currentSize <- liftIO $ hFileSize h
7886
let seekTo = 0 `max` (currentSize - tarTrailer)
79-
hSeek h AbsoluteSeek seekTo
80-
BS.L.hPut h $ BS.L.drop (fromInteger seekTo) uncompressed
87+
(newPrefix,newTail) = BS.L.splitAt (fromInteger seekTo)
88+
uncompressed
89+
90+
(oldPrefix,oldTrailer) <- BS.L.splitAt (fromInteger seekTo) <$>
91+
liftIO (BS.L.hGetContents h)
92+
93+
unless (oldPrefix == newPrefix) $
94+
exit (0,mempty) -- corrupted index.tar prefix
95+
96+
-- sanity check: verify there's a 1KiB zero-filled trailer
97+
unless (oldTrailer == tarTrailerBs) $
98+
exit (0,mempty) -- corrupted .tar trailer
8199

82-
unzipNonIncremenal = do
100+
return (seekTo,newTail)
101+
102+
if seekTo' <= 0
103+
then return False -- fallback to non-incremental update
104+
else withFile indexUn ReadWriteMode $ \h -> do
105+
-- everything seems fine; append the new data
106+
liftIO $ hSeek h AbsoluteSeek seekTo'
107+
liftIO $ BS.L.hPut h newTail'
108+
return True
109+
110+
unzipNonIncremental = do
83111
compressed <- readLazyByteString indexGz
84112
let uncompressed = GZip.decompress compressed
85113
withFile indexUn WriteMode $ \h ->
@@ -108,6 +136,8 @@ cacheRemoteFile cache downloaded f isCached = do
108136
tarTrailer :: Integer
109137
tarTrailer = 1024
110138

139+
tarTrailerBs = BS.L.replicate (fromInteger tarTrailer) 0x00
140+
111141
-- | Rebuild the tarball index
112142
--
113143
-- Attempts to add to the existing index, if one exists.

0 commit comments

Comments
 (0)
Please sign in to comment.