Filed at the invitation of Andrew Bartlett, in reply to a hardening note I sent privately to security@samba.org on 2026-08-28. Tested against current master (python/samba/safe_tarfile.py as of 2026-09-09); the version field offers no "master", hence "unspecified". python/samba/safe_tarfile.py selects tarfile's intermediate 'tar' extraction filter, with the comment: # The 'data' filter preserves no permissions so we select the # intermediate 'tar' filter here which prevents escape but # preserves permissions. The 'tar' filter bounds member PATHS. It does not bound link TARGETS. In CPython's tarfile._get_filtered_attrs(), the two checks that reject a link member whose target leaves the destination -- AbsoluteLinkError and LinkOutsideDestinationError -- are gated behind for_data=True. They therefore run under the 'data' filter only. Under 'tar', a symlink member whose linkname is an absolute path, or a relative path climbing out of the destination, is extracted as given. The point of raising it here is not the archive format. It is that the pre-PEP-706 fallback in this same class already refuses those members: _is_unsafe_symlink() rejects a symlink whose resolved target leaves the dir _is_unsafe_link() rejects a hardlink whose resolved target leaves the dir So on a Python without extraction filters, safe_tarfile refuses these members; on a current Python it accepts them. The class became less strict as the interpreter improved, which I take to be the opposite of what the try/except was written to do. MEASURED, Python 3.10.12, with controls so that a "blocked" result cannot come from a broken harness. Reproducer attached (probe-samba-fix.py); it builds each archive in a temp directory and reports what actually lands on disk. payload master with patch --------------------------------------------------------------------------- symlink -> ../../../escape created, points outside LinkOutsideDestinationError symlink -> /etc/passwd created, points outside AbsoluteLinkError hardlink -> ../../../escape/x KeyError LinkOutsideDestinationError CONTROL benign file extracted extracted CONTROL '../' member path OutsideDestinationError OutsideDestinationError CONTROL symlink -> ok.txt created, inside created, inside Two things the table is meant to settle. First, the 'tar' filter does close the original CVE-2007-4559 path-traversal case -- the '../' member control is refused both before and after -- so this is only the link-target half. Second, a legitimate symlink that stays inside the destination is still extracted after the change, so the fix does not cost you working behaviour. For completeness, and so this is not overstated: I also tested writing THROUGH such a symlink with a later member in the same archive. CPython raises OutsideDestinationError for that. This is file-read and link-placement, not arbitrary write. WHY THIS IS NOT FILED AS A SECURITY ISSUE The consumers are python/samba/ntacls.py and python/samba/netcmd/domain/backup.py, i.e. the "samba-tool domain backup restore" path. Restoring a domain backup is already a privileged operation on a file the administrator has chosen to trust, and that file reconstitutes an entire directory service. The harm case needs the victim to opt in, so I do not think it clears the bar for an advisory, and I said so when I first raised it privately. Andrew Bartlett agreed and asked me to file it publicly. Treat it as code hardening plus a comment-accuracy issue and score it however you see fit; I will not argue the severity. SUGGESTED FIX (patch attached) Apply the 'data' filter to link members only, and keep 'tar' for everything else, so ordinary members keep the permissions that motivated choosing 'tar': _tar_filter = staticmethod(tarfile.tar_filter) _data_filter = staticmethod(tarfile.data_filter) def extraction_filter(self, member, path): if member.issym() or member.islnk(): return self._data_filter(member, path) return self._tar_filter(member, path) The attached patch does that, updates the comment above it (that comment is the sentence a future reader will rely on), and adds three tests to samba.tests.safe_tarfile, which selftest/tests.py already plans. The two escape tests fail on master and pass with the change; the in-destination control and the two existing tests pass either way. The patch carries a Signed-off-by line per the copyright policy, and a BUG: line I will fill in with this bug's number. I have not built it against a full Samba tree -- I have no Samba build here -- so the module was exercised standalone with a stand-in for TestCaseInTempDir. Your CI is the real check, and I would rather say that than imply otherwise. A SECOND, MINOR OBSERVATION In the same fallback branch, _is_traversal_attempt() tests tarinfo.name.startswith(os.sep) and ".." + os.sep. Tar member names use '/' by specification, so on a Windows host os.sep is the backslash and neither test fires. Samba does not run its Python tooling on Windows as far as I know, so I mention it only for completeness and it is not addressed in the patch. RELATED The underlying documentation gap is python/cpython#156026: nothing in the tarfile docs states plainly that the 'tar' filter does not bound link targets. Several projects besides Samba have read it the same way. -- Shakir Mahmood Shakir Independent security researcher GitHub: drengir1
Created attachment 19200 [details] patch: bound link targets in safe_tarfile ('data' filter for link members only), plus three tests Patch against master, Signed-off-by present per the copyright policy, BUG: line pointing at this bug. It keeps the 'tar' filter for ordinary members, so permissions are preserved, and applies the 'data' filter to link members only, which is the only stdlib filter that bounds link targets. The comment above the filter selection is updated too, since that sentence is what a future reader will rely on. Three tests are added to samba.tests.safe_tarfile, which selftest/tests.py already plans. Both escape tests fail on master and pass with the change; the third is a control asserting that a legitimate symlink pointing inside the destination is still extracted, so the fix does not cost working behaviour. I have marked the CI flag '-' deliberately: I have no Samba build on this machine, so this exact patch has NOT been through GitLab CI or autobuild. The module was exercised standalone on Python 3.10.12 with a stand-in for TestCaseInTempDir. Please treat your CI as the real check. Happy to rework it in any direction you prefer, including the narrower option of only correcting the comment.
Created attachment 19201 [details] reproducer: probe-samba-fix.py (runs master's behaviour and the patched behaviour side by side, with controls) Standalone, no dependencies, no network. It copies the current class body from python/samba/safe_tarfile.py and the patched variant, builds each archive in a temporary directory, extracts, and reports what actually lands on disk. Run it with any Python that has the PEP 706 filters: python3 probe-samba-fix.py The controls are the point. A benign file must extract, a '../' member path must be refused, and a legitimate in-destination symlink must still be created, so a 'blocked' verdict cannot come from a broken harness or from a host that cannot create symlinks. Output on Python 3.10.12 is the table in comment 0.