Working draft

N35 symlink-escapes

A relative symlink whose target leads out of the destination.

Tolerate(draft) A strict parser may reject this archive. If it accepts it, it must extract the expected tree below.

Category
name
Default policy
accepts it: the symlink is created as it is, and writing through a symlink is rejected
Files
N35-symlink-escapes.tar test archive, 1,536 bytes sha256 588e06e5edf870af6822fb52fac4459d222224d4da963ee78baba86780850c60
N35-symlink-escapes.jsonexpected result
N35-symlink-escapes.txtannotated blocks
N35-symlink-escapes.pygenerator script

The case

The archive has one member, a symlink link to ../escaped, and does not write through it.

The default policy tolerates it, and rejects an archive that writes through a symlink.

The expected tree:

pathkindcontents
linksymlink→ ../escaped

Decision point: whether a symlink that points out of the destination is created.

Seen in: proteomics-runtime 0.3.8 and snowpark-checkpoints 0.1.0rc1.

The archive

Every block of 512 bytes, to scale, with its offset. Hover over a field for its bytes.

N35-symlink-escapes: every block of the archive0x000000header: symlinkname [0,100): "link" + 96 NULnamemode [100,108): "0000644" + NUL, 644 (octal)uid [108,116): "0000000" + NUL, 0gid [116,124): "0000000" + NUL, 0size [124,136): "00000000000" + NUL, 0mtime [136,148): "00000000000" + NUL, 0chksum [148,156): "011615\0 ", 5005: the unsigned sum of the header, this field as spacestypeflag [156,157): "2", symlinklinkname [157,257): "../escaped" + 90 NULlinknamemagic [257,263): "ustar" + NULversion [263,265): "00"uname [265,297): 32 NULunamegname [297,329): 32 NULgnamedevmajor [329,337): "0000000" + NUL, 0devminor [337,345): "0000000" + NUL, 0prefix [345,500): 155 NULprefixpad [500,512): 12 NULname "link" + 96 NULlinkname "../escaped" + 90 NUL0x0002002 zero blocks: the end-of-archive markerbytes 0 to 512 of 1024: 512 NUL0x0004002 zero blocks, continuedbytes 512 to 1024 of 1024: 512 NUL0x000600end of fileNULprintable ASCIIspacecontrol character, such as a newlinebyte over 0x7fwritten by the case itself
Every block, as text
N35-symlink-escapes: tolerated: a strict parser may reject it; if it accepts it, it must extract its tree, and the default policy accepts it

A relative symlink whose target leads out of the destination.

The archive has one member, a symlink `link` to `../escaped`, and does not write through it.

The default policy tolerates it, and rejects an archive that writes through a symlink.

Decision point: whether a symlink that points out of the destination is created.
Seen in: proteomics-runtime 0.3.8 and snowpark-checkpoints 0.1.0rc1.
See also: N07, a symlink to an absolute path, and S10 and S11, archives that write through a
symlink.

0x000000  header: symlink
          name     [  0,100)  "link" + 96 NUL
          mode     [100,108)  "0000644" + NUL  644 (octal)
          uid      [108,116)  "0000000" + NUL  0
          gid      [116,124)  "0000000" + NUL  0
          size     [124,136)  "00000000000" + NUL  0
          mtime    [136,148)  "00000000000" + NUL  0
          chksum   [148,156)  "011615\0 "  5005: the unsigned sum of the header, this field as spaces
          typeflag [156,157)  "2"  symlink
          linkname [157,257)  "../escaped" + 90 NUL
          magic    [257,263)  "ustar" + NUL
          version  [263,265)  "00"
          uname    [265,297)  32 NUL
          gname    [297,329)  32 NUL
          devmajor [329,337)  "0000000" + NUL  0
          devminor [337,345)  "0000000" + NUL  0
          prefix   [345,500)  155 NUL
          pad      [500,512)  12 NUL
0x000200  2 zero blocks: the end-of-archive marker
0x000600  end of file
Every byte, as a hex dump

As xxd writes it: xxd -r makes the archive of it.

