comparison src/http/v3/ngx_http_v3_table.c @ 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 efbcdb9b37dc
children acb8548c00e9
comparison
equal deleted inserted replaced
9061:af5adec171b4 9062:987bee4363d1
11 11
12 12
13 #define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) 13 #define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32)
14 14
15 15
16 static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); 16 static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target);
17 static void ngx_http_v3_unblock(void *data); 17 static void ngx_http_v3_unblock(void *data);
18 static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); 18 static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c);
19 19
20 20
21 typedef struct { 21 typedef struct {
202 ngx_http_v3_session_t *h3c; 202 ngx_http_v3_session_t *h3c;
203 ngx_http_v3_dynamic_table_t *dt; 203 ngx_http_v3_dynamic_table_t *dt;
204 204
205 size = ngx_http_v3_table_entry_size(name, value); 205 size = ngx_http_v3_table_entry_size(name, value);
206 206
207 if (ngx_http_v3_evict(c, size) != NGX_OK) { 207 h3c = ngx_http_v3_get_session(c);
208 dt = &h3c->table;
209
210 if (size > dt->capacity) {
211 ngx_log_error(NGX_LOG_ERR, c->log, 0,
212 "not enough dynamic table capacity");
208 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; 213 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
209 } 214 }
210
211 h3c = ngx_http_v3_get_session(c);
212 dt = &h3c->table;
213 215
214 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, 216 ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0,
215 "http3 insert [%ui] \"%V\":\"%V\", size:%uz", 217 "http3 insert [%ui] \"%V\":\"%V\", size:%uz",
216 dt->base + dt->nelts, name, value, size); 218 dt->base + dt->nelts, name, value, size);
217 219
231 233
232 dt->elts[dt->nelts++] = field; 234 dt->elts[dt->nelts++] = field;
233 dt->size += size; 235 dt->size += size;
234 236
235 dt->insert_count++; 237 dt->insert_count++;
238
239 if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) {
240 return NGX_ERROR;
241 }
236 242
237 ngx_post_event(&dt->send_insert_count, &ngx_posted_events); 243 ngx_post_event(&dt->send_insert_count, &ngx_posted_events);
238 244
239 if (ngx_http_v3_new_entry(c) != NGX_OK) { 245 if (ngx_http_v3_new_entry(c) != NGX_OK) {
240 return NGX_ERROR; 246 return NGX_ERROR;
291 ngx_log_error(NGX_LOG_INFO, c->log, 0, 297 ngx_log_error(NGX_LOG_INFO, c->log, 0,
292 "client exceeded http3_max_table_capacity limit"); 298 "client exceeded http3_max_table_capacity limit");
293 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; 299 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
294 } 300 }
295 301
296 dt = &h3c->table; 302 if (ngx_http_v3_evict(c, capacity) != NGX_OK) {
297 303 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR;
298 if (dt->size > capacity) { 304 }
299 if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { 305
300 return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; 306 dt = &h3c->table;
301 }
302 }
303
304 max = capacity / 32; 307 max = capacity / 32;
305 prev_max = dt->capacity / 32; 308 prev_max = dt->capacity / 32;
306 309
307 if (max > prev_max) { 310 if (max > prev_max) {
308 elts = ngx_alloc(max * sizeof(void *), c->log); 311 elts = ngx_alloc(max * sizeof(void *), c->log);
343 ngx_free(dt->elts); 346 ngx_free(dt->elts);
344 } 347 }
345 348
346 349
347 static ngx_int_t 350 static ngx_int_t
348 ngx_http_v3_evict(ngx_connection_t *c, size_t need) 351 ngx_http_v3_evict(ngx_connection_t *c, size_t target)
349 { 352 {
350 size_t size, target; 353 size_t size;
351 ngx_uint_t n; 354 ngx_uint_t n;
352 ngx_http_v3_field_t *field; 355 ngx_http_v3_field_t *field;
353 ngx_http_v3_session_t *h3c; 356 ngx_http_v3_session_t *h3c;
354 ngx_http_v3_dynamic_table_t *dt; 357 ngx_http_v3_dynamic_table_t *dt;
355 358
356 h3c = ngx_http_v3_get_session(c); 359 h3c = ngx_http_v3_get_session(c);
357 dt = &h3c->table; 360 dt = &h3c->table;
358
359 if (need > dt->capacity) {
360 ngx_log_error(NGX_LOG_ERR, c->log, 0,
361 "not enough dynamic table capacity");
362 return NGX_ERROR;
363 }
364
365 target = dt->capacity - need;
366 n = 0; 361 n = 0;
367 362
368 while (dt->size > target) { 363 while (dt->size > target) {
369 field = dt->elts[n++]; 364 field = dt->elts[n++];
370 size = ngx_http_v3_table_entry_size(&field->name, &field->value); 365 size = ngx_http_v3_table_entry_size(&field->name, &field->value);