This makes sure the user running Syncthing, and hence Synchting itself, has read/write/execute on directories in .stversions. The other permission bits remain copied from the source directory, ensuring whatever group and other permissions were set remain in effect. Closes #10695. --------- Signed-off-by: Jakob Borg <jakob@kastelo.net>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/build"
|
"github.com/syncthing/syncthing/lib/build"
|
||||||
"github.com/syncthing/syncthing/lib/config"
|
"github.com/syncthing/syncthing/lib/config"
|
||||||
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTaggedFilename(t *testing.T) {
|
func TestTaggedFilename(t *testing.T) {
|
||||||
@@ -256,3 +257,57 @@ func TestArchiveFoldersCreationPermission(t *testing.T) {
|
|||||||
t.Errorf("földer2 permissions %v, want %v", folder2VersionsInfo.Mode(), folder2Perms)
|
t.Errorf("földer2 permissions %v, want %v", folder2VersionsInfo.Mode(), folder2Perms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDupDirTreeWritePermissions(t *testing.T) {
|
||||||
|
// The structure should be replicated, with user permission bits set along the way
|
||||||
|
|
||||||
|
srcFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirTreeWritePermissions/srcFs")
|
||||||
|
dstFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirTreeWritePermissions/dstFs")
|
||||||
|
|
||||||
|
// A source dir to duplicate
|
||||||
|
_ = srcFs.Mkdir("foo", 0o444)
|
||||||
|
_ = srcFs.Mkdir("foo/bar", 0o555)
|
||||||
|
_ = srcFs.Mkdir("foo/bar/baz", 0o000)
|
||||||
|
|
||||||
|
// Duplication should succeed
|
||||||
|
if err := dupDirTree(srcFs, dstFs, "foo/bar/baz"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions should be the same, but with read/write/execute bits for
|
||||||
|
// the user
|
||||||
|
if info, err := dstFs.Lstat("foo"); err != nil || info.Mode() != 0o744 {
|
||||||
|
t.Fatalf("foo: 0o%o", info.Mode())
|
||||||
|
}
|
||||||
|
if info, err := dstFs.Lstat("foo/bar"); err != nil || info.Mode() != 0o755 {
|
||||||
|
t.Fatalf("foo/bar: 0o%o", info.Mode())
|
||||||
|
}
|
||||||
|
if info, err := dstFs.Lstat("foo/bar/baz"); err != nil || info.Mode() != 0o700 {
|
||||||
|
t.Fatalf("foo/bar/baz: 0o%o", info.Mode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDupDirFastPath(t *testing.T) {
|
||||||
|
srcFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirFastPath/srcFs")
|
||||||
|
dstFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirFastPath/dstFs")
|
||||||
|
|
||||||
|
// A source dir to duplicate
|
||||||
|
_ = srcFs.Mkdir("foo", 0o444)
|
||||||
|
_ = srcFs.Mkdir("foo/bar", 0o555)
|
||||||
|
_ = srcFs.Mkdir("foo/bar/baz", 0o000)
|
||||||
|
|
||||||
|
// The destination exists, but with too few permission bits
|
||||||
|
_ = dstFs.MkdirAll("foo/bar/baz", 0o555)
|
||||||
|
|
||||||
|
// Duplication should succeed
|
||||||
|
if err := dupDirTree(srcFs, dstFs, "foo/bar/baz"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions for the destination should have been updated. (This
|
||||||
|
// differs from what would have been created by the duplication of the
|
||||||
|
// 0o000 dir in the src, because it already existed.)
|
||||||
|
if info, err := dstFs.Lstat("foo/bar/baz"); err != nil || info.Mode() != 0o755 {
|
||||||
|
t.Fatalf("foo/bar/baz: 0o%o", info.Mode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+66
-36
@@ -192,48 +192,78 @@ func archiveFile(method fs.CopyRangeMethod, srcFs, dstFs fs.Filesystem, filePath
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func dupDirTree(srcFs, dstFs fs.Filesystem, folderPath string) error {
|
// dupDirTree ensures folderPath exists in dstFs, copying permissions mostly
|
||||||
// Return early if the folder already exists.
|
// from srcFs. Permissions are altered to have the user read, write, and
|
||||||
_, err := dstFs.Stat(folderPath)
|
// execute bits set so that Syncthing file operations are possible within
|
||||||
if err == nil || !fs.IsNotExist(err) {
|
// the destination directory.
|
||||||
return err
|
//
|
||||||
|
// We want to retain the source group and other bits so that we do not
|
||||||
|
// inadvertently open up a directory for users who shouldn't have access to
|
||||||
|
// it, but we do not consider it a security issue to open up the permissions
|
||||||
|
// for the current user.
|
||||||
|
//
|
||||||
|
// This is based on os.MkdirAll with our srcFs adjustments.
|
||||||
|
func dupDirTree(srcFs, dstFs fs.Filesystem, path string) error {
|
||||||
|
const (
|
||||||
|
allPerms = 0o777
|
||||||
|
minDirPerms = 0o700
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||||
|
if dir, err := dstFs.Lstat(path); err == nil {
|
||||||
|
if !dir.IsDir() {
|
||||||
|
return errors.New("destination exists but is not a directory")
|
||||||
|
}
|
||||||
|
if dir.Mode()&minDirPerms != minDirPerms {
|
||||||
|
// We want all the required permission bits set
|
||||||
|
_ = dstFs.Chmod(path, dir.Mode()&allPerms|minDirPerms)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
hadParent := true
|
|
||||||
for i := range folderPath {
|
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||||
if os.IsPathSeparator(folderPath[i]) {
|
|
||||||
// If the parent folder didn't exist, then this folder doesn't exist
|
// Extract the parent folder from path by first removing any trailing
|
||||||
// so we can skip the check
|
// path separator and then scanning backward until finding a path
|
||||||
if hadParent {
|
// separator or reaching the beginning of the string.
|
||||||
_, err := dstFs.Stat(folderPath[:i])
|
i := len(path) - 1
|
||||||
if err == nil {
|
for i >= 0 && os.IsPathSeparator(path[i]) {
|
||||||
continue
|
i--
|
||||||
}
|
}
|
||||||
if !fs.IsNotExist(err) {
|
for i >= 0 && !os.IsPathSeparator(path[i]) {
|
||||||
return err
|
i--
|
||||||
}
|
}
|
||||||
}
|
if i < 0 {
|
||||||
hadParent = false
|
i = 0
|
||||||
err := dupDirWithPerms(srcFs, dstFs, folderPath[:i])
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
// If there is a parent directory, and it is not the volume name,
|
||||||
}
|
// recurse to ensure parent directory exists.
|
||||||
|
if parent := path[:i]; len(parent) > len(filepath.VolumeName(path)) {
|
||||||
|
if err := dupDirTree(srcFs, dstFs, parent); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dupDirWithPerms(srcFs, dstFs, folderPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dupDirWithPerms(srcFs, dstFs fs.Filesystem, folderPath string) error {
|
// Parent now exists; invoke Mkdir and use its result.
|
||||||
srcStat, err := srcFs.Stat(folderPath)
|
srcPerms := fs.FileMode(minDirPerms)
|
||||||
if err != nil {
|
if srcDir, err := srcFs.Lstat(path); err == nil {
|
||||||
|
srcPerms = srcDir.Mode()&allPerms | minDirPerms
|
||||||
|
}
|
||||||
|
if err := dstFs.Mkdir(path, srcPerms); err != nil {
|
||||||
|
// Handle arguments like "foo/." by
|
||||||
|
// double-checking that directory doesn't exist.
|
||||||
|
dir, err1 := dstFs.Lstat(path)
|
||||||
|
if err1 == nil && dir.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If we call Mkdir with srcStat.Mode(), we won't get the expected perms because of umask
|
|
||||||
// So, we create the folder with 0700, and then change the perms to the srcStat.Mode()
|
// Extra chmod to ensure our permissions override umask
|
||||||
err = dstFs.Mkdir(folderPath, 0o700)
|
_ = dstFs.Chmod(path, srcPerms)
|
||||||
if err != nil {
|
|
||||||
return err
|
return nil
|
||||||
}
|
|
||||||
return dstFs.Chmod(folderPath, srcStat.Mode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreFile(method fs.CopyRangeMethod, src, dst fs.Filesystem, filePath string, versionTime time.Time, tagger fileTagger) error {
|
func restoreFile(method fs.CopyRangeMethod, src, dst fs.Filesystem, filePath string, versionTime time.Time, tagger fileTagger) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user