Coverage Report

Created: 2026-01-03 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/hpack-dec.c
Line
Count
Source
1
/*
2
 * HPACK decompressor (RFC7541)
3
 *
4
 * Copyright (C) 2014-2017 Willy Tarreau <willy@haproxy.org>
5
 * Copyright (C) 2017 HAProxy Technologies
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
 * OTHER DEALINGS IN THE SOFTWARE.
26
 */
27
28
#include <inttypes.h>
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <string.h>
32
33
#include <import/ist.h>
34
#include <haproxy/chunk.h>
35
#include <haproxy/global.h>
36
#include <haproxy/h2.h>
37
#include <haproxy/hpack-dec.h>
38
#include <haproxy/hpack-huff.h>
39
#include <haproxy/hpack-tbl.h>
40
#include <haproxy/tools.h>
41
42
43
#if defined(DEBUG_HPACK)
44
#define hpack_debug_printf printf
45
#define hpack_debug_hexdump debug_hexdump
46
#else
47
261k
#define hpack_debug_printf(...) do { } while (0)
48
2.32k
#define hpack_debug_hexdump(...) do { } while (0)
49
#endif
50
51
/* reads a varint from <raw>'s lowest <b> bits and <len> bytes max (raw included).
52
 * returns the 32-bit value on success after updating raw_in and len_in. Forces
53
 * len_in to (uint32_t)-1 on truncated input.
54
 */
55
static uint32_t get_var_int(const uint8_t **raw_in, uint32_t *len_in, int b)
56
101k
{
57
101k
  uint32_t ret = 0;
58
101k
  int len = *len_in;
59
101k
  const uint8_t *raw = *raw_in;
60
101k
  uint8_t shift = 0;
61
62
101k
  len--;
63
101k
  ret = *(raw++) & ((1 << b) - 1);
64
101k
  if (ret != (uint32_t)((1 << b) - 1))
65
97.9k
    goto end;
66
67
13.9k
  while (len && (*raw & 128)) {
68
10.0k
    ret += ((uint32_t)(*raw++) & 127) << shift;
69
10.0k
    shift += 7;
70
10.0k
    len--;
71
10.0k
  }
72
73
  /* last 7 bits */
74
3.90k
  if (!len)
75
89
    goto too_short;
76
3.81k
  len--;
77
3.81k
  ret += ((uint32_t)(*raw++) & 127) << shift;
78
79
101k
 end:
80
101k
  *raw_in = raw;
81
101k
  *len_in = len;
82
101k
  return ret;
83
84
89
 too_short:
85
89
  *len_in = (uint32_t)-1;
86
89
  return 0;
87
3.81k
}
88
89
/* returns the pseudo-header <idx> corresponds to among the following values :
90
 *   -  0 = unknown, the header's string needs to be used instead
91
 *   -  1 = ":authority"
92
 *   -  2 = ":method"
93
 *   -  3 = ":path"
94
 *   -  4 = ":scheme"
95
 *   -  5 = ":status"
96
 */
97
static inline int hpack_idx_to_phdr(uint32_t idx)
98
28.9k
{
99
28.9k
  if (idx > 14)
100
25.3k
    return 0;
101
102
3.61k
  idx >>= 1;
103
3.61k
  idx <<= 2;
104
3.61k
  return (0x55554321U >> idx) & 0xF;
105
28.9k
}
106
107
/* If <idx> designates a static header, returns <in>. Otherwise allocates some
108
 * room from chunk <store> to duplicate <in> into it and returns the string
109
 * allocated there. In case of allocation failure, returns a string whose
110
 * pointer is NULL.
111
 */
112
static inline struct ist hpack_alloc_string(struct buffer *store, uint32_t idx,
113
              struct ist in)
114
84.5k
{
115
84.5k
  struct ist out;
116
117
84.5k
  if (idx < HPACK_SHT_SIZE)
118
16.4k
    return in;
119
120
68.1k
  out.len = in.len;
121
68.1k
  out.ptr = chunk_newstr(store);
122
68.1k
  if (unlikely(!isttest(out)))
123
6
    return out;
124
125
68.1k
  if (unlikely(store->data + out.len > store->size)) {
126
39
    out.ptr = NULL;
127
39
    return out;
128
39
  }
129
130
68.0k
  store->data += out.len;
131
68.0k
  memcpy(out.ptr, in.ptr, out.len);
132
68.0k
  return out;
133
68.1k
}
134
135
/* decode an HPACK frame starting at <raw> for <len> bytes, using the dynamic
136
 * headers table <dht>, produces the output into list <list> of <list_size>
137
 * entries max, and uses pre-allocated buffer <tmp> for temporary storage (some
138
 * list elements will point to it). Some <list> name entries may be made of a
139
 * NULL pointer and a len, in which case they will designate a pseudo header
140
 * index according to the values returned by hpack_idx_to_phdr() above. The
141
 * number of <list> entries used is returned on success, or <0 on failure, with
142
 * the opposite one of the HPACK_ERR_* codes. A last element is always zeroed
143
 * and is not counted in the number of returned entries. This way the caller
144
 * can use list[].n.len == 0 as a marker for the end of list.
145
 */
146
int hpack_decode_frame(struct hpack_dht *dht, const uint8_t *raw, uint32_t len,
147
                       struct http_hdr *list, int list_size,
148
                       struct buffer *tmp)
