Unverified Commit 99404f53 authored by Cyrus Leung's avatar Cyrus Leung Committed by GitHub
Browse files

[Security] Fix image hash collision (#17378)


Signed-off-by: default avatarDarkLight1337 <tlleungac@connect.ust.hk>
parent 785d75a0
# SPDX-License-Identifier: Apache-2.0
from pathlib import Path
import numpy as np
import pytest
import torch
from PIL import Image, ImageDraw
from vllm.multimodal.hasher import MultiModalHasher
ASSETS_DIR = Path(__file__).parent / "assets"
assert ASSETS_DIR.exists()
# NOTE: Images that are the same visually are allowed to have the same hash
@pytest.mark.parametrize("mode_pair", [("1", "L"), ("RGBA", "CMYK")])
def test_hash_collision_image_mode(mode_pair):
mode1, mode2 = mode_pair
image1 = Image.new(mode1, size=(10, 10), color=1)
image2 = Image.new(mode2, size=(10, 10), color=1)
hasher = MultiModalHasher
assert hasher.hash_kwargs(image=image1) != hasher.hash_kwargs(image=image2)
def test_hash_collision_image_palette():
# These images differ only in Image.palette._palette
image1 = Image.open(ASSETS_DIR / "image1.png")
image2 = Image.open(ASSETS_DIR / "image2.png")
hasher = MultiModalHasher
assert hasher.hash_kwargs(image=image1) != hasher.hash_kwargs(image=image2)
def test_hash_collision_image_transpose():
image1 = Image.new("1", size=(10, 20))
ImageDraw.Draw(image1).line([(0, 0), (10, 0)])
image2 = Image.new("1", size=(20, 10))
ImageDraw.Draw(image2).line([(0, 0), (0, 10)])
hasher = MultiModalHasher
assert hasher.hash_kwargs(image=image1) != hasher.hash_kwargs(image=image2)
def test_hash_collision_tensor_shape():
# The hash should be different though the data is the same when flattened
arr1 = torch.zeros((5, 10, 20, 3))
arr2 = torch.zeros((10, 20, 5, 3))
hasher = MultiModalHasher
assert hasher.hash_kwargs(data=arr1) != hasher.hash_kwargs(data=arr2)
def test_hash_collision_array_shape():
# The hash should be different though the data is the same when flattened
arr1 = np.zeros((5, 10, 20, 3))
arr2 = np.zeros((10, 20, 5, 3))
hasher = MultiModalHasher
assert hasher.hash_kwargs(data=arr1) != hasher.hash_kwargs(data=arr2)
...@@ -31,16 +31,20 @@ class MultiModalHasher: ...@@ -31,16 +31,20 @@ class MultiModalHasher:
return obj.encode("utf-8") return obj.encode("utf-8")
if isinstance(obj, bytes): if isinstance(obj, bytes):
return obj return obj
if isinstance(obj, Image.Image): if isinstance(obj, (int, float)):
return obj.tobytes() return np.array(obj).tobytes()
# Convertible to NumPy arrays if isinstance(obj, Image.Image):
return cls.item_to_bytes("image", np.array(obj.convert("RGBA")))
if isinstance(obj, torch.Tensor): if isinstance(obj, torch.Tensor):
obj = obj.numpy() return cls.item_to_bytes("tensor", obj.numpy())
if isinstance(obj, (int, float)):
obj = np.array(obj)
if isinstance(obj, np.ndarray): if isinstance(obj, np.ndarray):
return obj.tobytes() return cls.item_to_bytes(
"ndarray", {
"dtype": obj.dtype.str,
"shape": obj.shape,
"data": obj.data.tobytes(),
})
logger.warning( logger.warning(
"No serialization method found for %s. " "No serialization method found for %s. "
...@@ -53,14 +57,22 @@ class MultiModalHasher: ...@@ -53,14 +57,22 @@ class MultiModalHasher:
cls, cls,
key: str, key: str,
obj: object, obj: object,
) -> bytes:
return b''.join(kb + vb for kb, vb in cls.iter_item_to_bytes(key, obj))
@classmethod
def iter_item_to_bytes(
cls,
key: str,
obj: object,
) -> Iterable[tuple[bytes, bytes]]: ) -> Iterable[tuple[bytes, bytes]]:
# Recursive cases # Recursive cases
if isinstance(obj, (list, tuple)): if isinstance(obj, (list, tuple)):
for i, elem in enumerate(obj): for i, elem in enumerate(obj):
yield from cls.item_to_bytes(f"{key}.{i}", elem) yield from cls.iter_item_to_bytes(f"{key}.{i}", elem)
elif isinstance(obj, dict): elif isinstance(obj, dict):
for k, v in obj.items(): for k, v in obj.items():
yield from cls.item_to_bytes(f"{key}.{k}", v) yield from cls.iter_item_to_bytes(f"{key}.{k}", v)
else: else:
key_bytes = cls.serialize_item(key) key_bytes = cls.serialize_item(key)
value_bytes = cls.serialize_item(obj) value_bytes = cls.serialize_item(obj)
...@@ -71,7 +83,7 @@ class MultiModalHasher: ...@@ -71,7 +83,7 @@ class MultiModalHasher:
hasher = blake3() hasher = blake3()
for k, v in kwargs.items(): for k, v in kwargs.items():
for k_bytes, v_bytes in cls.item_to_bytes(k, v): for k_bytes, v_bytes in cls.iter_item_to_bytes(k, v):
hasher.update(k_bytes) hasher.update(k_bytes)
hasher.update(v_bytes) hasher.update(v_bytes)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment