Skip to content

Commit 6d12dd8

Browse files
committed
Don't allocate memory when performing hpack huffman decoding
Use a ring buffer instead. Hopefully this should increase the fuzzing throughput.
1 parent 3508980 commit 6d12dd8

File tree

4 files changed

+205
-110
lines changed

4 files changed

+205
-110
lines changed

src/bin/fuzz/h2_huffman_fuzzer.cc

+3-8
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22
#include <stdlib.h>
33

44
extern "C" {
5-
uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input,
6-
size_t input_len);
5+
bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input,
6+
size_t input_len);
77

88
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
99
{
10-
uint8_t *decoded = lwan_h2_huffman_decode_for_fuzzing(data, size);
11-
if (decoded) {
12-
free(decoded);
13-
return 0;
14-
}
15-
return 1;
10+
return lwan_h2_huffman_decode_for_fuzzing(data, size) == true;
1611
}
1712
}

src/lib/lwan-h2-huffman.c

+67-43
Original file line numberDiff line numberDiff line change
@@ -305,86 +305,97 @@ static inline bool consume(struct bit_reader *reader, int count)
305305
return reader->total_bitcount > 0;
306306
}
307307

308-
static inline size_t output_size(size_t input_size)
308+
DEFINE_RING_BUFFER_TYPE(uint8_ring_buffer, uint8_t, 64)
309+
310+
struct lwan_h2_huffman_decoder {
311+
struct bit_reader bit_reader;
312+
struct uint8_ring_buffer buffer;
313+
};
314+
315+
void lwan_h2_huffman_init(struct lwan_h2_huffman_decoder *huff,
316+
const uint8_t *input,
317+
size_t input_len)
309318
{
310-
/* Smallest input is 5 bits which produces 8 bits. Scaling that to 8 bits,
311-
* we get 12.8 bits of output per 8 bits of input. */
312-
return (input_size * 128) / 10;
319+
huff->bit_reader = (struct bit_reader){
320+
.bitptr = input,
321+
.total_bitcount = (int64_t)input_len * 8,
322+
};
323+
uint8_ring_buffer_init(&huff->buffer);
313324
}
314325

