fix: load voice presets with a restricted unpickler (CWE-502)#397
Open
officialasishkumar wants to merge 1 commit into
Open
fix: load voice presets with a restricted unpickler (CWE-502)#397officialasishkumar wants to merge 1 commit into
officialasishkumar wants to merge 1 commit into
Conversation
Commit 303b283 switched the voice-preset loaders in demo/web/app.py and demo/realtime_model_inference_from_file.py to `torch.load(..., weights_only=True)` guarded by `torch.serialization.safe_globals([BaseModelOutputWithPast, DynamicCache])` to close a CWE-502 arbitrary-code-execution risk. That load can never succeed. The presets are dicts of `transformers.modeling_outputs.BaseModelOutputWithPast` objects, and PyTorch's weights-only unpickler refuses the `SETITEMS` opcode on `dict` subclasses (it accepts only the exact `dict`, `OrderedDict` and `Counter` types) even when the class is allowlisted via `safe_globals`. Loading therefore aborts on startup with: _pickle.UnpicklingError: Weights only load failed. ... Can only SETITEMS for dict, collections.OrderedDict, collections.Counter, but got <class 'transformers.modeling_outputs.BaseModelOutputWithPast'> making the streaming web demo and the realtime file demo unusable on PyTorch >= 2.6. `BaseModelOutputWithPast` has to stay an object (the model accesses `outputs.past_key_values` and `outputs.last_hidden_state`), so the presets cannot simply be flattened to plain tensors. Instead, load them through a restricted `pickle.Unpickler` whose `find_class` resolves only the container classes the presets are built from (`OrderedDict`, `BaseModelOutputWithPast`, `DynamicCache`) plus torch's tensor-rebuilding primitives. Any other global, e.g. `os.system`, is refused, so a tampered preset still cannot execute arbitrary code and the CWE-502 protection is preserved. The same restriction is applied to the module-level `load` / `loads` that `torch.load` uses for legacy-format metadata, so both serialization formats stay safe. The shared `load_voice_preset` helper lives in the streaming processor module imported by both demos; the now-unused `BaseModelOutputWithPast` and `DynamicCache` imports are removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
demo/web/app.pyanddemo/realtime_model_inference_from_file.pycrash on startup, making the streaming web demo and the realtime file demo unusable on PyTorch >= 2.6 (App/Demo exception since fix: use weights_only=True (CWE-502) #392).pickle.Unpicklerthat keeps the CWE-502 protection while actually being able to reconstruct the objects.Problem
Commit 303b283 changed both loaders to
torch.load(..., weights_only=True)wrapped intorch.serialization.safe_globals([BaseModelOutputWithPast, DynamicCache])to close a CWE-502 risk (arbitrary code execution from a tampered.pt).The presets are
dicts oftransformers.modeling_outputs.BaseModelOutputWithPastobjects (each holding alast_hidden_statetensor and aDynamicCache). PyTorch's weights-only unpickler refuses theSETITEMSopcode ondictsubclasses — it accepts only the exactdict,OrderedDictandCountertypes, even when the subclass is allowlisted viasafe_globals. So the load can never succeed and aborts on startup with:BaseModelOutputWithPasthas to stay an object — the model accessesoutputs.past_key_valuesandoutputs.last_hidden_state— so the presets cannot simply be flattened to plain tensors.Fix
Add a shared
load_voice_presethelper invibevoice/processor/vibevoice_streaming_processor.py(imported by both demos) that loads via a restrictedpickle.Unpickler:find_classresolves only the container classes the presets are built from (OrderedDict,BaseModelOutputWithPast,DynamicCache) and torch's tensor-rebuilding primitives (torch._utils._rebuild_*,torch.*Storage). Any other global, e.g.os.system, raisesUnpicklingError, so a tampered preset still cannot execute arbitrary code — the CWE-502 protection is preserved.load/loadsthattorch.loaduses for legacy-format metadata, so both the zip and legacy serialization formats stay safe.Because the restricted unpickler uses standard pickle semantics,
SETITEMSonBaseModelOutputWithPastworks and the objects load correctly. The now-unusedBaseModelOutputWithPast/DynamicCacheimports are removed from both demos.Test plan
Validated on PyTorch 2.12 + transformers 4.51.3:
weights_only=True+safe_globalswith the exactSETITEMSerror.demo/voices/streaming_model/*.ptfiles load viaload_voice_preset, byte-identical (torch.equalon everylast_hidden_stateand every cache key/value tensor) to a trusted full unpickle..ptwhose payload callsos.systemis rejected withUnpicklingErrorin both the zip and legacy formats, and the side effect never runs.outputs.past_key_values,outputs.last_hidden_state) and item access (outputs["last_hidden_state"]) keep working.Closes #392