00000000: 6c69 6e6b 0000 0000 0000 0000 0000 0000  link............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000060: 0000 0000 3030 3030 3634 3400 3030 3030  ....0000644.0000
00000070: 3030 3000 3030 3030 3030 3000 3030 3030  000.0000000.0000
00000080: 3030 3030 3030 3000 3030 3030 3030 3030  0000000.00000000
00000090: 3030 3000 3031 3136 3135 0020 322e 2e2f  000.011615. 2../
000000a0: 6573 6361 7065 6400 0000 0000 0000 0000  escaped.........
000000b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000100: 0075 7374 6172 0030 3000 0000 0000 0000  .ustar.00.......
00000110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000120: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000140: 0000 0000 0000 0000 0030 3030 3030 3030  .........0000000
00000150: 0030 3030 3030 3030 0000 0000 0000 0000  .0000000........
00000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000170: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000001f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000200: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000210: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000220: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000230: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000240: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000270: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000290: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000002f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000300: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000310: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000320: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000330: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000340: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000350: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000360: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000370: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000380: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000390: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000400: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000410: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000420: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000430: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000440: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000450: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000460: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000470: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000480: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000490: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000500: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000510: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000520: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000530: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000540: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000550: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000560: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000570: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000580: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000590: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

What implementations make of it

In the outcome column, ✅ means that the implementation follows the default policy.

implementationversionoutcomewhat it makes of it
GNU tartar (GNU tar) 1.35➕ acceptsthe expected tree
bsdtarbsdtar 3.8.11 - libarchive 3.8.11➕ acceptsthe expected tree
BusyBox tarBusyBox v1.38.0, defconfig➕ acceptsthe expected tree
7-Zip7-Zip (z) 26.05 (x64)➖ rejectserror: ERROR: Dangerous link path was ignored : link : ../escaped
Python tarfile, filter=data, as in pipCPython 3.15.2➖ rejectserror: LinkOutsideDestinationError: 'link' would link to 'ROOT/x/y/escaped', which is outside the destination
Python tarfile, filter=tarCPython 3.15.2➕ acceptsthe expected tree
Python tarfile, filter=fully_trustedCPython 3.15.2➕ acceptsthe expected tree
node-tartar 7.5.22❌ another tree, or rejectstree A: nothing
node-tar, stricttar 7.5.22➖ rejectserror: Error: TAR_ENTRY_ERROR: linkpath escapes extraction directory
tar-fstar-fs 3.1.3, on tar-stream 3.2.2➖ rejectserror: Error: DEST/link is not a valid symlink
Java Commons CompressCommons Compress 1.28.0, Expander, on Java 27.0.1.0.2❌ another tree, or rejectstree B: link (0 B, e3b0c442)
.NET System.Formats.TarSystem.Formats.Tar of .NET 10.0.14, ExtractToDirectory➖ rejectserror: IOException: Extracting the Tar entry '../escaped' would have resulted in a link target outside the specified destination directory: 'DEST/'
PHP PharData, as in ComposerPharData of PHP 8.5.12, as Composer's TarDownloader calls it❌ another tree, or rejectstree B: link (0 B, e3b0c442)
Go containerd archive.Applycontainerd v2.4.2, archive.Apply, of go1.27.3➕ acceptsthe expected tree
Rust tartar 0.4.46, unpack➕ acceptsthe expected tree
Rust astral-tokio-tarastral-tokio-tar 0.7.1, unpack➕ acceptsthe expected tree
Rust tar-codec, as in uvtar-codec 0.0.16, with the policies of uv 0.13➖ rejectserror: at byte 0: unsafe symbolic-link target "../escaped": escapes the destination root
RubyGemsRubyGems 4.0.20 of Ruby 4.0.9, extract_tar_gz➖ rejectserror: Gem::Package::SymlinkError: installing symlink 'link' pointing to parent path ROOT/x/y/escaped of DEST is not allowed
R untar, internalutils::untar of R 4.6.2, tar = "internal"➕ acceptsthe expected tree
Perl Archive::TarArchive::Tar 3.12 of Perl v5.44.0➖ rejectserror: Symlink 'link' target attempts traversal. Not extracting under SECURE EXTRACT MODE at contrib/matrix/runners/perl/extract.pl line 14. Could not extract 'link' at contrib/matrix/runners/perl/extract.p…
Haskell tartar 0.7.2.0, extract, of GHC 9.14➖ rejectserror: Unsafe link target in tar archive: "./../escaped"
Julia Tar.jlTar 1.10.0 of Julia 1.13.3➕ acceptsthe expected tree
live-bootstrap untarmescc-tools-extra a151c245e512, as live-bootstrap pins it➕ acceptsthe expected tree
live-bootstrap untar, --non-strictmescc-tools-extra a151c245e512, as live-bootstrap pins it➕ acceptsthe expected tree

Prevalence in release tarballs

The table below shows how many release tarballs have the features of this test case. The default policy accepts them.

ecosystemtarballswith itshare
Spack37,5530none
nixpkgs18,9900none
Guix10,1680none
PyPI10,00020.02%
crates.io5,0000none
RubyGems5,0000none
live-bootstrap1710none
total (distinct)82,5862<0.01%

See the list of these 2 tarballs, with the sha256 of each and an example from it.

See also