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.

Keras 3 .keras loader: uncontrolled memory allocation via attacker-controlled build_config layer shapes

Target: keras (PyPI) β€” verified against keras 3.15.0, numpy backend Public entrypoint: keras.saving.load_model(path, safe_mode=True, compile=False) Class: CWE-789 (Memory Allocation with Excessive Size Value) / CWE-409 (Improper Handling of Highly Compressed Data β€” decompression/expansion bomb) Impact: Denial of Service β€” a ~1.4 KB .keras model file with no large data forces an arbitrarily large host-side memory allocation (OOM-kill / MemoryError). safe_mode=True provides no protection.


Root cause

When loading a .keras v3 archive, serialization_lib.deserialize_keras_object() reconstructs each layer and then, at serialization_lib.py:785-787, unconditionally rebuilds it from the embedded build_config with no validation of the shapes it declares:

build_config = config.get("build_config", None)
if build_config and not instance.built:
    instance.build_from_config(build_config)

For a Dense layer, Dense.build() (dense.py:136) issues:

self._kernel = self.add_weight(
    name="kernel",
    shape=(input_shape[-1], self.units),
    initializer=self.kernel_initializer,   # GlorotUniform
    ...
)

which materializes the kernel array via the initializer. Both integers that determine the kernel shape are attacker-controlled JSON fields in the tiny config.json:

  • build_config["input_shape"][-1]
  • the layer config's units

The numpy initializer path (initializers/random_initializers.py:316 β†’ backend/numpy/random.py:23 uniform) allocates the array in float64, so the peak resident spike is 8Γ— the nominal float32 weight size.

Crucially, the allocation is fabricated purely from config.json integers and is unrelated to the actual weight data present in the archive. In this PoC the archive's model.weights.h5 still holds only a benign (4, 3) kernel, yet the loader tries to allocate a (units, input_shape[-1]) array before it ever reaches the weight-shape check. A weaponized file is therefore ~1.4 KB and carries no large data of its own.

safe_mode=True gives no protection here β€” it only guards the __lambda__ special-case in the deserializer, not the build_from_config shape path.


Verified root-cause traceback (evil_huge, verbatim frames)

saving_lib.py:434       _model_from_config
serialization_lib.py:775 deserialize_keras_object   (instance = cls.from_config(inner_config))
sequential.py:393       from_config
serialization_lib.py:787 deserialize_keras_object   (instance.build_from_config(build_config))
layer.py:491            build_from_config
dense.py:136            build
layer.py:622            add_weight
variables.py:418        _initialize_with_initializer
random_initializers.py:316 __call__
backend/numpy/random.py:23 uniform
  -> numpy._core._exceptions._ArrayMemoryError:
     Unable to allocate 7.28 TiB for an array with shape (1000000, 1000000) and data type float64

PoC

Built a genuine 1-layer Sequential model Input((4,)) -> Dense(3) and saved it with keras.Model.save() β†’ base.keras (11,580 B). Then, keeping the identical valid metadata.json and the original tiny model.weights.h5, edited two integers in config.json β€” the Dense layer's units and build_config.input_shape[-1] β€” and repackaged as a valid ZIP.

Two weaponized variants:

file size units input_shape
evil_mod.keras 1,415 B 20000 [None, 20000]
evil_huge.keras 1,414 B 1000000 [None, 1000000]

Each is loaded via the public API keras.saving.load_model(path, safe_mode=True, compile=False) against the real installed keras 3.15.0 (numpy backend) in a clean venv. Negative control: the untampered base.keras.

Artifacts in this repo: base.keras, evil_mod.keras, evil_huge.keras, build_and_verify.py (builds the evil files from base and runs all three with RSS measurement).

Reproduce

python -m venv venv && . venv/bin/activate
pip install "keras==3.15.0" numpy
python build_and_verify.py

Captured evidence (verbatim)

keras 3.15.0 backend numpy
baseline rss after import: 255 MB
[CONTROL]   base.keras      11580B -> loaded OK, peak rss 257 MB
[EVIL-MOD]  evil_mod.keras   1415B -> ValueError AFTER alloc; peak rss 4835 MB  (delta ~4578 MB from a 1415-byte file)
[EVIL-HUGE] evil_huge.keras  1414B -> MemoryError: Unable to allocate 7.28 TiB for an array
                                       with shape (1000000, 1000000) and data type float64
  • evil_mod forces a ~4.6 GB resident spike from a 1.4 KB file. The allocation completes before the later weight-shape ValueError is raised β€” i.e. the DoS lands even though the file is ultimately "invalid".
  • evil_huge attempts a 7.28 TiB allocation β†’ numpy MemoryError.

Negative controls

  • (a) base.keras (untampered, 11,580 B) loads to a 257 MB peak β€” no spike.
  • (b) A deeply-nested config raises a catchable RecursionError, not this bug β€” confirming the vector is the build-shape path, not generic nesting.
  • (c) CPython json.loads on 1e6-deep nesting raises RecursionError (no segfault) β€” confirming this finding is the build_from_config shape path, not a JSON-parser issue.

Dedup note

Distinct from prior Keras DoS findings in this account:

  • huntr-poc-dos-keras-config-json-bomb-4gib-floor β€” abuses raw config.json size / JSON parsing volume. This finding uses a tiny (~1.4 KB) config and instead abuses semantic integer fields (units, input_shape) consumed by build_from_config; no large data present.
  • huntr-poc-keras-legacy-h5-weightnames-stackbomb-oom β€” legacy .h5 weight-name path. This is the v3 .keras build_config path (serialization_lib.py:787 β†’ Dense.build), a different sink.
  • huntr-poc-keras-assets-zipbomb β€” ZIP decompression of assets. This finding is not a compression bomb of the archive; the allocation is fabricated from config integers independent of any stored/compressed bytes.
  • huntr-poc-keras-nested-lambda / lambda RCE β€” unrelated __lambda__ code-exec path.

No known CVE covers build_config shape-driven allocation in the Keras 3 v3 loader at time of filing. The vector survives safe_mode=True.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support