149
2.14k
{
150
2.14k
  uint32_t idx;
151
2.14k
  uint32_t nlen;
152
2.14k
  uint32_t vlen;
153
2.14k
  uint8_t huff;
154
2.14k
  struct ist name;
155
2.14k
  struct ist value;
156
2.14k
  int must_index;
157
2.14k
  int ret;
158
159
2.14k
  hpack_debug_hexdump(stderr, "[HPACK-DEC] ", (const char *)raw, 0, len);
160
161
2.14k
  chunk_reset(tmp);
162
2.14k
  ret = 0;
163
66.2k
  while (len) {
164
65.5k
    int __maybe_unused code = *raw; /* first byte, only for debugging */
165
166
65.5k
    must_index = 0;
167
65.5k
    if (*raw >= 0x80) {
168
      /* indexed header field */
169
27.8k
      if (*raw == 0x80) {
170
2
        hpack_debug_printf("unhandled code 0x%02x (raw=%p, len=%u)\n", *raw, raw, len);
171
2
        ret = -HPACK_ERR_UNKNOWN_OPCODE;
172
2
        goto leave;
173
2
      }
174
175
27.8k
      hpack_debug_printf("%02x: p14: indexed header field : ", code);
176
177
27.8k
      idx = get_var_int(&raw, &len, 7);
178
27.8k
      if (len == (uint32_t)-1) { // truncated
179
10
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
180
10
        ret = -HPACK_ERR_TRUNCATED;
181
10
        goto leave;
182
10
      }
183
184
27.8k
      hpack_debug_printf(" idx=%u ", idx);
185
186
27.8k
      if (!hpack_valid_idx(dht, idx)) {
187
108
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
188
108
        ret = -HPACK_ERR_TOO_LARGE;
189
108
        goto leave;
190
108
      }
191
192
27.7k
      value = hpack_alloc_string(tmp, idx, hpack_idx_to_value(dht, idx));
193
27.7k
      if (!isttest(value)) {
194
16
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
195
16
        ret = -HPACK_ERR_TOO_LARGE;
196
16
        goto leave;
197
16
      }
198
199
      /* here we don't index so we can always keep the pseudo header number */
200
27.7k
      name = ist2(NULL, hpack_idx_to_phdr(idx));
201
202
27.7k
      if (!name.len) {
203
24.7k
        name = hpack_alloc_string(tmp, idx, hpack_idx_to_name(dht, idx));
204
24.7k
        if (!isttest(name)) {
205
14
          hpack_debug_printf("##ERR@%d##\n", __LINE__);
206
14
          ret = -HPACK_ERR_TOO_LARGE;
207
14
          goto leave;
208
14
        }
209
24.7k
      }
210
      /* <name> and <value> are now set and point to stable values */
211
27.7k
    }
212
37.6k
    else if (*raw >= 0x20 && *raw <= 0x3f) {
213
      /* max dyn table size change */
214
632
      hpack_debug_printf("%02x: p18: dynamic table size update : ", code);
215
216
632
      if (ret) {
217
        /* 7541#4.2.1 : DHT size update must only be at the beginning */
218
15
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
219
15
        ret = -HPACK_ERR_TOO_LARGE;
220
15
        goto leave;
221
15
      }
222
223
617
      idx = get_var_int(&raw, &len, 5);
224
617
      if (len == (uint32_t)-1) { // truncated
225
12
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
226
12
        ret = -HPACK_ERR_TRUNCATED;
227
12
        goto leave;
228
12
      }
229
605
      hpack_debug_printf(" new len=%u\n", idx);
230
231
605
      if (idx > dht->size) {
232
59
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
233
59
        ret = -HPACK_ERR_INVALID_ARGUMENT;
234
59
        goto leave;
235
59
      }
236
546
      continue;
237
605
    }
238
37.0k
    else if (!(*raw & (*raw - 0x10))) {
239
      /* 0x00, 0x10, and 0x40 (0x20 and 0x80 were already handled above) */
240
241
      /* literal header field without/never/with incremental indexing -- literal name */
242
3.79k
      if (*raw == 0x00)
243
1.51k
        hpack_debug_printf("%02x: p17: literal without indexing : ", code);
244
2.27k
      else if (*raw == 0x10)
245
60
        hpack_debug_printf("%02x: p18: literal never indexed : ", code);
246
2.21k
      else if (*raw == 0x40)
247
2.21k
        hpack_debug_printf("%02x: p16: literal with indexing : ", code);
248
249
3.79k
      if (*raw == 0x40)
250
2.21k
        must_index = 1;
251
252
3.79k
      raw++; len--;
253
254
      /* retrieve name */
255
3.79k
      if (!len) { // truncated
256
26
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
257
26
        ret = -HPACK_ERR_TRUNCATED;
258
26
        goto leave;
259
26
      }
260
261
3.76k
      huff = *raw & 0x80;
262
3.76k
      nlen = get_var_int(&raw, &len, 7);
263
3.76k
      if (len == (uint32_t)-1 || len < nlen) { // truncated
264
117
        hpack_debug_printf("##ERR@%d## (truncated): nlen=%d len=%d\n",
265
117
                           __LINE__, (int)nlen, (int)len);
266
117
        ret = -HPACK_ERR_TRUNCATED;
267
117
        goto leave;
268
117
      }
269
270
3.64k
      name = ist2(raw, nlen);
271
272
3.64k
      raw += nlen;
273
3.64k
      len -= nlen;
274
275
3.64k
      if (huff) {
276
2.17k
        char *ntrash = chunk_newstr(tmp);
277
2.17k
        if (!ntrash) {
278
2
          hpack_debug_printf("##ERR@%d##\n", __LINE__);
279
2
          ret = -HPACK_ERR_TOO_LARGE;
280
2
          goto leave;
281
2
        }
282
283
2.16k
        nlen = huff_dec((const uint8_t *)name.ptr, name.len, ntrash,
284
2.16k
            tmp->size - tmp->data);
285
2.16k
        if (nlen == (uint32_t)-1) {
286
163
          hpack_debug_printf("2: can't decode huffman.\n");
287
163
          ret = -HPACK_ERR_HUFFMAN;
288
163
          goto leave;
289
163
        }
290
2.00k
        hpack_debug_printf(" [name huff %d->%d] ", (int)name.len, (int)nlen);
291
292
2.00k
        tmp->data += nlen; // make room for the value
293
2.00k
        name = ist2(ntrash, nlen);
294
2.00k
      }
295
296
      /* retrieve value */
297
3.48k
      if (!len) { // truncated
298
115
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
299
115
        ret = -HPACK_ERR_TRUNCATED;
300
115
        goto leave;
301
115
      }
302
303
3.36k
      huff = *raw & 0x80;
304
3.36k
      vlen = get_var_int(&raw, &len, 7);
305
3.36k
      if (len == (uint32_t)-1 || len < vlen) { // truncated
306
116
        hpack_debug_printf("##ERR@%d## : vlen=%d len=%d\n",
307
116
                           __LINE__, (int)vlen, (int)len);
308
116
        ret = -HPACK_ERR_TRUNCATED;
309
116
        goto leave;
310
116
      }
311
312
3.25k
      value = ist2(raw, vlen);
313
3.25k
      raw += vlen;
314
3.25k
      len -= vlen;
315
316
3.25k
      if (huff) {
317
1.64k
        char *vtrash = chunk_newstr(tmp);
318
1.64k
        if (!vtrash) {
319
2
          hpack_debug_printf("##ERR@%d##\n", __LINE__);
320
2
          ret = -HPACK_ERR_TOO_LARGE;
321
2
          goto leave;
322
2
        }
323
324
1.64k
        vlen = huff_dec((const uint8_t *)value.ptr, value.len, vtrash,
325
1.64k
            tmp->size - tmp->data);
326
1.64k
        if (vlen == (uint32_t)-1) {
327
48
          hpack_debug_printf("3: can't decode huffman.\n");
328
48
          ret = -HPACK_ERR_HUFFMAN;
329
48
          goto leave;
330
48
        }
331
1.59k
        hpack_debug_printf(" [value huff %d->%d] ", (int)value.len, (int)vlen);
332
333
1.59k
        tmp->data += vlen; // make room for the value
334
1.59k
        value = ist2(vtrash, vlen);
335
1.59k
      }
336
337
      /* <name> and <value> are correctly filled here */
338
3.25k
    }
339
33.2k
    else {
340
      /* 0x01..0x0f : literal header field without indexing -- indexed name */
341
      /* 0x11..0x1f : literal header field never indexed -- indexed name */
342
      /* 0x41..0x7f : literal header field with incremental indexing -- indexed name */
343
344
33.2k
      if (*raw <= 0x0f)
345
970
        hpack_debug_printf("%02x: p16: literal without indexing -- indexed name : ", code);
346
32.2k
      else if (*raw >= 0x41)
347
31.7k
        hpack_debug_printf("%02x: p15: literal with indexing -- indexed name : ", code);
348
480
      else
349
480
        hpack_debug_printf("%02x: p16: literal never indexed -- indexed name : ", code);
350
351
      /* retrieve name index */
352
33.2k
      if (*raw >= 0x41) {
353
31.7k
        must_index = 1;
354
31.7k
        idx = get_var_int(&raw, &len, 6);
355
31.7k
      }
356
1.45k
      else
357
1.45k
        idx = get_var_int(&raw, &len, 4);
358
359
33.2k
      hpack_debug_printf(" idx=%u ", idx);
360
361
33.2k
      if (len == (uint32_t)-1 || !len) { // truncated
362
97
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
363
97
        ret = -HPACK_ERR_TRUNCATED;
364
97
        goto leave;
365
97
      }
366
367
33.1k
      if (!hpack_valid_idx(dht, idx)) {
368
86
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
369
86
        ret = -HPACK_ERR_TOO_LARGE;
370
86
        goto leave;
371
86
      }
372
373
      /* retrieve value */
374
33.0k
      huff = *raw & 0x80;
375
33.0k
      vlen = get_var_int(&raw, &len, 7);
376
33.0k
      if (len == (uint32_t)-1 || len < vlen) { // truncated
377
117
        hpack_debug_printf("##ERR@%d##\n", __LINE__);
378
117
        ret = -HPACK_ERR_TRUNCATED;
379
117
        goto leave;
380
117
      }
381
382
32.9k
      value = ist2(raw, vlen);
383
32.9k
      raw += vlen;
384
32.9k
      len -= vlen;
385
386
32.9k
      if (huff) {
387
5.49k
        char *vtrash = chunk_newstr(tmp);
388
5.49k
        if (!vtrash) {
389
2
          hpack_debug_printf("##ERR@%d##\n", __LINE__);
390
2
          ret = -HPACK_ERR_TOO_LARGE;
391
2
          goto leave;
392
2
        }
393
394
5.49k
        vlen = huff_dec((const uint8_t *)value.ptr, value.len, vtrash,
395
5.49k
            tmp->size - tmp->data);
396
5.49k
        if (vlen == (uint32_t)-1) {
397
182
          hpack_debug_printf("##ERR@%d## can't decode huffman : ilen=%d osize=%d\n",
398
182
                             __LINE__, (int)value.len,
399
182
                             (int)(tmp->size - tmp->data));
400
182
          hpack_debug_hexdump(stderr, "[HUFFMAN] ", value.ptr, 0, value.len);
401
182
          ret = -HPACK_ERR_HUFFMAN;
402
182
          goto leave;
403
182
        }
404
5.30k
        tmp->data += vlen; // make room for the value
405
5.30k
        value = ist2(vtrash, vlen);
406
5.30k
      }
407
408
32.7k
      name = IST_NULL;
409
32.7k
      if (!must_index)
410
1.27k
        name.len = hpack_idx_to_phdr(idx);
411
412
32.7k
      if (!name.len) {
413
32.0k
        name = hpack_alloc_string(tmp, idx, hpack_idx_to_name(dht, idx));
414
32.0k
        if (!isttest(name)) {
415
17
          hpack_debug_printf("##ERR@%d##\n", __LINE__);
416
17
          ret = -HPACK_ERR_TOO_LARGE;
417
17
          goto leave;
418
17
        }
419
32.0k
      }
420
      /* <name> and <value> are correctly filled here */
421
32.7k
    }
422
423
    /* We must not accept empty header names (forbidden by the spec and used
424
     * as a list termination).
425
     */
426
63.6k
    if (!name.len) {
427
86
      hpack_debug_printf("##ERR@%d##\n", __LINE__);
428
86
      ret = -HPACK_ERR_INVALID_ARGUMENT;
429
86
      goto leave;
430
86
    }
431
432
    /* here's what we have here :
433
     *   - name.len > 0
434
     *   - value is filled with either const data or data allocated from tmp
435
     *   - name.ptr == NULL && !must_index : known pseudo-header #name.len
436
     *   - name.ptr != NULL || must_index : general header, unknown pseudo-header or index needed
437
     */
438
63.5k
    if (ret >= list_size) {
439
2
      hpack_debug_printf("##ERR@%d##\n", __LINE__);
440
2
      ret = -HPACK_ERR_TOO_LARGE;
441
2
      goto leave;
442
2
    }
443
444
63.5k
    list[ret].n = name;
445
63.5k
    list[ret].v = value;
446
63.5k
    ret++;
447
448
63.5k
    if (must_index && hpack_dht_insert(dht, name, value) < 0) {
449
0
      hpack_debug_printf("failed to find some room in the dynamic table\n");
450
0
      ret = -HPACK_ERR_DHT_INSERT_FAIL;
451
0
      goto leave;
452
0
    }
453
454
63.5k
    hpack_debug_printf("\e[1;34m%s\e[0m: ",
455
63.5k
           isttest(name) ? istpad(trash.area, name).ptr : h2_phdr_to_str(name.len));
456
457
63.5k
    hpack_debug_printf("\e[1;35m%s\e[0m [mustidx=%d, used=%d] [n=(%p,%d) v=(%p,%d)]\n",
458
63.5k
           istpad(trash.area, value).ptr, must_index,
459
63.5k
           dht->used,
460
63.5k
           name.ptr, (int)name.len, value.ptr, (int)value.len);
461
63.5k
  }
462
463
729
  if (ret >= list_size) {
464
1
    ret = -HPACK_ERR_TOO_LARGE;
465
1
    goto leave;
466
1
  }
467
468
  /* put an end marker */
469
728
  list[ret].n = list[ret].v = IST_NULL;
470
728
  ret++;
471
472
2.14k
 leave:
473
2.14k
  hpack_debug_printf("-- done: ret=%d list_size=%d --\n", (int)ret, (int)list_size);
474
2.14k
  return ret;
475
728
}