addurl --preserve-filename and a few related changes

* addurl --preserve-filename: New option, uses server-provided filename
  without any sanitization, but with some security checking.

  Not yet implemented for remotes other than the web.

* addurl, importfeed: Avoid adding filenames with leading '.', instead
  it will be replaced with '_'.

  This might be considered a security fix, but a CVE seems unwattanted.
  It was possible for addurl to create a dotfile, which could change
  behavior of some program. It was also possible for a web server to say
  the file name was ".git" or "foo/.git". That would not overrwrite the
  .git directory, but would cause addurl to fail; of course git won't
  add "foo/.git".

sanitizeFilePath is too opinionated to remain in Utility, so moved it.

The changes to mkSafeFilePath are because it used sanitizeFilePath.
In particular:

	isDrive will never succeed, because "c:" gets munged to "c_"
	".." gets sanitized now
	".git" gets sanitized now
	It will never be null, because sanitizeFilePath keeps the length
	the same, and splitDirectories never returns a null path.

Also, on the off chance a web server suggests a filename of "",
ignore that, rather than trying to save to such a filename, which would
fail in some way.
This commit is contained in:
Joey Hess 2020-05-08 16:09:29 -04:00
parent 54599207f7
commit 6952060665
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
9 changed files with 132 additions and 39 deletions

View file

@ -1,6 +1,6 @@
{- git-annex command
-
- Copyright 2011-2017 Joey Hess <id@joeyh.name>
- Copyright 2011-2020 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
@ -23,6 +23,7 @@ import Annex.CheckIgnore
import Annex.Perms
import Annex.UUID
import Annex.YoutubeDl
import Annex.UntrustedFilePath
import Logs.Web
import Types.KeySource
import Types.UrlContents
@ -52,6 +53,7 @@ data DownloadOptions = DownloadOptions
{ relaxedOption :: Bool
, rawOption :: Bool
, fileOption :: Maybe FilePath
, preserveFilenameOption :: Bool
}
optParser :: CmdParamsDesc -> Parser AddUrlOptions
@ -77,7 +79,7 @@ optParser desc = AddUrlOptions
)
parseDownloadOptions :: Bool -> Parser DownloadOptions
parseDownloadOptions withfileoption = DownloadOptions
parseDownloadOptions withfileoptions = DownloadOptions
<$> switch
( long "relaxed"
<> help "skip size check"
@ -86,12 +88,18 @@ parseDownloadOptions withfileoption = DownloadOptions
( long "raw"
<> help "disable special handling for torrents, youtube-dl, etc"
)
<*> if withfileoption
<*> (if withfileoptions
then optional (strOption
( long "file" <> metavar paramFile
<> help "specify what file the url is added to"
))
else pure Nothing
else pure Nothing)
<*> (if withfileoptions
then switch
( long "preserve-filename"
<> help "use filename provided by server as-is"
)
else pure False)
seek :: AddUrlOptions -> CommandSeek
seek o = startConcurrency commandStages $ do
@ -207,16 +215,35 @@ startWeb addunlockedmatcher o urlstring = go $ fromMaybe bad $ parseURI urlstrin
file <- adjustFile o <$> case fileOption (downloadOptions o) of
Just f -> pure f
Nothing -> case Url.urlSuggestedFile urlinfo of
Nothing -> pure $ url2file url (pathdepthOption o) pathmax
Just sf -> do
let f = truncateFilePath pathmax $
sanitizeFilePath sf
ifM (liftIO $ doesFileExist f <||> doesDirectoryExist f)
( pure $ url2file url (pathdepthOption o) pathmax
, pure f
)
Just sf | not (null sf) -> if preserveFilenameOption (downloadOptions o)
then do
checkPreserveFileNameSecurity sf
return sf
else do
let f = truncateFilePath pathmax $
sanitizeFilePath sf
ifM (liftIO $ doesFileExist f <||> doesDirectoryExist f)
( pure $ url2file url (pathdepthOption o) pathmax
, pure f
)
_ -> pure $ url2file url (pathdepthOption o) pathmax
performWeb addunlockedmatcher o urlstring file urlinfo
-- sanitizeFilePath avoids all these security problems
-- (and probably others, but at least this catches the most egrarious ones).
checkPreserveFileNameSecurity :: FilePath -> Annex ()
checkPreserveFileNameSecurity f = do
checksecurity escapeSequenceInFilePath False "escape sequence"
checksecurity pathTraversalInFilePath True "path traversal"
checksecurity gitDirectoryInFilePath True "contains a .git directory"
where
checksecurity p canshow d = when (p f) $
giveup $ concat
[ "--preserve-filename was used, but the filename "
, if canshow then "(" ++ f ++ ") " else ""
, "has a security problem (" ++ d ++ "), not adding."
]
performWeb :: AddUnlockedMatcher -> AddUrlOptions -> URLString -> FilePath -> Url.UrlInfo -> CommandPerform
performWeb addunlockedmatcher o url file urlinfo = ifAnnexed (toRawFilePath file) addurl geturl
where