Compare commits

..

1 Commits

Author SHA1 Message Date
Jakob Borg
1f09488a0f lib/model, lib/versioner: Prevent symlink attack via versioning (fixes #4286)
Prior to this, the following is possible:

- Create a symlink "foo -> /somewhere", it gets synced
- Delete "foo", it gets versioned
- Create "foo/bar", it gets synced
- Delete "foo/bar", it gets versioned in "/somewhere/bar"

With this change, versioners should never version symlinks.
2017-08-08 08:02:33 +02:00
8 changed files with 37 additions and 67 deletions

View File

@@ -394,7 +394,7 @@
</tr>
<tr>
<th><span class="fa fa-fw fa-share-alt"></span>&nbsp;<span translate>Shared With</span></th>
<td class="text-right" title="{{sharesFolder(folder)}}">{{sharesFolder(folder)}}</td>
<td class="text-right">{{sharesFolder(folder)}}</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-clock-o"></span>&nbsp;<span translate>Last Scan</span></th>
@@ -645,7 +645,7 @@
</tr>
<tr ng-if="deviceFolders(deviceCfg).length > 0">
<th><span class="fa fa-fw fa-folder"></span>&nbsp;<span translate>Folders</span></th>
<td class="text-right" title="{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}">{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}</td>
<td class="text-right">{{deviceFolders(deviceCfg).map(folderLabel).join(", ")}}</td>
</tr>
</tbody>
</table>

View File

@@ -17,8 +17,6 @@ import (
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
@@ -31,7 +29,7 @@ import (
const (
OldestHandledVersion = 10
CurrentVersion = 21
CurrentVersion = 20
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -316,9 +314,6 @@ func (cfg *Configuration) clean() error {
if cfg.Version == 19 {
convertV19V20(cfg)
}
if cfg.Version == 20 {
convertV20V21(cfg)
}
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool)
@@ -368,30 +363,6 @@ func (cfg *Configuration) clean() error {
return nil
}
func convertV20V21(cfg *Configuration) {
for _, folder := range cfg.Folders {
switch folder.Versioning.Type {
case "simple", "trashcan":
// Clean out symlinks in the known place
cleanSymlinks(filepath.Join(folder.Path(), ".stversions"))
case "staggered":
versionDir := folder.Versioning.Params["versionsPath"]
if versionDir == "" {
// default place
cleanSymlinks(filepath.Join(folder.Path(), ".stversions"))
} else if filepath.IsAbs(versionDir) {
// absolute
cleanSymlinks(versionDir)
} else {
// relative to folder
cleanSymlinks(filepath.Join(folder.Path(), versionDir))
}
}
}
cfg.Version = 21
}
func convertV19V20(cfg *Configuration) {
cfg.Options.MinHomeDiskFree = Size{Value: cfg.Options.DeprecatedMinHomeDiskFreePct, Unit: "%"}
cfg.Options.DeprecatedMinHomeDiskFreePct = 0
@@ -669,23 +640,3 @@ loop:
}
return devices[0:count]
}
func cleanSymlinks(dir string) {
if runtime.GOOS == "windows" {
// We don't do symlinks on Windows. Additionally, there may
// be things that look like symlinks that are not, which we
// should leave alone. Deduplicated files, for example.
return
}
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
l.Infoln("Removing incorrectly versioned symlink", path)
os.Remove(path)
return filepath.SkipDir
}
return nil
})
}

View File

@@ -1,15 +0,0 @@
<configuration version="21">
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFree unit="%">1</minDiskFree>
<maxConflicts>-1</maxConflicts>
<fsync>true</fsync>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>

View File

@@ -27,6 +27,8 @@ type External struct {
}
func NewExternal(folderID, folderPath string, params map[string]string) Versioner {
cleanSymlinks(folderPath)
command := params["command"]
s := External{

View File

@@ -26,6 +26,8 @@ type Simple struct {
}
func NewSimple(folderID, folderPath string, params map[string]string) Versioner {
cleanSymlinks(folderPath)
keep, err := strconv.Atoi(params["keep"])
if err != nil {
keep = 5 // A reasonable default

View File

@@ -39,6 +39,8 @@ type Staggered struct {
}
func NewStaggered(folderID, folderPath string, params map[string]string) Versioner {
cleanSymlinks(folderPath)
maxAge, err := strconv.ParseInt(params["maxAge"], 10, 0)
if err != nil {
maxAge = 31536000 // Default: ~1 year

View File

@@ -28,6 +28,8 @@ type Trashcan struct {
}
func NewTrashcan(folderID, folderPath string, params map[string]string) Versioner {
cleanSymlinks(folderPath)
cleanoutDays, _ := strconv.Atoi(params["cleanoutDays"])
// On error we default to 0, "do not clean out the trash can"

View File

@@ -8,6 +8,12 @@
// simple default versioning scheme.
package versioner
import (
"os"
"path/filepath"
"runtime"
)
type Versioner interface {
Archive(filePath string) error
}
@@ -18,3 +24,23 @@ const (
TimeFormat = "20060102-150405"
TimeGlob = "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]" // glob pattern matching TimeFormat
)
func cleanSymlinks(dir string) {
if runtime.GOOS == "windows" {
// We don't do symlinks on Windows. Additionally, there may
// be things that look like symlinks that are not, which we
// should leave alone. Deduplicated files, for example.
return
}
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
l.Infoln("Removing incorrectly versioned symlink", path)
os.Remove(path)
return filepath.SkipDir
}
return nil
})
}