The birth-time (btime) write in writeMetadataToFile followed symlinks for any object that was not a translated link, so under -l/--links a symlink planted by an untrusted source at the destination path could redirect the btime write to a target outside the backup destination on OSes where birth time is settable (Windows). Use the NOFOLLOW birth-time write whenever translating symlinks, not only for translated links. It is a no-op on a real file or directory and stops a planted symlink from being followed out of the destination.
172 lines
4.2 KiB
Go
172 lines
4.2 KiB
Go
package local
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
)
|
|
|
|
const metadataTimeFormat = time.RFC3339Nano
|
|
|
|
// system metadata keys which this backend owns
|
|
//
|
|
// not all values supported on all OSes
|
|
var systemMetadataInfo = map[string]fs.MetadataHelp{
|
|
"mode": {
|
|
Help: "File type and mode",
|
|
Type: "octal, unix style",
|
|
Example: "0100664",
|
|
},
|
|
"uid": {
|
|
Help: "User ID of owner",
|
|
Type: "decimal number",
|
|
Example: "500",
|
|
},
|
|
"gid": {
|
|
Help: "Group ID of owner",
|
|
Type: "decimal number",
|
|
Example: "500",
|
|
},
|
|
"rdev": {
|
|
Help: "Device ID (if special file)",
|
|
Type: "hexadecimal",
|
|
Example: "1abc",
|
|
},
|
|
"atime": {
|
|
Help: "Time of last access",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
"mtime": {
|
|
Help: "Time of last modification",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
"btime": {
|
|
Help: "Time of file birth (creation)",
|
|
Type: "RFC 3339",
|
|
Example: "2006-01-02T15:04:05.999999999Z07:00",
|
|
},
|
|
}
|
|
|
|
// parse a time string from metadata with key
|
|
func (o *Object) parseMetadataTime(m fs.Metadata, key string) (t time.Time, ok bool) {
|
|
value, ok := m[key]
|
|
if ok {
|
|
var err error
|
|
t, err = time.Parse(metadataTimeFormat, value)
|
|
if err != nil {
|
|
fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
|
|
ok = false
|
|
}
|
|
}
|
|
return t, ok
|
|
}
|
|
|
|
// parse am int from metadata with key and base
|
|
func (o *Object) parseMetadataInt(m fs.Metadata, key string, base int) (result int, ok bool) {
|
|
value, ok := m[key]
|
|
if ok {
|
|
var err error
|
|
parsed, err := strconv.ParseInt(value, base, 0)
|
|
if err != nil {
|
|
fs.Debugf(o, "failed to parse metadata %s: %q: %v", key, value, err)
|
|
ok = false
|
|
}
|
|
result = int(parsed)
|
|
}
|
|
return result, ok
|
|
}
|
|
|
|
// Write the metadata into the file
|
|
//
|
|
// It isn't possible to set the ctime and btime under Unix
|
|
func (o *Object) writeMetadataToFile(m fs.Metadata) (outErr error) {
|
|
var err error
|
|
atime, atimeOK := o.parseMetadataTime(m, "atime")
|
|
mtime, mtimeOK := o.parseMetadataTime(m, "mtime")
|
|
btime, btimeOK := o.parseMetadataTime(m, "btime")
|
|
if atimeOK || mtimeOK {
|
|
if atimeOK && !mtimeOK {
|
|
mtime = atime
|
|
}
|
|
if !atimeOK && mtimeOK {
|
|
atime = mtime
|
|
}
|
|
err = o.setTimes(atime, mtime)
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to set times: %w", err)
|
|
}
|
|
}
|
|
if haveSetBTime {
|
|
if btimeOK {
|
|
// When translating symlinks, never follow the path. A planted symlink must
|
|
// not redirect the birth-time write out of the root. The NOFOLLOW open is a
|
|
// no-op on a real file or directory
|
|
if o.translatedLink || o.fs.opt.TranslateSymlinks {
|
|
err = lsetBTime(o.path, btime)
|
|
} else {
|
|
err = setBTime(o.path, btime)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to set birth (creation) time: %w", err)
|
|
}
|
|
}
|
|
}
|
|
uid, hasUID := o.parseMetadataInt(m, "uid", 10)
|
|
gid, hasGID := o.parseMetadataInt(m, "gid", 10)
|
|
if hasUID {
|
|
// FIXME should read UID and GID of current user and only attempt to set it if different
|
|
if !hasGID {
|
|
gid = uid
|
|
}
|
|
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
|
fs.Debugf(o, "Ignoring request to set ownership %o.%o on this OS", gid, uid)
|
|
} else {
|
|
if o.translatedLink {
|
|
err = os.Lchown(o.path, uid, gid)
|
|
} else {
|
|
err = o.fs.chown(o.path, uid, gid)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to change ownership: %w", err)
|
|
}
|
|
}
|
|
}
|
|
mode, hasMode := o.parseMetadataInt(m, "mode", 8)
|
|
if hasMode {
|
|
if mode >= 0 {
|
|
umode := uint(mode)
|
|
if umode <= math.MaxUint32 {
|
|
fileMode := os.FileMode(umode)
|
|
// fileMode comes from the source, which may be untrusted, so
|
|
// by default apply only the permission bits and strip the
|
|
// setuid, setgid and sticky bits.
|
|
if !o.fs.opt.MetadataRestoreSpecial {
|
|
fileMode = fileMode.Perm()
|
|
}
|
|
if o.translatedLink {
|
|
if haveLChmod {
|
|
err = lChmod(o.path, fileMode)
|
|
} else {
|
|
fs.Debugf(o, "Unable to set mode %v on a symlink on this OS", fileMode)
|
|
err = nil
|
|
}
|
|
} else {
|
|
err = o.fs.chmod(o.path, fileMode)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to change permissions: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// FIXME not parsing rdev yet
|
|
return outErr
|
|
}
|