When applying the "mode" from --metadata the local backend cast the source value straight to an os.FileMode, so a source that supplied a mode with Go's setuid, setgid or sticky bits set would have those bits applied to the freshly written file. As both the file content and its metadata come from the source remote, a malicious source could plant a setuid binary, and a victim running "rclone copy -M" as root against an untrusted remote could end up with a root-owned setuid binary with attacker-controlled content. Rclone records "mode" in the unix st_mode layout where the special bits live in different positions to Go's os.FileMode, so honest sources never actually round-tripped these bits in the first place. Apply only the permission bits by default, which closes this off and is backwards compatible, and add the --local-metadata-restore-special-bits lag to restore the previous behaviour for trusted sources such as restoring a system backup made by rclone. See: GHSA-945v-v9p3-v5xw
169 lines
4.0 KiB
Go
169 lines
4.0 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 {
|
|
if o.translatedLink {
|
|
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 = os.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 = os.Chmod(o.path, fileMode)
|
|
}
|
|
if err != nil {
|
|
outErr = fmt.Errorf("failed to change permissions: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// FIXME not parsing rdev yet
|
|
return outErr
|
|
}
|