changeset 9062:987bee4363d1 quic

HTTP/3: handled insertion reference to a going to be evicted entry. As per RFC 9204, section 3.2.2, a new entry can reference an entry in the dynamic table that will be evicted when adding this new entry into the dynamic table. Previously, such inserts resulted in use-after-free since the old entry was evicted before the insertion (ticket #2431). Now it's evicted after the insertion. This change fixes Insert with Name Reference and Duplicate encoder instructions.
author Roman Arutyunyan <arut@nginx.com>
date Tue, 03 Jan 2023 16:24:45 +0400
parents af5adec171b4
children e3760b9b7c8e
files src/http/v3/ngx_http_v3_table.c
diffstat 1 files changed, 16 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/v3/ngx_http_v3_table.c
+++ b/src/http/v3/ngx_http_v3_table.c
@@ -13,7 +13,7 @@
 #define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
 
 
-static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need);
+static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target);
 static void ngx_http_v3_unblock(void *data);
 static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
 
@@ -204,13 +204,15 @@ ngx_http_v3_insert(ngx_connection_t *c, 
 
     size = ngx_http_v3_table_entry_size(name, value);
 
-    if (ngx_http_v3_evict(c, size) != NGX_OK) {
+    h3c = ngx_http_v3_get_session(c);
+    dt = &h3c->table;
+
+    if (size > dt->capacity) {
+        ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                      "not enough dynamic table capacity");
         return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
     }
 
-    h3c = ngx_http_v3_get_session(c);
-    dt = &h3c->table;
-
     ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
                    "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
                    dt->base + dt->nelts, name, value, size);
@@ -234,6 +236,10 @@ ngx_http_v3_insert(ngx_connection_t *c, 
 
     dt->insert_count++;
 
+    if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
     ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
 
     if (ngx_http_v3_new_entry(c) != NGX_OK) {
@@ -293,14 +299,11 @@ ngx_http_v3_set_capacity(ngx_connection_
         return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
     }
 
-    dt = &h3c->table;
-
-    if (dt->size > capacity) {
-        if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) {
-            return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
-        }
+    if (ngx_http_v3_evict(c, capacity) != NGX_OK) {
+        return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
     }
 
+    dt = &h3c->table;
     max = capacity / 32;
     prev_max = dt->capacity / 32;
 
@@ -345,9 +348,9 @@ ngx_http_v3_cleanup_table(ngx_http_v3_se
 
 
 static ngx_int_t
-ngx_http_v3_evict(ngx_connection_t *c, size_t need)
+ngx_http_v3_evict(ngx_connection_t *c, size_t target)
 {
-    size_t                        size, target;
+    size_t                        size;
     ngx_uint_t                    n;
     ngx_http_v3_field_t          *field;
     ngx_http_v3_session_t        *h3c;
@@ -355,14 +358,6 @@ ngx_http_v3_evict(ngx_connection_t *c, s
 
     h3c = ngx_http_v3_get_session(c);
     dt = &h3c->table;
-
-    if (need > dt->capacity) {
-        ngx_log_error(NGX_LOG_ERR, c->log, 0,
-                      "not enough dynamic table capacity");
-        return NGX_ERROR;
-    }
-
-    target = dt->capacity - need;
     n = 0;
 
     while (dt->size > target) {