GHSA-945v-v9p3-v5xw: Low severity go/github.com/rclone/rclone vulnerability
Summary When writing an object with metadata, the local backend applies the source-supplied mode, uid, and gid verbatim: it parses mode as an octal integer and passes it straight into os.Chmod(o.path, os.FileMode(umode)), and passes uid/gid straight into os.Chown. The value is never masked to permission bits, so any value with Go's ModeSetuid (1<<23) or ModeSetgid (1<<22) bit set causes the setuid/setgid bit to be applied. Because both the file content and its metadata come from the (attacker-controlled) source remote, an attacker stores a binary of their choosing with mode = 40000755 (and uid = 0); when the victim runs rclone copy -M <remote>: /dest, rclone writes the attacker's binary and makes it setuid. If the victim runs rclone as root (typical for system backup/restore), the uid=0 chown plus setuid produces a root-owned setuid binary with attacker content — any local user then escalates to root. When rclone runs as a non-root service user, the planted setuid binary is owned by that user, giving any local user that user's privileges (lateral escalation / persistent backdoor).
Details backend/local/metadata.go, writeMetadataToFile(): go uid, hasUID := o.parseMetadataInt(m, "uid", 10) gid, hasGID := o.parseMetadataInt(m, "gid", 10) if hasUID { ... err = os.Chown(o.path, uid, gid) // source-controlled owner, no same-uid guard } mode, hasMode := o.parseMetadataInt(m, "mode", 8) if hasMode && mode >= 0 { umode := uint(mode) if umode <= math.MaxUint32 { err = os.Chmod(o.path, os.FileMode(umode)) // <-- raw value; ModeSetuid/ModeSetgid NOT masked off } } os.Chmod/os.FileMode honor ModeSetuid/ModeSetgid/ModeSticky. There is no &^ (os.ModeSetuid|os.ModeSetgid) mask and no check that the source is trusted, so an attacker-chosen mode string sets those bits on the freshly written, attacker-controlled file. (Note: a legitimate local source reports mode in unix stmode layout e.g. 0106755, whose bit 1<<23 is unset, so honest copies happen to drop setuid — but the attacker supplies the Go-FileMode layout 40000755 directly, which sets it.)
PoC 1) Get the official stable binary: curl -fsSLO https://downloads.rclone.org/v1.74.3/rclone-v1.74.3-linux-amd64.zip unzip -j rclone-v1.74.3-linux-amd64.zip '/rclone' -d . # ./rclone -> v1.74.3 2) Create a payload (the attacker-controlled binary content) and copy it with the malicious mode metadata: mkdir -p msrc mdst && cp /bin/true msrc/payload ./rclone copy -M --metadata-set mode=40000755 msrc mdst # 40000755 = Go FileMode setuid|0755 3) Observe — the destination file is now setuid: stat -c '%A %a' mdst/payload -rwsr-xr-x 4755 # 's' = setuid bit SET on attacker binary Variants: mode=20000755 → setgid (-rwxr-sr-x); mode=60000755 → both (-rwsr-sr-x). With rclone run as root and the source object also carrying uid=0/gid=0, the file is chown'd root:root, yielding a root-owned setuid binary executable by any local user.
Impact A victim performing a metadata-preserving copy/restore (-M) from an untrusted or compromised remote installs an attacker-chosen executable with the setuid/setgid bit set. Run as root (system backup/restore, the common case for --metadata), this is a root-owned setuid root backdoor executable by any local user → local privilege escalation to root. Run as a non-root user, it is a setuid backdoor for that service account. The companion uid/gid application lets a root-run transfer also reassign ownership of written files arbitrarily.
Remediation Mask special bits before applying mode from metadata — os.Chmod(o.path, os.FileMode(umode).Perm()) (or umode & 0o777) — and do not honor setuid/setgid/sticky from source metadata; gate uid/gid/setuid application behind an explicit opt-in (e.g. --local-metadata-set-ownership) that is off by default, and document that -M from untrusted remotes must not restore privileged bits. Regression test: copying an object with mode=40000755/uid=0 must produce a non-setuid, caller-owned file unless the opt-in is set.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/rclone/rcloneto a version that resolves this vulnerability.Fixed in 1.74.4 - Upgrade
Upgrade
rcloneto a version that resolves this vulnerability.Fixed in v1.74.3 - Configuration
Modify writeMetadataToFile() so that when applying source-controlled metadata, rclone masks the parsed metadata mode to only permission bits before calling os.Chmod(o.path,...). Specifically, change raw os.Chmod(o.path, os.FileMode(umode)) to os.Chmod(o.path, os.FileMode(umode).Perm()) or to os.Chmod(o.path, os.FileMode(umode & 0o777)), so ModeSetuid/ModeSetgid/ModeSticky are not honored from untrusted metadata.
rclone backend/local metadata (backend/local/metadata.go, writeMetadataToFile) metadata mode handling (mask special bits) = umode & 0o777 (or os.FileMode(umode).Perm()) - Configuration
Do not apply source-controlled uid/gid verbatim from metadata. Gate uid/gid (and any setuid/setgid behavior) behind an explicit opt-in (e.g., only honor uid/gid/setuid bits when --local-metadata-set-ownership is enabled, and keep it off by default as described).
rclone backend/local metadata (backend/local/metadata.go, writeMetadataToFile) metadata owner (uid/gid) application = disable unless explicit opt-in - Configuration
Ensure --local-metadata-set-ownership is kept off by default and document/ensure that using -M (metadata-preserving copy/restore) from untrusted remotes does not restore privileged bits like setuid/setgid or apply attacker-supplied ownership.
rclone CLI / local metadata options --local-metadata-set-ownership = off by default - Compensating control
Run rclone restores/copies that use metadata (-M / --metadata) as a non-root user when possible, because if run as root the setuid/setgid plus uid=0/gid=0 metadata can plant a root-owned setuid backdoor for local privilege escalation.