#3546·h2o

Undefined behavior: Null pointer dereference in h2o_qpack_decoder_handle_input

Author: RamblurrCreated Jan 6, 2026Updated Mar 17, 2026

Like in #3540, while porting h2o to the zig build system I discovered another undefined behavior bug.

In lib/http3/qpack.c, the function h2o_qpack_decoder_handle_input unconditionally accesses entries[0] even when the blocked_streams.list is empty and entries is NULL.

At line 490:

https://github.com/h2o/h2o/blob/2e62ee29c98ef70e9f1749884557229fd255a8e5/lib/http3/qpack.c#L490

When blocked_streams.list.size == 0, the entries pointer is NULL (set via memset in h2o_qpack_create_decoder). The subsequent loop (lines 491-497) correctly checks num_unblocked < list.size and never executes when size is 0. However, line 490 still executes and dereferences the NULL pointer via the subscript operator.

entries[0] is equivalent to *(entries + 0), which dereferences NULL when entries is NULL.

This is undefined behavior per the C standard, though I suppose it works in practice on most compilers because the actual dereference happens inside the address-of operator.

reproduce with:

c
#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main(void) {
    struct entry { int64_t stream_id; int64_t largest_ref; };
    struct { struct entry *entries; size_t size; } list = {NULL, 0};

    // This is what h2o_qpack_decoder_handle_input does:
    int64_t *result = &list.entries[0].stream_id;
    printf("result: %p\n", (void*)result);
    return 0;
}

output:

$gcc -fsanitize=undefined test.c -o test
$ ./test
test.c:10:14: runtime error: member access within null pointer of type 'struct entry'
result: (nil)

effect:

When QPACK encoder stream data arrives on a fresh connection before any streams have been blocked and the build used --sanitize=undefined, then there is a runtime panic.