GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-F8G7-2XJC-7MFH

Vulnerability from github – Published: 2026-09-10 22:51 – Updated: 2026-09-10 22:51
VLAI
Summary
rclone: Directory metadata (chmod/chown/chtimes) applied through a planted symlink in rclone local --links escapes the destination
Details

Summary

With -l/--links, rclone's local backend recreates a source .rclonelink object as a real symlink at the destination verbatim (preserved by design for faithful backups). Directory-metadata application, however, does not go through the os.Root sandbox and does not use NOFOLLOW syscalls. A local Directory always has translatedLink=false, so when the destination path already exists as a planted symlink, rclone applies chmod/chown/chtimes through that symlink to a target outside the destination tree. An attacker who controls the source contents (malicious/compromised remote, shared bucket) obtains attacker-valued chmod/chown/chtimes of an arbitrary path outside the backup destination.

Root Cause

  • MkdirMetadata (backend/local/local.go:895) calls f.lstat (=os.Lstat, local.go:465) on the destination path. On a pre-planted symlink, os.Lstat succeeds, so the errors.Is(err, os.ErrNotExist) branch (local.go:896) that would create a real directory via the os.Root-guarded f.Mkdir is not taken. Instead a Directory is built directly on the symlink path.
  • writeMetadataToFile runs raw os.Chown (backend/local/metadata.go:131) and os.Chmod (metadata.go:158); setTimes runs raw os.Chtimes (backend/local/local.go:1318).
  • The CVE-2024-52522 NOFOLLOW fix (os.Lchown/lChmod/lChtimes) is gated on if o.translatedLink (metadata.go:128/150, local.go:1315). A Directory (newDirectorynewObject with no .rclonelink suffix) is never translatedLink, so it always takes the raw following branch. The CVE-2026-54572 os.Root fix covers only content writes, not metadata syscalls.

Impact

