Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 64 additions & 21 deletions platform/sqlite/docker-image-extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,68 @@
import os
import sys

image_path = sys.argv[1]
extracted_path = sys.argv[2]

image = tarfile.open(image_path)
manifest = json.loads(image.extractfile('manifest.json').read())

for layer in manifest[0]['Layers']:
print('Found layer: %s' % layer)
layer_tar = tarfile.open(fileobj=image.extractfile(layer))

for tarinfo in layer_tar:
print(' ... %s' % tarinfo.name)
if tarinfo.isdev():
print(' --> skip device files')
def is_safe_path(basedir, path):
base = os.path.realpath(basedir)
target = os.path.realpath(path)
return os.path.commonpath([base]) == os.path.commonpath([base, target])
def is_safe_link(basedir, tarinfo):
if not (tarinfo.issym() or tarinfo.islnk()):
return True
if os.path.isabs(tarinfo.linkname):
return False
link_dir = os.path.dirname(os.path.join(basedir, tarinfo.name))
target_path = os.path.normpath(os.path.join(link_dir, tarinfo.linkname))
return is_safe_path(basedir, target_path)
def main():
if len(sys.argv) < 3:
print("Usage: python docker-image-extract.py <image.tar> <destination_dir>")
sys.exit(1)
image_path = sys.argv[1]
extracted_path = sys.argv[2]
if not os.path.exists(extracted_path):
os.makedirs(extracted_path)
extracted_path = os.path.realpath(extracted_path)
try:
image = tarfile.open(image_path)
except Exception as e:
print(f"Error opening image: {e}")
sys.exit(1)
try:
manifest_data = image.extractfile('manifest.json')
if not manifest_data:
print("Error: manifest.json not found in the image archive.")
sys.exit(1)
manifest = json.loads(manifest_data.read())
except Exception as e:
print(f"Error reading manifest: {e}")
sys.exit(1)
for layer in manifest[0]['Layers']:
print(f"Found layer: {layer}")
layer_fileobj = image.extractfile(layer)
if not layer_fileobj:
continue

dest = os.path.join(extracted_path, tarinfo.name)
if not tarinfo.isdir() and os.path.exists(dest):
print(' --> remove old version of file')
os.unlink(dest)

layer_tar.extract(tarinfo, path=extracted_path)
layer_tar = tarfile.open(fileobj=layer_fileobj)
for tarinfo in layer_tar:
dest = os.path.normpath(os.path.join(extracted_path, tarinfo.name))
if not is_safe_path(extracted_path, dest):
print(f" [!] SECURITY WARNING: Skipping unsafe path: {tarinfo.name}")
continue
if not is_safe_link(extracted_path, tarinfo):
print(f" [!] SECURITY WARNING: Skipping unsafe link: {tarinfo.name} -> {tarinfo.linkname}")
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link targets are never validated by security check

High Severity

The is_safe_path check only validates tarinfo.name (the entry's filesystem destination) but never inspects tarinfo.linkname (the target of symlink or hardlink entries). A malicious tar can include a hardlink with a safe-looking name but a linkname like ../../etc/shadow, causing tarfile.extract to create a hard link to a sensitive file outside the extraction directory. Similarly, a symlink entry can have linkname pointing to an arbitrary absolute path. This is a separate bypass from the abspath-vs-realpath issue and matches the pattern described in CVE-2025-4138.

Fix in Cursor Fix in Web

print(f" ... {tarinfo.name}")
if tarinfo.isdev():
print(" --> skip device files")
continue
if not tarinfo.isdir() and os.path.exists(dest):
if os.path.isfile(dest) or os.path.islink(dest):
print(" --> remove old version of file")
os.unlink(dest)
try:
layer_tar.extract(tarinfo, path=extracted_path)
except Exception as e:
print(f" [!] Error extracting {tarinfo.name}: {e}")
image.close()
print("\nExtraction complete.")
if __name__ == "__main__":
main()