GHSA-P6M2-R3W9-MPXW
Vulnerability from github – Published: 2026-09-10 22:49 – Updated: 2026-09-10 22:49Summary
When backend/local is used with --links/-l (or the links=true config option), each symlink is exposed as an rclone object whose content is the target path string, suffixed .rclonelink. Object.Open() decodes an incoming fs.RangeOption via Decode(o.Size()), then for a translated-symlink object passes the decoded offset straight into openTranslatedLink, which indexes the target string directly: linkdst[offset:].
RangeOption.Decode's Start >= 0 branch (an ordinary Range: bytes=X- request) sets offset = o.Start with no upper bound, unlike its suffix-range branch (Start < 0, e.g. bytes=-N), which already clamps a too-large value to 0 - the fix for a prior, related crash (issue #6310: "bytes=-90407" against a 5-byte object panicked with "slice bounds out of range", now covered by an existing regression test). The Start >= 0 branch never received the analogous protection.
A Range: bytes=<hugeStart>- request sent to rclone serve http/webdav (or any consumer of lib/http/serve's Object(), which parses and decodes the client's own Range header) against a directory containing a symlink therefore reaches linkdst[offset:] with offset far beyond the target string's length, and Go panics with "slice bounds out of range" instead of returning an empty read.
Details
Vulnerable code (before fix):
func (o *Object) openTranslatedLink(offset, limit int64) (lrc io.ReadCloser, err error) {
linkdst, err := os.Readlink(o.path)
if err != nil { return nil, err }
return readers.NewLimitedReadCloser(io.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil
}
PoC
Called the real production Object.Open() on a translated-symlink object (target length 12) with &fs.RangeOption{Start: math.MaxInt64, End: -1}:
panic: runtime error: slice bounds out of range [9223372036854775807:8]
...backend/local.(*Object).openTranslatedLink
...backend/local.(*Object).Open
Impact
A remote client can send a single crafted Range header against any symlink-backed object exposed by rclone serve http/webdav/etc (backed by backend/local with --links enabled) to deterministically panic the request-handling goroutine. Go's net/http recovers panics per-connection by default, so this fails the one request/connection rather than crashing the whole server process, and no file handle is left open (the panic occurs before any read handle is acquired) - but it is fully deterministic and remotely triggerable with no authentication or race window needed, unlike some other panic-recovery findings.
Fix
Clamp offset to the length of the target string before slicing, matching how a real file read past EOF behaves (an empty read):
if offset > int64(len(linkdst)) {
offset = int64(len(linkdst))
}
Note: the shared RangeOption.Decode() also has a related, unaddressed issue - limit = o.End - o.Start + 1 can itself overflow to a large negative number for a huge End - but a fix attempted there during this investigation broke fs/operations/reopen.go's NewReOpen, which calls Decode with its h.end field still at its zero value at that point in construction. Flagged for awareness but not changed here to keep this patch minimal and low-risk.
{
"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-88015"
],
"database_specific": {
"cwe_ids": [
"CWE-190",
"CWE-248"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-10T22:49:27Z",
"nvd_published_at": "2026-09-10T16:18:08Z",
"severity": "MODERATE"
},
"details": "### Summary\nWhen `backend/local` is used with `--links`/`-l` (or the `links=true` config option), each symlink is exposed as an rclone object whose content is the target path string, suffixed `.rclonelink`. `Object.Open()` decodes an incoming `fs.RangeOption` via `Decode(o.Size())`, then for a translated-symlink object passes the decoded `offset` straight into `openTranslatedLink`, which indexes the target string directly: `linkdst[offset:]`.\n\n`RangeOption.Decode`\u0027s `Start \u003e= 0` branch (an ordinary `Range: bytes=X-` request) sets `offset = o.Start` with no upper bound, unlike its suffix-range branch (`Start \u003c 0`, e.g. `bytes=-N`), which already clamps a too-large value to 0 - the fix for a prior, related crash (issue #6310: \"bytes=-90407\" against a 5-byte object panicked with \"slice bounds out of range\", now covered by an existing regression test). The `Start \u003e= 0` branch never received the analogous protection.\n\nA `Range: bytes=\u003chugeStart\u003e-` request sent to `rclone serve http`/`webdav` (or any consumer of `lib/http/serve`\u0027s `Object()`, which parses and decodes the client\u0027s own Range header) against a directory containing a symlink therefore reaches `linkdst[offset:]` with offset far beyond the target string\u0027s length, and Go panics with \"slice bounds out of range\" instead of returning an empty read.\n\n### Details\nVulnerable code (before fix):\n```go\nfunc (o *Object) openTranslatedLink(offset, limit int64) (lrc io.ReadCloser, err error) {\n\tlinkdst, err := os.Readlink(o.path)\n\tif err != nil { return nil, err }\n\treturn readers.NewLimitedReadCloser(io.NopCloser(strings.NewReader(linkdst[offset:])), limit), nil\n}\n```\n\n### PoC\nCalled the real production `Object.Open()` on a translated-symlink object (target length 12) with `\u0026fs.RangeOption{Start: math.MaxInt64, End: -1}`:\n```\npanic: runtime error: slice bounds out of range [9223372036854775807:8]\n ...backend/local.(*Object).openTranslatedLink\n ...backend/local.(*Object).Open\n```\n\n### Impact\nA remote client can send a single crafted `Range` header against any symlink-backed object exposed by `rclone serve http`/`webdav`/etc (backed by `backend/local` with `--links` enabled) to deterministically panic the request-handling goroutine. Go\u0027s `net/http` recovers panics per-connection by default, so this fails the one request/connection rather than crashing the whole server process, and no file handle is left open (the panic occurs before any read handle is acquired) - but it is fully deterministic and remotely triggerable with no authentication or race window needed, unlike some other panic-recovery findings.\n\n### Fix\nClamp `offset` to the length of the target string before slicing, matching how a real file read past EOF behaves (an empty read):\n```go\nif offset \u003e int64(len(linkdst)) {\n\toffset = int64(len(linkdst))\n}\n```\nNote: the shared `RangeOption.Decode()` also has a related, unaddressed issue - `limit = o.End - o.Start + 1` can itself overflow to a large negative number for a huge `End` - but a fix attempted there during this investigation broke `fs/operations/reopen.go`\u0027s `NewReOpen`, which calls `Decode` with its `h.end` field still at its zero value at that point in construction. Flagged for awareness but not changed here to keep this patch minimal and low-risk.",
"id": "GHSA-p6m2-r3w9-mpxw",
"modified": "2026-09-10T22:49:27Z",
"published": "2026-09-10T22:49:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/rclone/rclone/security/advisories/GHSA-p6m2-r3w9-mpxw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-88015"
},
{
"type": "WEB",
"url": "https://github.com/rclone/rclone/commit/28bf49d66f94acc3f4f7f318504a706686281af9"
},
{
"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:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "rclone local: crafted Range request against a translated symlink panics (DoS)"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.