Skip to content

[Bug] Null Pointer Dereference in parser.c — first=1 path of 4 collection-entry functions #337

Description

@BoxStrikesTeam

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions