Summary
projects/libyaml/libyaml_dumper_fuzzer.c in google/oss-fuzz leaks the entire yaml_parser_t (68 KB, 8 internal buffers) when yaml_emitter_initialize() fails. Line 227 returns without calling yaml_parser_delete(). All five other fuzzers in the same project that use this init pattern handle it correctly.
Bug Description
Line 222–227 problem:
if (!yaml_parser_initialize(&parser)) // line 222 — allocates 8 internal buffers
return 0;
yaml_parser_set_input_string(&parser, data, size);
if (!yaml_emitter_initialize(&emitter))
return 0; // BUG: parser (68 KB) leaked — yaml_parser_delete never called
yaml_parser_initialize() allocates 8 internal buffers totaling 68 KB. If yaml_emitter_initialize() then fails, the function returns immediately without cleanup.
All five other fuzzers in the same project handle this correctly:
// libyaml_emitter_fuzzer.c (line 228–231):
if (!yaml_emitter_initialize(&emitter)) {
yaml_parser_delete(&parser);
return 0;
}
// libyaml_deconstructor_fuzzer.c (line 49–52):
if (!yaml_emitter_initialize(&emitter)) {
yaml_parser_delete(&parser);
return 1;
}
// libyaml_deconstructor_alt_fuzzer.c (line 51–54):
if (!yaml_emitter_initialize(&emitter)) {
yaml_parser_delete(&parser);
return 1;
}
// libyaml_reformatter_fuzzer.c (line 45–46):
if (!yaml_emitter_initialize(&emitter))
goto cleanup_parser; // jumps to yaml_parser_delete
// libyaml_reformatter_alt_fuzzer.c (line 45–46):
if (!yaml_emitter_initialize(&emitter))
goto cleanup_parser; // jumps to yaml_parser_delete
libyaml_dumper_fuzzer.c is the only one that forgot the cleanup.
Evidence
PoC uses gcc -Wl,--wrap=malloc to fail the first malloc of yaml_emitter_initialize (call #9), so yaml_parser_initialize succeeds but yaml_emitter_initialize returns 0. The same failure is applied to both the buggy and fixed versions.
git clone --depth 1 https://github.com/yaml/libyaml && cd libyaml
mkdir build && cd build && cmake -DBUILD_SHARED_LIBS=OFF .. && make -j$(nproc)
# place poc_dumper_leak.c here
gcc -g -O0 poc_dumper_leak.c libyaml.a -I../include -Wl,--wrap=malloc -o poc_dumper_leak
valgrind --leak-check=full --show-leak-kinds=all ./poc_dumper_leak
Original (buggy):
==3002666== HEAP SUMMARY:
==3002666== in use at exit: 68,224 bytes in 8 blocks
==3002666== total heap usage: 24 allocs, 16 frees, 204,672 bytes allocated
==3002666==
==3002666== 16,384 bytes in 1 blocks are definitely lost in loss record 7 of 8
==3002666== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3002666== by 0x109359: __wrap_malloc (poc_dumper_leak.c:31)
==3002666== by 0x10976A: yaml_malloc (api.c:33)
==3002666== by 0x109CD9: yaml_parser_initialize (api.c:182)
==3002666== by 0x109396: buggy_dumper_init (poc_dumper_leak.c:40)
==3002666== by 0x109627: main (poc_dumper_leak.c:111)
==3002666==
==3002666== 49,152 bytes in 1 blocks are definitely lost in loss record 8 of 8
==3002666== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3002666== by 0x109359: __wrap_malloc (poc_dumper_leak.c:31)
==3002666== by 0x10976A: yaml_malloc (api.c:33)
==3002666== by 0x109D65: yaml_parser_initialize (api.c:184)
==3002666== by 0x109396: buggy_dumper_init (poc_dumper_leak.c:40)
==3002666== by 0x109627: main (poc_dumper_leak.c:111)
==3002666==
==3002666== LEAK SUMMARY:
==3002666== definitely lost: 68,224 bytes in 8 blocks
==3002666== indirectly lost: 0 bytes in 0 blocks
==3002666== possibly lost: 0 bytes in 0 blocks
==3002666== still reachable: 0 bytes in 0 blocks
==3002666== suppressed: 0 bytes in 0 blocks
==3002666==
==3002666== ERROR SUMMARY: 8 errors from 8 contexts (suppressed: 0 from 0)
Fixed (same malloc failure injected):
==3003496== HEAP SUMMARY:
==3003496== in use at exit: 0 bytes in 0 blocks
==3003496== total heap usage: 16 allocs, 16 frees, 136,448 bytes allocated
==3003496==
==3003496== All heap blocks were freed -- no leaks are possible
==3003496==
==3003496== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
| Metric |
Original |
Fixed |
| allocs / frees |
24 / 16 |
16 / 16 |
| definitely lost |
68,224 bytes in 8 blocks |
0 bytes |
All 8 leaked blocks are parser internal buffers allocated by yaml_parser_initialize inside buggy_dumper_init, confirming the missing yaml_parser_delete.
Fix
--- a/projects/libyaml/libyaml_dumper_fuzzer.c
+++ b/projects/libyaml/libyaml_dumper_fuzzer.c
@@ -224,7 +224,10 @@
yaml_parser_set_input_string(&parser, data, size);
- if (!yaml_emitter_initialize(&emitter))
- return 0;
+ if (!yaml_emitter_initialize(&emitter)) {
+ yaml_parser_delete(&parser);
+ return 0;
+ }
Consistent with how all five other fuzzers in the same project handle this identical pattern.
poc_dumper_leak.c (click to expand)
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "yaml.h"
extern void *__real_malloc(size_t size);
static int g_malloc_count = 0;
static int g_fail_at = -1;
void *__wrap_malloc(size_t size)
{
g_malloc_count++;
if (g_fail_at > 0 && g_malloc_count == g_fail_at) {
fprintf(stderr, " [wrap] malloc #%d (size=%zu) -> NULL\n",
g_malloc_count, size);
return NULL;
}
return __real_malloc(size);
}
static int buggy_dumper_init(const uint8_t *data, size_t size)
{
yaml_parser_t parser;
yaml_emitter_t emitter;
if (!yaml_parser_initialize(&parser))
return 0;
yaml_parser_set_input_string(&parser, data, size);
if (!yaml_emitter_initialize(&emitter))
return 0; /* BUG: parser leaked */
yaml_emitter_delete(&emitter);
yaml_parser_delete(&parser);
return 0;
}
static int fixed_dumper_init(const uint8_t *data, size_t size)
{
yaml_parser_t parser;
yaml_emitter_t emitter;
if (!yaml_parser_initialize(&parser))
return 0;
yaml_parser_set_input_string(&parser, data, size);
if (!yaml_emitter_initialize(&emitter)) {
yaml_parser_delete(&parser); /* FIX */
return 0;
}
yaml_emitter_delete(&emitter);
yaml_parser_delete(&parser);
return 0;
}
int main(void)
{
const uint8_t data[] = "00key: value\n";
size_t size = sizeof(data) - 1;
/* Count mallocs for parser init */
g_fail_at = -1; g_malloc_count = 0;
{ yaml_parser_t p; yaml_parser_initialize(&p); yaml_parser_delete(&p); }
int n = g_malloc_count;
/* Fail first malloc of yaml_emitter_initialize */
g_fail_at = n + 1; g_malloc_count = 0;
buggy_dumper_init(data, size);
g_fail_at = n + 1; g_malloc_count = 0;
fixed_dumper_init(data, size);
return 0;
}
Summary
projects/libyaml/libyaml_dumper_fuzzer.cin google/oss-fuzz leaks the entireyaml_parser_t(68 KB, 8 internal buffers) whenyaml_emitter_initialize()fails. Line 227 returns without callingyaml_parser_delete(). All five other fuzzers in the same project that use this init pattern handle it correctly.Bug Description
Line 222–227 problem:
yaml_parser_initialize()allocates 8 internal buffers totaling 68 KB. Ifyaml_emitter_initialize()then fails, the function returns immediately without cleanup.All five other fuzzers in the same project handle this correctly:
libyaml_dumper_fuzzer.cis the only one that forgot the cleanup.Evidence
PoC uses
gcc -Wl,--wrap=mallocto fail the first malloc ofyaml_emitter_initialize(call #9), soyaml_parser_initializesucceeds butyaml_emitter_initializereturns 0. The same failure is applied to both the buggy and fixed versions.Original (buggy):
Fixed (same malloc failure injected):
All 8 leaked blocks are parser internal buffers allocated by
yaml_parser_initializeinsidebuggy_dumper_init, confirming the missingyaml_parser_delete.Fix
Consistent with how all five other fuzzers in the same project handle this identical pattern.
poc_dumper_leak.c (click to expand)