|
|
|
|
| import pickle
|
| import collections
|
|
|
| import torch
|
| import numpy
|
| import _codecs
|
| import zipfile
|
| import re
|
|
|
|
|
|
|
| from modules import errors
|
|
|
| TypedStorage = torch.storage.TypedStorage if hasattr(torch.storage, 'TypedStorage') else torch.storage._TypedStorage
|
|
|
| def encode(*args):
|
| out = _codecs.encode(*args)
|
| return out
|
|
|
|
|
| class RestrictedUnpickler(pickle.Unpickler):
|
| extra_handler = None
|
|
|
| def persistent_load(self, saved_id):
|
| assert saved_id[0] == 'storage'
|
|
|
| try:
|
| return TypedStorage(_internal=True)
|
| except TypeError:
|
| return TypedStorage()
|
|
|
| def find_class(self, module, name):
|
| if self.extra_handler is not None:
|
| res = self.extra_handler(module, name)
|
| if res is not None:
|
| return res
|
|
|
| if module == 'collections' and name == 'OrderedDict':
|
| return getattr(collections, name)
|
| if module == 'torch._utils' and name in ['_rebuild_tensor_v2', '_rebuild_parameter', '_rebuild_device_tensor_from_numpy']:
|
| return getattr(torch._utils, name)
|
| if module == 'torch' and name in ['FloatStorage', 'HalfStorage', 'IntStorage', 'LongStorage', 'DoubleStorage', 'ByteStorage', 'float32', 'BFloat16Storage']:
|
| return getattr(torch, name)
|
| if module == 'torch.nn.modules.container' and name in ['ParameterDict']:
|
| return getattr(torch.nn.modules.container, name)
|
| if module == 'numpy.core.multiarray' and name in ['scalar', '_reconstruct']:
|
| return getattr(numpy.core.multiarray, name)
|
| if module == 'numpy' and name in ['dtype', 'ndarray']:
|
| return getattr(numpy, name)
|
| if module == '_codecs' and name == 'encode':
|
| return encode
|
| if module == "pytorch_lightning.callbacks" and name == 'model_checkpoint':
|
| import pytorch_lightning.callbacks
|
| return pytorch_lightning.callbacks.model_checkpoint
|
| if module == "pytorch_lightning.callbacks.model_checkpoint" and name == 'ModelCheckpoint':
|
| import pytorch_lightning.callbacks.model_checkpoint
|
| return pytorch_lightning.callbacks.model_checkpoint.ModelCheckpoint
|
| if module == "__builtin__" and name == 'set':
|
| return set
|
|
|
|
|
| raise Exception(f"global '{module}/{name}' is forbidden")
|
|
|
|
|
|
|
| allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|(data\.pkl))$")
|
| data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$")
|
|
|
| def check_zip_filenames(filename, names):
|
| for name in names:
|
| if allowed_zip_names_re.match(name):
|
| continue
|
|
|
| raise Exception(f"bad file inside {filename}: {name}")
|
|
|
|
|
| def check_pt(filename, extra_handler):
|
| try:
|
|
|
|
|
| with zipfile.ZipFile(filename) as z:
|
| check_zip_filenames(filename, z.namelist())
|
|
|
|
|
| data_pkl_filenames = [f for f in z.namelist() if data_pkl_re.match(f)]
|
| if len(data_pkl_filenames) == 0:
|
| raise Exception(f"data.pkl not found in {filename}")
|
| if len(data_pkl_filenames) > 1:
|
| raise Exception(f"Multiple data.pkl found in {filename}")
|
| with z.open(data_pkl_filenames[0]) as file:
|
| unpickler = RestrictedUnpickler(file)
|
| unpickler.extra_handler = extra_handler
|
| unpickler.load()
|
|
|
| except zipfile.BadZipfile:
|
|
|
|
|
| with open(filename, "rb") as file:
|
| unpickler = RestrictedUnpickler(file)
|
| unpickler.extra_handler = extra_handler
|
| for _ in range(5):
|
| unpickler.load()
|
|
|
|
|
| def load(filename, *args, **kwargs):
|
| return load_with_extra(filename, *args, extra_handler=global_extra_handler, **kwargs)
|
|
|
|
|
| def load_with_extra(filename, extra_handler=None, *args, **kwargs):
|
| """
|
| this function is intended to be used by extensions that want to load models with
|
| some extra classes in them that the usual unpickler would find suspicious.
|
|
|
| Use the extra_handler argument to specify a function that takes module and field name as text,
|
| and returns that field's value:
|
|
|
| ```python
|
| def extra(module, name):
|
| if module == 'collections' and name == 'OrderedDict':
|
| return collections.OrderedDict
|
|
|
| return None
|
|
|
| safe.load_with_extra('model.pt', extra_handler=extra)
|
| ```
|
|
|
| The alternative to this is just to use safe.unsafe_torch_load('model.pt'), which as the name implies is
|
| definitely unsafe.
|
| """
|
|
|
| from modules import shared
|
|
|
| try:
|
| if not shared.cmd_opts.disable_safe_unpickle:
|
| check_pt(filename, extra_handler)
|
|
|
| except pickle.UnpicklingError:
|
| errors.report(
|
| f"Error verifying pickled file from {filename}\n"
|
| "-----> !!!! The file is most likely corrupted !!!! <-----\n"
|
| "You can skip this check with --disable-safe-unpickle commandline argument, but that is not going to help you.\n\n",
|
| exc_info=True,
|
| )
|
| return None
|
| except Exception:
|
| errors.report(
|
| f"Error verifying pickled file from {filename}\n"
|
| f"The file may be malicious, so the program is not going to read it.\n"
|
| f"You can skip this check with --disable-safe-unpickle commandline argument.\n\n",
|
| exc_info=True,
|
| )
|
| return None
|
|
|
| return unsafe_torch_load(filename, *args, **kwargs)
|
|
|
|
|
| class Extra:
|
| """
|
| A class for temporarily setting the global handler for when you can't explicitly call load_with_extra
|
| (because it's not your code making the torch.load call). The intended use is like this:
|
|
|
| ```
|
| import torch
|
| from modules import safe
|
|
|
| def handler(module, name):
|
| if module == 'torch' and name in ['float64', 'float16']:
|
| return getattr(torch, name)
|
|
|
| return None
|
|
|
| with safe.Extra(handler):
|
| x = torch.load('model.pt')
|
| ```
|
| """
|
|
|
| def __init__(self, handler):
|
| self.handler = handler
|
|
|
| def __enter__(self):
|
| global global_extra_handler
|
|
|
| assert global_extra_handler is None, 'already inside an Extra() block'
|
| global_extra_handler = self.handler
|
|
|
| def __exit__(self, exc_type, exc_val, exc_tb):
|
| global global_extra_handler
|
|
|
| global_extra_handler = None
|
|
|
|
|
| unsafe_torch_load = torch.load
|
| torch.load = load
|
| global_extra_handler = None
|
|
|