315-
uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input,
316-
size_t input_len)
326+
ssize_t lwan_h2_huffman_next(struct lwan_h2_huffman_decoder *huff)
317327
{
318-
uint8_t *output = malloc(output_size(input_len));
319-
uint8_t *ret = output;
320-
struct bit_reader bit_reader = {.bitptr = input,
321-
.total_bitcount = (int64_t)input_len * 8};
328+
struct bit_reader *reader = &huff->bit_reader;
329+
struct uint8_ring_buffer *buffer = &huff->buffer;
322330

323-
while (bit_reader.total_bitcount > 7) {
324-
uint8_t peeked_byte = peek_byte(&bit_reader);
331+
while (reader->total_bitcount > 7) {
332+
if (uint8_ring_buffer_full(buffer))
333+
goto done;
334+
335+
uint8_t peeked_byte = peek_byte(reader);
325336
if (LIKELY(level0[peeked_byte].num_bits)) {
326-
*output++ = level0[peeked_byte].symbol;
327-
consume(&bit_reader, level0[peeked_byte].num_bits);
337+
uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol);
338+
consume(reader, level0[peeked_byte].num_bits);
328339
assert(bit_reader.total_bitcount >= 0);
329340
continue;
330341
}
331342

332-
if (!consume(&bit_reader, 8))
333-
goto fail;
343+
if (!consume(reader, 8))
344+
return -1;
334345

335346
const struct h2_huffman_code *level1 = next_level0(peeked_byte);
336-
peeked_byte = peek_byte(&bit_reader);
347+
peeked_byte = peek_byte(reader);
337348
if (level1[peeked_byte].num_bits) {
338-
*output++ = level1[peeked_byte].symbol;
339-
if (!consume(&bit_reader, level1[peeked_byte].num_bits))
340-
goto fail;
349+
uint8_ring_buffer_put_copy(buffer, level1[peeked_byte].symbol);
350+
if (!consume(reader, level1[peeked_byte].num_bits))
351+
return -1;
341352
continue;
342353
}
343354

344-
if (!consume(&bit_reader, 8))
345-
goto fail;
355+
if (!consume(reader, 8))
356+
return -1;
346357

347358
const struct h2_huffman_code *level2 = next_level1(peeked_byte);
348-
peeked_byte = peek_byte(&bit_reader);
359+
peeked_byte = peek_byte(reader);
349360
if (level2[peeked_byte].num_bits) {
350-
*output++ = level2[peeked_byte].symbol;
351-
if (!consume(&bit_reader, level2[peeked_byte].num_bits))
352-
goto fail;
361+
uint8_ring_buffer_put_copy(buffer, level2[peeked_byte].symbol);
362+
if (!consume(reader, level2[peeked_byte].num_bits))
363+
return -1;
353364
continue;
354365
}
355366

356-
if (!consume(&bit_reader, 8))
367+
if (!consume(reader, 8))
357368
goto fail;
358369

359370
const struct h2_huffman_code *level3 = next_level2(peeked_byte);
360371
if (LIKELY(level3)) {
361-
peeked_byte = peek_byte(&bit_reader);
372+
peeked_byte = peek_byte(reader);
362373
if (level3[peeked_byte].num_bits < 0) {
363374
/* EOS found */
364-
return ret;
375+
goto done;
365376
}
366377
if (LIKELY(level3[peeked_byte].num_bits)) {
367-
*output++ = level3[peeked_byte].symbol;
368-
if (!consume(&bit_reader, level3[peeked_byte].num_bits))
369-
goto fail;
378+
uint8_ring_buffer_put_copy(buffer, level3[peeked_byte].symbol);
379+
if (!consume(reader, level3[peeked_byte].num_bits))
380+
return -1;
370381
continue;
371382
}
372383
}
373384

374-
goto fail;
385+
return -1;
375386
}
376387

377388
/* FIXME: ensure we're not promoting types unnecessarily here */
378-
if (bit_reader.total_bitcount) {
379-
const uint8_t peeked_byte = peek_byte(&bit_reader);
389+
if (reader->total_bitcount) {
390+
const uint8_t peeked_byte = peek_byte(reader);
380391
const uint8_t eos_prefix = ((1 << bit_reader.total_bitcount) - 1)
381392
<< (8 - bit_reader.total_bitcount);
382393

383394
if ((peeked_byte & eos_prefix) == eos_prefix)
384395
goto done;
385396

386-
if (level0[peeked_byte].num_bits == (int8_t)bit_reader.total_bitcount) {
387-
*output = level0[peeked_byte].symbol;
397+
if (level0[peeked_byte].num_bits == (int8_t)reader->total_bitcount) {
398+
uint8_ring_buffer_put_copy(buffer, level0[peeked_byte].symbol);
388399
goto done;
389400
}
390401

@@ -393,15 +404,28 @@ uint8_t *lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input,
393404
* - Incomplete sequence
394405
* - Has overlong padding
395406
*/
396-
goto fail;
407+
return -1;
397408
}
398409

399410
done:
400-
return ret;
401-
402-
fail:
403-
free(ret);
404-
return NULL;
411+
return (ssize_t)uint8_ring_buffer_size(buffer);
405412
}
406413

414+
bool lwan_h2_huffman_decode_for_fuzzing(const uint8_t *input, size_t input_len)
415+
{
416+
struct lwan_h2_huffman_decoder decoder;
417+
418+
lwan_h2_huffman_init(&decoder, input, input_len);
419+
420+
while (true) {
421+
ssize_t n_decoded = lwan_h2_huffman_next(&decoder);
422+
423+
if (UNLIKELY(n_decoded < 0))
424+
return false;
425+
if (n_decoded < 64)
426+
return true;
427+
428+
uint8_ring_buffer_init(&decoder->buffer);
429+
}
430+
}
407431
#endif

src/lib/ringbuffer.h

+22
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@
7676
memcpy(&rb->array[type_name_##_mask(rb->write++)], e, sizeof(*e)); \
7777
} \
7878
\
79+
__attribute__((unused)) static inline void type_name_##_put_copy( \
80+
struct type_name_ *rb, const element_type_ e) \
81+
{ \
82+
assert(!type_name_##_full(rb)); \
83+
rb->array[type_name_##_mask(rb->write++)] = e; \
84+
} \
85+
\
7986
__attribute__((unused)) static inline bool type_name_##_try_put( \
8087
struct type_name_ *rb, const element_type_ *e) \
8188
{ \
@@ -93,6 +100,21 @@
93100
return rb->array[type_name_##_mask(rb->read++)]; \
94101
} \
95102
\
103+
__attribute__((unused)) static inline void type_name_##_consume( \
104+
struct type_name_ *rb, element_type_ *buffer, uint32_t entries) \
105+
{ \
106+
assert(type_name_##_size(rb) >= entries); \
107+
const uint32_t mask = type_name_##_mask(rb->read); \
108+
if (mask && entries > mask) { \
109+
memcpy(buffer, &rb->array[rb->read], \
110+
sizeof(*buffer) * (entries - mask)); \
111+
memcpy(buffer + mask, &rb->array[0], sizeof(*buffer) * mask); \
112+
} else { \
113+
memcpy(buffer, &rb->array[rb->read], sizeof(*buffer) * entries); \
114+
} \
115+
rb->read += entries; \
116+
} \
117+
\
96118
__attribute__((unused)) static inline element_type_ *type_name_##_get_ptr( \
97119
struct type_name_ *rb) \
98120
{ \

0 commit comments

Comments
 (0)