Skip to content

nil panic in OnHttpResponseTrailers when WAF context is not initialized (HTTP/2 trailers / gRPC) #320

@ruitingbrain

Description

@ruitingbrain

Summary

OnHttpResponseTrailers crashes with a nil pointer panic when ctx.logger is nil. This can happen when getWAFOrDefault fails in OnHttpRequestHeaders (e.g. no WAF matches the request authority). In that case, the function returns early without initializing ctx.tx or ctx.logger. Subsequent callbacks including OnHttpResponseTrailers are still invoked by Envoy — and unlike the sibling functions, OnHttpResponseTrailers has no nil guard.

In practice this manifests with gRPC traffic, because gRPC is the most common use of HTTP/2 response trailers (gRPC always sends grpc-status in a trailers frame). Regular HTTP responses do not use trailers, so those code paths never reach OnHttpResponseTrailers even when ctx.logger is nil.

Crash backtrace (from Envoy WASM host logs)

Proxy-Wasm plugin in-VM backtrace:
  0:  0xc34e6 - runtime.runtimePanicAt
  1:  0x9edea - runtime.nilPanic
  2:  0x235532 - interface:{debuglog.Logger}.Debug$invoke
  3:  0x2a0196 - proxy_on_response_trailers

Preceded in the Envoy logs by:

wasm log coraza-waf coraza: Failed to resolve WAF for authority "<ip>:<port>": no default WAF

Root cause

In wasmplugin/plugin.go, OnHttpResponseTrailers calls ctx.logger.Debug() as its first substantive line with no nil guard:

func (ctx *httpContext) OnHttpResponseTrailers(numTrailers int) types.Action {
    defer logTime("OnHttpResponseTrailers", currentTime())
    ctx.logger.Debug().Msg("Enforced response body processing at OnHttpResponseTrailers") // PANICS if ctx.logger is nil
    return ctx.OnHttpResponseBody(ctx.bodyReadIndex, true)
}

ctx.logger is only assigned in OnHttpRequestHeaders inside the success branch of getWAFOrDefault. When that lookup fails, the function returns ActionContinue early without setting ctx.logger or ctx.tx.

All sibling response-phase functions guard against this correctly:

// OnHttpResponseBody — safe:
if ctx.tx == nil {
    return types.ActionContinue
}

OnHttpResponseTrailers is the only callback missing this guard.

Impact

  • Any stream where getWAFOrDefault fails in OnHttpRequestHeaders AND the response carries HTTP/2 trailers will crash the WASM plugin.
  • After the panic, the WASM plugin enters a bad state — subsequent streams on the same worker return grpc-status: 14 (UNAVAILABLE) to the client.
  • Regular HTTP/1.1 and HTTP/2-without-trailers traffic is unaffected, which is why this is specific to gRPC workloads in practice.

Proposed fix

Add the same nil guard used in OnHttpResponseBody:

func (ctx *httpContext) OnHttpResponseTrailers(numTrailers int) types.Action {
    if ctx.tx == nil {
        return types.ActionContinue
    }
    defer logTime("OnHttpResponseTrailers", currentTime())
    ctx.logger.Debug().Msg("Enforced response body processing at OnHttpResponseTrailers")
    return ctx.OnHttpResponseBody(ctx.bodyReadIndex, true)
}

Note: OnHttpRequestTrailers has the same missing nil guard and may be susceptible to the same panic on the request side.

Environment

  • coraza-proxy-wasm: 0.6.0
  • Envoy Gateway: deployed via EnvoyExtensionPolicy targeting a Gateway resource
  • Traffic: gRPC over HTTP/2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions