openzipkin/zipkin · error · IllegalStateException

Maximum skip depth exceeded

Error message

Maximum skip depth exceeded

What it means

When skipping unknown thrift fields, ThriftCodec.skip recurses for nested structs/lists with a depth budget (MAX_SKIP_DEPTH). Each nesting level decrements it; reaching zero throws IllegalStateException('Maximum skip depth exceeded'). This is a bomb-defusal guard against maliciously deep thrift payloads that would cause stack overflow or unbounded CPU.

Source

Thrown at zipkin/src/main/java/zipkin2/internal/ThriftCodec.java:123

    }
  }

  static IllegalArgumentException exceptionReading(String type, Exception e) {
    String cause = e.getMessage() == null ? "Error" : e.getMessage();
    if (e instanceof EOFException) cause = "EOF";
    if (e instanceof IllegalStateException || e instanceof BufferUnderflowException) {
      cause = "Malformed";
    }
    String message = String.format("%s reading %s from TBinary", cause, type);
    throw new IllegalArgumentException(message, e);
  }

  static void skip(ReadBuffer buffer, byte type) {
    skip(buffer, type, MAX_SKIP_DEPTH);
  }

  static void skip(ReadBuffer buffer, byte type, int maxDepth) {
    if (maxDepth <= 0) throw new IllegalStateException("Maximum skip depth exceeded");
    switch (type) {
      case TYPE_BOOL:
      case TYPE_BYTE:
        buffer.skip(1);
        break;
      case TYPE_I16:
        buffer.skip(2);
        break;
      case TYPE_I32:
        buffer.skip(4);
        break;
      case TYPE_DOUBLE:
      case TYPE_I64:
        buffer.skip(8);
        break;
      case TYPE_STRING:
        buffer.skip(buffer.readInt());
        break;

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Treat it as malicious or corrupt input: drop the message, do not retry.
  2. If legitimately deep data is expected (it is not, for zipkin spans), re-encode with the current zipkin thrift schema so no unknown nested fields exist.
  3. Rate-limit and authenticate public ingest endpoints so fuzzed payloads cannot reach the decoder.
  4. Log the payload hash to correlate repeated attack/corruption sources.

Example fix

// before
Span span = SpanBytesDecoder.THRIFT.decodeOne(bytes);

// after
Span span;
try {
  span = SpanBytesDecoder.THRIFT.decodeOne(bytes);
} catch (IllegalArgumentException | IllegalStateException e) {
  LOG.warn("rejecting malformed thrift span ({}), dropping", e.getMessage());
  span = null;
}
Defensive patterns

Strategy: try-catch

Try / catch

catch (IllegalStateException | IllegalArgumentException e) { quarantine(bytes); LOG.warn("rejected pathological thrift payload: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Decoding a thrift-encoded span containing an unknown list/struct field whose elements are themselves containers, nested deeper than the allowed limit — typical of fuzzed or adversarial input, or corrupt bytes that look like deeply nested containers when mis-skipped.

Common situations: A public scribe/collector endpoint receiving crafted spans; corrupt storage blobs where random bytes decode as nested type codes.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/0951f28dd7226053. Report an issue: GitHub.