Attacker-controlled chmod/chown/chtimes (values taken from the source directory's mode/uid/gid/mtime) applied to any file or directory outside the destination. chtimes (mtime) escape works with just --links and default flags; chmod/chown escape additionally needs --metadata. When rclone runs as root with --metadata and a source uid=0, the chown primitive reaches the CVE-2024-52522 privilege-escalation ceiling (take ownership of an out-of-tree path).

Proof of Concept

mkdir -p /src /dest
# run 1: source object pwn.rclonelink whose body = /home/victim/secret.d
printf '/home/victim/secret.d' > /src/pwn.rclonelink
rclone sync --links /src /dest              # plants /dest/pwn -> /home/victim/secret.d
# attacker swaps source pwn to a real directory with chosen metadata:
rm /src/pwn.rclonelink ; mkdir -p /src/pwn/keep ; chmod 777 /src/pwn
rclone sync --links --metadata /src /dest   # MkdirMetadata sees /dest/pwn exists (symlink) ->
                                            # chmod 0777 applied THROUGH it to /home/victim/secret.d
ls -ld /home/victim/secret.d                # => drwxrwxrwx  (outside dir, attacker-chosen mode)

A single-run PoC is achievable against directory-based object sources (drive/onedrive-class) that satisfy both ReadDirMetadata and CanHaveEmptyDirectories and can present pwn.rclonelink and pwn/ simultaneously. Local→local uses the two-run backup model (same repeated-backup model as CVE-2024-52522 and CVE-2026-54572). Verified end-to-end against the real fs/sync.Sync engine on HEAD: the two-run backup backdated the outside target's mtime and chmod'd it 0777 while os.Root correctly blocked the content-copy of pwn/keep — isolating the metadata gap.

Attack Chain

  1. Entry. Victim runs rclone copy/sync --links [--metadata] <untrusted-remote>: /dest. Attacker controls source contents.
  2. Guard: none — --links copying an untrusted remote is a documented, supported operation.
  3. Plant symlink. Source serves pwn.rclonelink with body = absolute outside path; rclone recreates dst/pwn → outside.
  4. Guard: Fs.symlink routes creation through os.Root.Symlink (local.go:~1552).
  5. Bypass proof: os.Root creates the link verbatim by design (commit 1154afe); the upstream os.Root fix's test TestSymlinkEscapeWriteThroughBlocked confirms only write-through is refused, the link is planted.
  6. Deferred dir-metadata fires after transfers. Source presents non-empty dir pwn; setDelayedDirModTimes (sync.go:1002) runs strictly after stopTransfers() (sync.go:988) — after the symlink is planted.
  7. Guard: MkdirMetadata would create a real dir via os.Root-guarded f.Mkdir (local.go:897) inside its errors.Is(err, os.ErrNotExist) branch.
  8. Bypass proof: os.Lstat (local.go:465) on the existing symlink returns success, so the ErrNotExist branch (local.go:896) is NOT taken; f.Mkdir/os.Root never runs. Empirically os.IsNotExist(err)=false for the planted symlink.
  9. Sink follows the symlink. CopyDirMetadataMkdirMetadatawriteMetadataToFile runs os.Chown/os.Chmod (metadata.go:131/158); DirSetModTimesetTimes runs os.Chtimes (local.go:1318) — all on o.path="dst/pwn" with translatedLink=false.
  10. Guard: CVE-2024-52522 NOFOLLOW branch (os.Lchown/lChmod/lChtimes).
  11. Bypass proof: that branch is gated on if o.translatedLink (metadata.go:128/150, local.go:1315); a Directory always has translatedLink=false, so the raw following branch runs. POSIX-confirmed: chmod 777/touch on a symlink path change the target's mode/mtime.
  12. Impact. chmod/chown/chtimes on an attacker-chosen path outside the destination, with attacker-controlled values.

Bypass Evidence

  • if o.translatedLink gates verified verbatim on v1.75.0 at metadata.go:128/150 and local.go:1315; os.Chown/os.Chmod/os.Chtimes on the else branch at metadata.go:131/158 and local.go:1318.
  • newDirectorynewObject (local.go:581/589/596) never sets the .rclonelink suffix → translatedLink=false for all directories.
  • MkdirMetadata skip branch: os.Lstat succeeds on planted symlink → errors.Is(err, os.ErrNotExist) false at local.go:896 → guarded f.Mkdir skipped.
  • Real fs/sync.Sync E2E on HEAD: TestDirMetadataThroughPlantedSymlink (outside dir → 0777), TestDirSetModTimeThroughPlantedSymlink (mtime set, default-on), TestE2E_TwoRunBackup (backdated outside target while content-copy blocked by os.Root). All PASS. Control TestControl_ContentWriteBlocked confirms harness fidelity.

Affected Versions

<= 1.75.0. Vulnerable code present on latest release tag v1.75.0 and HEAD (5629f26); git log v1.75.0..HEAD -- backend/local/metadata.go backend/local/local.go is empty (no post-release fix).

Suggested Fix

Route directory metadata through os.Root when TranslateSymlinks is set (use fchmodat(AT_SYMLINK_NOFOLLOW)/Lchown/UtimesNanoAt(AT_SYMLINK_NOFOLLOW) on the rel path within the root), and/or extend MkdirMetadata to detect that the pre-existing destination path is a symlink and refuse to apply following-metadata — mirroring the CVE-2024-52522 NOFOLLOW branch that currently exists only for translatedLink objects.


Reported by zx (Jace) — GitHub: @manus-use

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.75.0"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/rclone/rclone"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.75.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-88016"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-59",
      "CWE-281"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-10T22:51:34Z",
    "nvd_published_at": "2026-09-10T16:18:08Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\nWith `-l/--links`, rclone\u0027s local backend recreates a source `.rclonelink` object as a real symlink at the destination **verbatim** (preserved by design for faithful backups). Directory-metadata application, however, does **not** go through the `os.Root` sandbox and does **not** use NOFOLLOW syscalls. A local `Directory` always has `translatedLink=false`, so when the destination path already exists as a planted symlink, rclone applies `chmod`/`chown`/`chtimes` **through** that symlink to a target **outside** the destination tree. An attacker who controls the source contents (malicious/compromised remote, shared bucket) obtains attacker-valued `chmod`/`chown`/`chtimes` of an arbitrary path outside the backup destination.\n\n## Root Cause\n- `MkdirMetadata` (`backend/local/local.go:895`) calls `f.lstat` (=`os.Lstat`, local.go:465) on the destination path. On a pre-planted symlink, `os.Lstat` succeeds, so the `errors.Is(err, os.ErrNotExist)` branch (local.go:896) that would create a real directory via the `os.Root`-guarded `f.Mkdir` is **not** taken. Instead a `Directory` is built directly on the symlink path.\n- `writeMetadataToFile` runs raw `os.Chown` (`backend/local/metadata.go:131`) and `os.Chmod` (`metadata.go:158`); `setTimes` runs raw `os.Chtimes` (`backend/local/local.go:1318`).\n- The CVE-2024-52522 NOFOLLOW fix (`os.Lchown`/`lChmod`/`lChtimes`) is gated on `if o.translatedLink` (metadata.go:128/150, local.go:1315). A `Directory` (`newDirectory`\u2192`newObject` with no `.rclonelink` suffix) is never `translatedLink`, so it always takes the raw *following* branch. The CVE-2026-54572 `os.Root` fix covers only content **writes**, not metadata syscalls.\n\n## Impact\nAttacker-controlled `chmod`/`chown`/`chtimes` (values taken from the source directory\u0027s mode/uid/gid/mtime) applied to any file or directory **outside** the destination. `chtimes` (mtime) escape works with just `--links` and default flags; `chmod`/`chown` escape additionally needs `--metadata`. When rclone runs as root with `--metadata` and a source `uid=0`, the `chown` primitive reaches the CVE-2024-52522 privilege-escalation ceiling (take ownership of an out-of-tree path).\n\n## Proof of Concept\n```\nmkdir -p /src /dest\n# run 1: source object pwn.rclonelink whose body = /home/victim/secret.d\nprintf \u0027/home/victim/secret.d\u0027 \u003e /src/pwn.rclonelink\nrclone sync --links /src /dest              # plants /dest/pwn -\u003e /home/victim/secret.d\n# attacker swaps source pwn to a real directory with chosen metadata:\nrm /src/pwn.rclonelink ; mkdir -p /src/pwn/keep ; chmod 777 /src/pwn\nrclone sync --links --metadata /src /dest   # MkdirMetadata sees /dest/pwn exists (symlink) -\u003e\n                                            # chmod 0777 applied THROUGH it to /home/victim/secret.d\nls -ld /home/victim/secret.d                # =\u003e drwxrwxrwx  (outside dir, attacker-chosen mode)\n```\nA single-run PoC is achievable against directory-based object sources (drive/onedrive-class) that satisfy both `ReadDirMetadata` and `CanHaveEmptyDirectories` and can present `pwn.rclonelink` and `pwn/` simultaneously. Local\u2192local uses the two-run backup model (same repeated-backup model as CVE-2024-52522 and CVE-2026-54572). Verified end-to-end against the real `fs/sync.Sync` engine on HEAD: the two-run backup backdated the outside target\u0027s mtime and chmod\u0027d it 0777 while os.Root correctly blocked the content-copy of `pwn/keep` \u2014 isolating the metadata gap.\n\n## Attack Chain\n1. **Entry.** Victim runs `rclone copy`/`sync --links [--metadata] \u003cuntrusted-remote\u003e: /dest`. Attacker controls source contents.\n   - Guard: none \u2014 `--links` copying an untrusted remote is a documented, supported operation.\n2. **Plant symlink.** Source serves `pwn.rclonelink` with body = absolute outside path; rclone recreates `dst/pwn` \u2192 outside.\n   - Guard: `Fs.symlink` routes creation through `os.Root.Symlink` (local.go:~1552).\n   - Bypass proof: `os.Root` creates the link **verbatim by design** (commit 1154afe); the upstream `os.Root` fix\u0027s test `TestSymlinkEscapeWriteThroughBlocked` confirms only write-*through* is refused, the link is planted.\n3. **Deferred dir-metadata fires after transfers.** Source presents non-empty dir `pwn`; `setDelayedDirModTimes` (sync.go:1002) runs strictly after `stopTransfers()` (sync.go:988) \u2014 after the symlink is planted.\n   - Guard: `MkdirMetadata` would create a real dir via os.Root-guarded `f.Mkdir` (local.go:897) inside its `errors.Is(err, os.ErrNotExist)` branch.\n   - Bypass proof: `os.Lstat` (local.go:465) on the existing symlink returns success, so the `ErrNotExist` branch (local.go:896) is NOT taken; `f.Mkdir`/os.Root never runs. Empirically `os.IsNotExist(err)=false` for the planted symlink.\n4. **Sink follows the symlink.** `CopyDirMetadata`\u2192`MkdirMetadata`\u2192`writeMetadataToFile` runs `os.Chown`/`os.Chmod` (metadata.go:131/158); `DirSetModTime`\u2192`setTimes` runs `os.Chtimes` (local.go:1318) \u2014 all on `o.path=\"dst/pwn\"` with `translatedLink=false`.\n   - Guard: CVE-2024-52522 NOFOLLOW branch (`os.Lchown`/`lChmod`/`lChtimes`).\n   - Bypass proof: that branch is gated on `if o.translatedLink` (metadata.go:128/150, local.go:1315); a `Directory` always has `translatedLink=false`, so the raw following branch runs. POSIX-confirmed: `chmod 777`/`touch` on a symlink path change the *target\u0027s* mode/mtime.\n5. **Impact.** `chmod`/`chown`/`chtimes` on an attacker-chosen path outside the destination, with attacker-controlled values.\n\n## Bypass Evidence\n- `if o.translatedLink` gates verified verbatim on v1.75.0 at metadata.go:128/150 and local.go:1315; `os.Chown`/`os.Chmod`/`os.Chtimes` on the else branch at metadata.go:131/158 and local.go:1318.\n- `newDirectory`\u2192`newObject` (local.go:581/589/596) never sets the `.rclonelink` suffix \u2192 `translatedLink=false` for all directories.\n- `MkdirMetadata` skip branch: `os.Lstat` succeeds on planted symlink \u2192 `errors.Is(err, os.ErrNotExist)` false at local.go:896 \u2192 guarded `f.Mkdir` skipped.\n- Real `fs/sync.Sync` E2E on HEAD: `TestDirMetadataThroughPlantedSymlink` (outside dir \u2192 0777), `TestDirSetModTimeThroughPlantedSymlink` (mtime set, default-on), `TestE2E_TwoRunBackup` (backdated outside target while content-copy blocked by os.Root). All PASS. Control `TestControl_ContentWriteBlocked` confirms harness fidelity.\n\n## Affected Versions\n`\u003c= 1.75.0`. Vulnerable code present on latest release tag v1.75.0 and HEAD (5629f26); `git log v1.75.0..HEAD -- backend/local/metadata.go backend/local/local.go` is empty (no post-release fix).\n\n## Suggested Fix\nRoute directory metadata through `os.Root` when `TranslateSymlinks` is set (use `fchmodat(AT_SYMLINK_NOFOLLOW)`/`Lchown`/`UtimesNanoAt(AT_SYMLINK_NOFOLLOW)` on the `rel` path within the root), and/or extend `MkdirMetadata` to detect that the pre-existing destination path is a symlink and refuse to apply following-metadata \u2014 mirroring the CVE-2024-52522 NOFOLLOW branch that currently exists only for `translatedLink` objects.\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
  "id": "GHSA-f8g7-2xjc-7mfh",
  "modified": "2026-09-10T22:51:34Z",
  "published": "2026-09-10T22:51:34Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/security/advisories/GHSA-f8g7-2xjc-7mfh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-88016"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/commit/17b0c03338a857bcb0a68d2d4c82ddbdec3f7893"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/commit/a7ab39d3d1958afa1446982c1dc4e4a73a887e3e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rclone/rclone"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rclone/rclone/releases/tag/v1.75.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "rclone: Directory metadata (chmod/chown/chtimes) applied through a planted symlink in rclone local --links escapes the destination"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…