add: Detect when xattrs or perhaps ACLs prevent locking down a file's content
And fail with an informative message. I don't think ACLs can prevent removing the write bit, but I'm not sure, so kept it mentioning them as a possibility. Should git-annex lock also check if the write bits are able to be removed? Maybe, but the case I know about with xattrs involves cp -a copying NFS xattrs, and it's the copy of the file that is the problem. So when locking a file, I guess it will not be the copy. Sponsored-by: Dartmouth College's Datalad project
This commit is contained in:
parent
e17342b2a0
commit
a99a84f342
6 changed files with 84 additions and 38 deletions
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex content ingestion
|
{- git-annex content ingestion
|
||||||
-
|
-
|
||||||
- Copyright 2010-2020 Joey Hess <id@joeyh.name>
|
- Copyright 2010-2021 Joey Hess <id@joeyh.name>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -51,8 +51,6 @@ import Annex.AdjustedBranch
|
||||||
import Annex.FileMatcher
|
import Annex.FileMatcher
|
||||||
import qualified Utility.RawFilePath as R
|
import qualified Utility.RawFilePath as R
|
||||||
|
|
||||||
import Control.Exception (IOException)
|
|
||||||
|
|
||||||
data LockedDown = LockedDown
|
data LockedDown = LockedDown
|
||||||
{ lockDownConfig :: LockDownConfig
|
{ lockDownConfig :: LockDownConfig
|
||||||
, keySource :: KeySource
|
, keySource :: KeySource
|
||||||
|
@ -78,7 +76,8 @@ data LockDownConfig = LockDownConfig
|
||||||
- against some changes, like deletion or overwrite of the file, and
|
- against some changes, like deletion or overwrite of the file, and
|
||||||
- allows lsof checks to be done more efficiently when adding a lot of files.
|
- allows lsof checks to be done more efficiently when adding a lot of files.
|
||||||
-
|
-
|
||||||
- Lockdown can fail if a file gets deleted, and Nothing will be returned.
|
- Lockdown can fail if a file gets deleted, or if it's unable to remove
|
||||||
|
- write permissions, and Nothing will be returned.
|
||||||
-}
|
-}
|
||||||
lockDown :: LockDownConfig -> FilePath -> Annex (Maybe LockedDown)
|
lockDown :: LockDownConfig -> FilePath -> Annex (Maybe LockedDown)
|
||||||
lockDown cfg file = either
|
lockDown cfg file = either
|
||||||
|
@ -86,8 +85,8 @@ lockDown cfg file = either
|
||||||
(return . Just)
|
(return . Just)
|
||||||
=<< lockDown' cfg file
|
=<< lockDown' cfg file
|
||||||
|
|
||||||
lockDown' :: LockDownConfig -> FilePath -> Annex (Either IOException LockedDown)
|
lockDown' :: LockDownConfig -> FilePath -> Annex (Either SomeException LockedDown)
|
||||||
lockDown' cfg file = tryIO $ ifM crippledFileSystem
|
lockDown' cfg file = tryNonAsync $ ifM crippledFileSystem
|
||||||
( nohardlink
|
( nohardlink
|
||||||
, case hardlinkFileTmpDir cfg of
|
, case hardlinkFileTmpDir cfg of
|
||||||
Nothing -> nohardlink
|
Nothing -> nohardlink
|
||||||
|
@ -96,7 +95,9 @@ lockDown' cfg file = tryIO $ ifM crippledFileSystem
|
||||||
where
|
where
|
||||||
file' = toRawFilePath file
|
file' = toRawFilePath file
|
||||||
|
|
||||||
nohardlink = withTSDelta $ liftIO . nohardlink'
|
nohardlink = do
|
||||||
|
setperms
|
||||||
|
withTSDelta $ liftIO . nohardlink'
|
||||||
|
|
||||||
nohardlink' delta = do
|
nohardlink' delta = do
|
||||||
cache <- genInodeCache file' delta
|
cache <- genInodeCache file' delta
|
||||||
|
@ -107,8 +108,7 @@ lockDown' cfg file = tryIO $ ifM crippledFileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
withhardlink tmpdir = do
|
withhardlink tmpdir = do
|
||||||
when (lockingFile cfg) $
|
setperms
|
||||||
freezeContent file'
|
|
||||||
withTSDelta $ \delta -> liftIO $ do
|
withTSDelta $ \delta -> liftIO $ do
|
||||||
(tmpfile, h) <- openTempFile (fromRawFilePath tmpdir) $
|
(tmpfile, h) <- openTempFile (fromRawFilePath tmpdir) $
|
||||||
relatedTemplate $ "ingest-" ++ takeFileName file
|
relatedTemplate $ "ingest-" ++ takeFileName file
|
||||||
|
@ -125,6 +125,16 @@ lockDown' cfg file = tryIO $ ifM crippledFileSystem
|
||||||
, contentLocation = toRawFilePath tmpfile
|
, contentLocation = toRawFilePath tmpfile
|
||||||
, inodeCache = cache
|
, inodeCache = cache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setperms = when (lockingFile cfg) $ do
|
||||||
|
freezeContent file'
|
||||||
|
checkContentWritePerm file' >>= \case
|
||||||
|
Just False -> giveup $ unwords
|
||||||
|
[ "Unable to remove all write permissions from"
|
||||||
|
, file
|
||||||
|
, "-- perhaps it has an xattr or ACL set."
|
||||||
|
]
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
{- Ingests a locked down file into the annex. Updates the work tree and
|
{- Ingests a locked down file into the annex. Updates the work tree and
|
||||||
- index. -}
|
- index. -}
|
||||||
|
|
|
@ -16,7 +16,7 @@ module Annex.Perms (
|
||||||
noUmask,
|
noUmask,
|
||||||
freezeContent,
|
freezeContent,
|
||||||
freezeContent',
|
freezeContent',
|
||||||
isContentWritePermOk,
|
checkContentWritePerm,
|
||||||
thawContent,
|
thawContent,
|
||||||
thawContent',
|
thawContent',
|
||||||
createContentDir,
|
createContentDir,
|
||||||
|
@ -131,6 +131,12 @@ createWorkTreeDirectory dir = do
|
||||||
- necessary to let other users in the group lock the file. But, in a
|
- necessary to let other users in the group lock the file. But, in a
|
||||||
- shared repository, the current user may not be able to change a file
|
- shared repository, the current user may not be able to change a file
|
||||||
- owned by another user, so failure to set this mode is ignored.
|
- owned by another user, so failure to set this mode is ignored.
|
||||||
|
-
|
||||||
|
- Note that, on Linux, xattrs can sometimes prevent removing
|
||||||
|
- certain permissions from a file with chmod. (Maybe some ACLs too?)
|
||||||
|
- In such a case, this will return with the file still having some mode
|
||||||
|
- it should not normally have. checkContentWritePerm can detect when
|
||||||
|
- that happens with write permissions.
|
||||||
-}
|
-}
|
||||||
freezeContent :: RawFilePath -> Annex ()
|
freezeContent :: RawFilePath -> Annex ()
|
||||||
freezeContent file = unlessM crippledFileSystem $
|
freezeContent file = unlessM crippledFileSystem $
|
||||||
|
@ -149,19 +155,34 @@ freezeContent' sr file = do
|
||||||
removeModes writeModes .
|
removeModes writeModes .
|
||||||
addModes [ownerReadMode]
|
addModes [ownerReadMode]
|
||||||
|
|
||||||
isContentWritePermOk :: RawFilePath -> Annex Bool
|
{- Checks if the write permissions are as freezeContent should set them.
|
||||||
isContentWritePermOk file = ifM crippledFileSystem
|
-
|
||||||
( return True
|
- When the repository is shared, the user may not be able to change
|
||||||
|
- permissions of a file owned by another user. So if the permissions seem
|
||||||
|
- wrong, but the repository is shared, returns Nothing. If the permissions
|
||||||
|
- are wrong otherwise, returns Just False.
|
||||||
|
-}
|
||||||
|
checkContentWritePerm :: RawFilePath -> Annex (Maybe Bool)
|
||||||
|
checkContentWritePerm file = ifM crippledFileSystem
|
||||||
|
( return (Just True)
|
||||||
, withShared go
|
, withShared go
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
go GroupShared = want [ownerWriteMode, groupWriteMode]
|
go GroupShared = want sharedret
|
||||||
go AllShared = want writeModes
|
(includemodes [ownerWriteMode, groupWriteMode])
|
||||||
go _ = return True
|
go AllShared = want sharedret (includemodes writeModes)
|
||||||
want wantmode =
|
go _ = want Just (excludemodes writeModes)
|
||||||
|
|
||||||
|
want mk f =
|
||||||
liftIO (catchMaybeIO $ fileMode <$> R.getFileStatus file) >>= return . \case
|
liftIO (catchMaybeIO $ fileMode <$> R.getFileStatus file) >>= return . \case
|
||||||
Nothing -> True
|
Just havemode -> mk (f havemode)
|
||||||
Just havemode -> havemode == combineModes (havemode:wantmode)
|
Nothing -> mk True
|
||||||
|
|
||||||
|
includemodes l havemode = havemode == combineModes (havemode:l)
|
||||||
|
excludemodes l havemode = all (\m -> intersectFileModes m havemode == nullFileMode) l
|
||||||
|
|
||||||
|
sharedret True = Just True
|
||||||
|
sharedret False = Nothing
|
||||||
|
|
||||||
{- Allows writing to an annexed file that freezeContent was called on
|
{- Allows writing to an annexed file that freezeContent was called on
|
||||||
- before. -}
|
- before. -}
|
||||||
|
|
|
@ -22,6 +22,8 @@ git-annex (8.20210804) UNRELEASED; urgency=medium
|
||||||
* Run cp -a with --no-preserve=xattr, to avoid problems with copied
|
* Run cp -a with --no-preserve=xattr, to avoid problems with copied
|
||||||
xattrs, including them breaking permissions setting on some NFS
|
xattrs, including them breaking permissions setting on some NFS
|
||||||
servers.
|
servers.
|
||||||
|
* add: Detect when xattrs or perhaps ACLs prevent locking down
|
||||||
|
a file's content, and fail with an informative message.
|
||||||
|
|
||||||
-- Joey Hess <id@joeyh.name> Tue, 03 Aug 2021 12:22:45 -0400
|
-- Joey Hess <id@joeyh.name> Tue, 03 Aug 2021 12:22:45 -0400
|
||||||
|
|
||||||
|
|
|
@ -260,8 +260,9 @@ verifyLocationLog key keystatus ai = do
|
||||||
KeyUnlockedThin -> thawContent obj
|
KeyUnlockedThin -> thawContent obj
|
||||||
KeyLockedThin -> thawContent obj
|
KeyLockedThin -> thawContent obj
|
||||||
_ -> freezeContent obj
|
_ -> freezeContent obj
|
||||||
unlessM (isContentWritePermOk obj) $
|
checkContentWritePerm obj >>= \case
|
||||||
warning $ "** Unable to set correct write mode for " ++ fromRawFilePath obj ++ " ; perhaps you don't own that file"
|
Nothing -> warning $ "** Unable to set correct write mode for " ++ fromRawFilePath obj ++ " ; perhaps you don't own that file, or perhaps it has an xattr or ACL set"
|
||||||
|
_ -> return ()
|
||||||
whenM (liftIO $ R.doesPathExist $ parentDir obj) $
|
whenM (liftIO $ R.doesPathExist $ parentDir obj) $
|
||||||
freezeContentDir obj
|
freezeContentDir obj
|
||||||
|
|
||||||
|
|
|
@ -16,29 +16,29 @@ a file themselves with cp -a on this NFS and then git-annex adds the copy.
|
||||||
Probably git-annex would then be unable to remove the write bit
|
Probably git-annex would then be unable to remove the write bit
|
||||||
from the annex object file.
|
from the annex object file.
|
||||||
|
|
||||||
For that matter, the same could happen with ACLs. Eg, I was able to
|
I also worried about ACLS, but it seems like ACLs do not have this
|
||||||
use setfacl to make this happen:
|
problem, because chmod a-w causes the write ACL that was set to be
|
||||||
|
effectively unset:
|
||||||
|
|
||||||
joey@darkstar:~>chmod -w foo
|
joey@darkstar:~>chmod -w foo
|
||||||
joey@darkstar:~>setfacl -m g:nogroup:rw foo
|
joey@darkstar:~>setfacl -m g:nogroup:rw foo
|
||||||
joey@darkstar:~>ls -l foo
|
joey@darkstar:~>ls -l foo
|
||||||
-r--rw-r--+ 1 joey joey 0 Aug 27 12:53 foo
|
-r--rw-r--+ 1 joey joey 0 Aug 27 12:53 foo
|
||||||
joey@darkstar:~>chmod -w foo
|
nobody@darkstar:/home/joey$ echo test >> foo
|
||||||
chmod: foo: new permissions are r--rw-r--, not r--r--r--
|
joey@darkstar:~>chmod a-w foo
|
||||||
- exit 1
|
|
||||||
joey@darkstar:~>perl -e 'chmod(400)' foo
|
|
||||||
joey@darkstar:~>ls -l foo
|
joey@darkstar:~>ls -l foo
|
||||||
-r--rw-r--+ 1 joey joey 0 Aug 27 12:53 foo
|
-r--rw-r--+ 1 joey joey 0 Aug 27 12:53 foo
|
||||||
|
nobody@darkstar:/home/joey$ echo test >> foo
|
||||||
|
bash: foo: Permission denied
|
||||||
|
joey@darkstar:~>getfacl foo
|
||||||
|
# file: foo
|
||||||
|
# owner: joey
|
||||||
|
# group: joey
|
||||||
|
user::r--
|
||||||
|
group::r--
|
||||||
|
group:nogroup:rw- #effective:r--
|
||||||
|
mask::r--
|
||||||
|
other::r--
|
||||||
|
|
||||||
So git-annex would be unable to clear the write bit, and would not be able
|
I've verified that git-annex add also clears the write ACL.
|
||||||
to effectively lock down the file for all users, eg user nobody can write
|
|
||||||
to the file in the above example. There's probably a way to let user joey
|
|
||||||
also write to it, but my attempt to do that with an ACL failed.
|
|
||||||
|
|
||||||
Perhaps git-annex should clear ACLs when ingesting (and locking) files.
|
|
||||||
But perhaps users use ACLs for other purposes that would not prevent
|
|
||||||
lockdown, and so they should not be cleared. And as far as internal-use NFS
|
|
||||||
xattrs, it doesn't seem wise for git-annex to try to unset them from files
|
|
||||||
its ingesting. So I guess I'm going to punt on this broader question,
|
|
||||||
if users want to use the ACL rope, it's over there..
|
|
||||||
"""]]
|
"""]]
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
[[!comment format=mdwn
|
||||||
|
username="joey"
|
||||||
|
subject="""comment 12"""
|
||||||
|
date="2021-08-27T17:13:54Z"
|
||||||
|
content="""
|
||||||
|
I've also made git-annex add check, after removing write bits,
|
||||||
|
if the file still has write bits set. It will refuse to add a file
|
||||||
|
when it can't lock it down.
|
||||||
|
|
||||||
|
That should avoid the NFS xattr problem in a situation where
|
||||||
|
cp -a was used to make a copy that then gets added to the annex.
|
||||||
|
"""]]
|
Loading…
Reference in a new issue