/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 | } |