Summary
Four functions in src/parser.c call PEEK_TOKEN(parser) inside a if (first) block and immediately dereference the result without checking for NULL. PEEK_TOKEN can return NULL on a read or memory error. When this happens, the next line accesses token->start_mark, causing a null pointer dereference / segfault.
Affected Version
v0.2.6-rc.1 (latest commit)
Affected File & Lines
File: src/parser.c
| Function |
Line |
Vulnerable code |
yaml_parser_parse_block_sequence_entry |
740–741 |
token = PEEK_TOKEN(parser); → token->start_mark |
yaml_parser_parse_block_mapping_key |
848–849 |
token = PEEK_TOKEN(parser); → token->start_mark |
yaml_parser_parse_flow_sequence_entry |
963–964 |
token = PEEK_TOKEN(parser); → token->start_mark |
yaml_parser_parse_flow_mapping_key |
1119–1120 |
token = PEEK_TOKEN(parser); → token->start_mark |
Root Cause
PEEK_TOKEN is defined as (parser.c line 48):
#define PEEK_TOKEN(parser) \
((parser->token_available || yaml_parser_fetch_more_tokens(parser)) ? \
parser->tokens.head : NULL)
It returns NULL when yaml_parser_fetch_more_tokens fails (e.g. read error, memory allocation failure, or malformed input that causes the scanner to return early).
All four functions follow the same unsafe pattern inside their if (first) branch:
if (first) {
token = PEEK_TOKEN(parser); // ← can return NULL
if (!PUSH(parser, parser->marks, token->start_mark)) // ← NPD if token == NULL
return 0;
SKIP_TOKEN(parser);
}
Notably, every other PEEK_TOKEN call in these same functions is guarded correctly:
token = PEEK_TOKEN(parser);
if (!token) return 0; // ← guard present everywhere else
The first branch is the sole exception.
Contrast with correct usage
The very next PEEK_TOKEN call in each function (outside the if (first) block) is properly guarded:
// yaml_parser_parse_block_sequence_entry
if (first) {
token = PEEK_TOKEN(parser); // NO check ← BUG
if (!PUSH(parser, parser->marks, token->start_mark))
return 0;
SKIP_TOKEN(parser);
}
token = PEEK_TOKEN(parser);
if (!token) return 0; // check present ← correct
How to trigger
The first=1 path is taken the very first time each collection is entered. A truncated or malformed YAML stream that causes the scanner to fail (return error / EOF) during that first token fetch will hit the NULL dereference before any graceful error handling occurs.
Minimal malformed YAML examples likely to exercise each path:
# Block sequence (triggers yaml_parser_parse_block_sequence_entry)
- [truncated here
# Block mapping (triggers yaml_parser_parse_block_mapping_key)
key: [truncated here
# Flow sequence (triggers yaml_parser_parse_flow_sequence_entry)
[truncated
# Flow mapping (triggers yaml_parser_parse_flow_mapping_key)
{truncated
Paired with a read handler that returns an error mid-stream, any of these will trigger the bug.
Suggested Fix
Add a NULL guard immediately after each PEEK_TOKEN call inside the if (first) block, consistent with every other call site in the file:
// Before (buggy):
if (first) {
token = PEEK_TOKEN(parser);
if (!PUSH(parser, parser->marks, token->start_mark))
return 0;
SKIP_TOKEN(parser);
}
// After (fixed):
if (first) {
token = PEEK_TOKEN(parser);
if (!token) return 0; // ← add this guard
if (!PUSH(parser, parser->marks, token->start_mark))
return 0;
SKIP_TOKEN(parser);
}
This one-line fix must be applied to all four functions:
yaml_parser_parse_block_sequence_entry (line ~741)
yaml_parser_parse_block_mapping_key (line ~849)
yaml_parser_parse_flow_sequence_entry (line ~964)
yaml_parser_parse_flow_mapping_key (line ~1120)
Environment
- Library: libyaml master branch
- File:
src/parser.c
- Compiler: Any C compiler (bug is logic-level, not platform-specific
Summary
Four functions in
src/parser.ccallPEEK_TOKEN(parser)inside aif (first)block and immediately dereference the result without checking for NULL.PEEK_TOKENcan returnNULLon a read or memory error. When this happens, the next line accessestoken->start_mark, causing a null pointer dereference / segfault.Affected Version
v0.2.6-rc.1(latest commit)Affected File & Lines
File:
src/parser.cyaml_parser_parse_block_sequence_entrytoken = PEEK_TOKEN(parser);→token->start_markyaml_parser_parse_block_mapping_keytoken = PEEK_TOKEN(parser);→token->start_markyaml_parser_parse_flow_sequence_entrytoken = PEEK_TOKEN(parser);→token->start_markyaml_parser_parse_flow_mapping_keytoken = PEEK_TOKEN(parser);→token->start_markRoot Cause
PEEK_TOKENis defined as (parser.c line 48):It returns
NULLwhenyaml_parser_fetch_more_tokensfails (e.g. read error, memory allocation failure, or malformed input that causes the scanner to return early).All four functions follow the same unsafe pattern inside their
if (first)branch:Notably, every other
PEEK_TOKENcall in these same functions is guarded correctly:The
firstbranch is the sole exception.Contrast with correct usage
The very next
PEEK_TOKENcall in each function (outside theif (first)block) is properly guarded:How to trigger
The
first=1path is taken the very first time each collection is entered. A truncated or malformed YAML stream that causes the scanner to fail (return error / EOF) during that first token fetch will hit the NULL dereference before any graceful error handling occurs.Minimal malformed YAML examples likely to exercise each path:
Paired with a read handler that returns an error mid-stream, any of these will trigger the bug.
Suggested Fix
Add a NULL guard immediately after each
PEEK_TOKENcall inside theif (first)block, consistent with every other call site in the file:This one-line fix must be applied to all four functions:
yaml_parser_parse_block_sequence_entry(line ~741)yaml_parser_parse_block_mapping_key(line ~849)yaml_parser_parse_flow_sequence_entry(line ~964)yaml_parser_parse_flow_mapping_key(line ~1120)Environment
src/parser.c