view src/http/v3/ngx_http_v3_encode.c @ 9282:acb8548c00e9

HTTP/3: fixed dynamic table overflow. While inserting a new entry into the dynamic table, first the entry is added, and then older entries are evicted until table size is within capacity. After the first step, the number of entries may temporarily exceed the maximum calculated from capacity by one entry, which previously caused table overflow. The easiest way to trigger the issue is to keep adding entries with empty names and values until first eviction. The issue was introduced by 987bee4363d1.
author Roman Arutyunyan <arut@nginx.com>
date Tue, 28 May 2024 17:18:50 +0400
parents 5c86189a1c1b
children
line wrap: on
line source


/*
 * Copyright (C) Roman Arutyunyan
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


uintptr_t
ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value)
{
    if (value <= 63) {
        if (p == NULL) {
            return 1;
        }

        *p++ = value;
        return (uintptr_t) p;
    }

    if (value <= 16383) {
        if (p == NULL) {
            return 2;
        }

        *p++ = 0x40 | (value >> 8);
        *p++ = value;
        return (uintptr_t) p;
    }

    if (value <= 1073741823) {
        if (p == NULL) {
            return 4;
        }

        *p++ = 0x80 | (value >> 24);
        *p++ = (value >> 16);
        *p++ = (value >> 8);
        *p++ = value;
        return (uintptr_t) p;
    }

    if (p == NULL) {
        return 8;
    }

    *p++ = 0xc0 | (value >> 56);
    *p++ = (value >> 48);
    *p++ = (value >> 40);
    *p++ = (value >> 32);
    *p++ = (value >> 24);
    *p++ = (value >> 16);
    *p++ = (value >> 8);
    *p++ = value;
    return (uintptr_t) p;
}


uintptr_t
ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix)
{
    ngx_uint_t  thresh, n;

    thresh = (1 << prefix) - 1;

    if (value < thresh) {
        if (p == NULL) {
            return 1;
        }

        *p++ |= value;
        return (uintptr_t) p;
    }

    value -= thresh;

    if (p == NULL) {
        for (n = 2; value >= 128; n++) {
            value >>= 7;
        }

        return n;
    }

    *p++ |= thresh;

    while (value >= 128) {
        *p++ = 0x80 | value;
        value >>= 7;
    }

    *p++ = value;

    return (uintptr_t) p;
}


uintptr_t
ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count,
    ngx_uint_t sign, ngx_uint_t delta_base)
{
    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8)
               + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7);
    }

    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8);

    *p = sign ? 0x80 : 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7);

    return (uintptr_t) p;
}


uintptr_t
ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
{
    /* Indexed Field Line */

    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, index, 6);
    }

    *p = dynamic ? 0x80 : 0xc0;

    return ngx_http_v3_encode_prefix_int(p, index, 6);
}


uintptr_t
ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index,
    u_char *data, size_t len)
{
    size_t   hlen;
    u_char  *p1, *p2;

    /* Literal Field Line With Name Reference */

    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, index, 4)
               + ngx_http_v3_encode_prefix_int(NULL, len, 7)
               + len;
    }

    *p = dynamic ? 0x40 : 0x50;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4);

    p1 = p;
    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);

    if (data) {
        p2 = p;
        hlen = ngx_http_huff_encode(data, len, p, 0);

        if (hlen) {
            p = p1;
            *p = 0x80;
            p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);

            if (p != p2) {
                ngx_memmove(p, p2, hlen);
            }

            p += hlen;

        } else {
            p = ngx_cpymem(p, data, len);
        }
    }

    return (uintptr_t) p;
}


uintptr_t
ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value)
{
    size_t   hlen;
    u_char  *p1, *p2;

    /* Literal Field Line With Literal Name */

    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
               + name->len
               + ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
               + value->len;
    }

    p1 = p;
    *p = 0x20;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);

    p2 = p;
    hlen = ngx_http_huff_encode(name->data, name->len, p, 1);

    if (hlen) {
        p = p1;
        *p = 0x28;
        p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3);

        if (p != p2) {
            ngx_memmove(p, p2, hlen);
        }

        p += hlen;

    } else {
        ngx_strlow(p, name->data, name->len);
        p += name->len;
    }

    p1 = p;
    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7);

    p2 = p;
    hlen = ngx_http_huff_encode(value->data, value->len, p, 0);

    if (hlen) {
        p = p1;
        *p = 0x80;
        p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);

        if (p != p2) {
            ngx_memmove(p, p2, hlen);
        }

        p += hlen;

    } else {
        p = ngx_cpymem(p, value->data, value->len);
    }

    return (uintptr_t) p;
}


uintptr_t
ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
{
    /* Indexed Field Line With Post-Base Index */

    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, index, 4);
    }

    *p = 0x10;

    return ngx_http_v3_encode_prefix_int(p, index, 4);
}


uintptr_t
ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data,
    size_t len)
{
    size_t   hlen;
    u_char  *p1, *p2;

    /* Literal Field Line With Post-Base Name Reference */

    if (p == NULL) {
        return ngx_http_v3_encode_prefix_int(NULL, index, 3)
               + ngx_http_v3_encode_prefix_int(NULL, len, 7)
               + len;
    }

    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3);

    p1 = p;
    *p = 0;
    p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7);

    if (data) {
        p2 = p;
        hlen = ngx_http_huff_encode(data, len, p, 0);

        if (hlen) {
            p = p1;
            *p = 0x80;
            p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7);

            if (p != p2) {
                ngx_memmove(p, p2, hlen);
            }

            p += hlen;

        } else {
            p = ngx_cpymem(p, data, len);
        }
    }

    return (uintptr_t) p;
}