Skip to content

# Harden LYB parsing against truncated memory input#2538

Open
Alearner12 wants to merge 1 commit into
CESNET:masterfrom
Alearner12:fix/lyb-bounds-check
Open

# Harden LYB parsing against truncated memory input#2538
Alearner12 wants to merge 1 commit into
CESNET:masterfrom
Alearner12:fix/lyb-bounds-check

Conversation

@Alearner12

Copy link
Copy Markdown

Harden LYB parsing against truncated memory input

Summary

This change hardens the LYB data parser against arithmetic and bounds issues when parsing binary data from memory-backed inputs.

  • Adds length-aware memory input helpers so binary parsers can be given an explicit buffer size.
  • Rejects LYB parsing from legacy unbounded memory inputs, which are intended for NULL-terminated text data.
  • Checks all LYB reads and skips for end-of-input before copying or advancing the input cursor.
  • Propagates truncated-input failures through LYB count, size, string, value, hash, header, and node parsing paths.
  • Converts malformed schema-hash collision/prefix cases into validation errors instead of parser assertions.
  • Adds a dedicated LYB memory fuzz target that exercises the length-aware memory parsing path.

Bug

The LYB parser reads a binary format, but memory-backed inputs created with ly_in_new_memory() do not carry an explicit length. That API is suitable for NULL-terminated text formats, but LYB data can contain embedded NULL bytes and must be parsed using a bounded byte buffer.

Before this change, lyb_read() called ly_in_read() and ly_in_skip() without propagating short-read failures. For unbounded memory inputs, the low-level input code had no buffer length to check, so truncated LYB data passed through public memory parsing APIs could make the parser read past the allocated input buffer.

Affected paths included:

  • LYB header and context-hash reads.
  • Node type and flags reads.
  • Variable-length count and size decoding.
  • String and value payload reads.
  • Schema hash collision-chain decoding.

Malformed inputs could also reach assertion-only schema hash checks, causing aborts instead of clean parser errors.

Fix

This PR makes LYB binary memory parsing explicit and fail-closed:

  • struct ly_in now records whether an input is bounded by length.
  • File and file-descriptor inputs are marked bounded because they are backed by mapped file lengths.
  • New ly_in_new_memory_len() API creates bounded memory inputs.
  • New lyd_parse_data_mem_len() wraps bounded memory parsing for callers that parse data directly from byte buffers.
  • ly_in_read(), ly_in_peek(), and ly_in_skip() check bounded inputs before reading or advancing.
  • lyd_parse_lyb() rejects LY_IN_MEMORY inputs that do not have an explicit length.
  • lyb_read(), lyb_read_count(), and lyb_read_size() now return LY_ERR, and their callers propagate errors.
  • Truncated LYB data now reports Unexpected end of LYB data. instead of reading past the input buffer.

This keeps existing text-format memory parsing behavior intact while requiring LYB callers to use a length-aware memory input path.

Tests

Added fuzz coverage:

  • lyd_parse_mem_lyb

Local verification performed:

  • Built libyang with ASan/UBSan enabled.
  • Confirmed a legacy unbounded memory LYB repro now fails closed with LYB memory input requires an explicit input length.
  • Confirmed bounded memory LYB parsing accepts a valid LYB sample.
  • Confirmed truncated bounded-memory LYB samples return parser errors without ASan reports.
  • Confirmed file-backed yanglint parsing of the same truncated LYB samples returns cleanly without sanitizer reports or assertion failures.

## Summary

This change hardens the LYB data parser against arithmetic and bounds issues when parsing binary data from memory-backed inputs.

- Adds length-aware memory input helpers so binary parsers can be given an explicit buffer size.
- Rejects LYB parsing from legacy unbounded memory inputs, which are intended for NULL-terminated text data.
- Checks all LYB reads and skips for end-of-input before copying or advancing the input cursor.
- Propagates truncated-input failures through LYB count, size, string, value, hash, header, and node parsing paths.
- Converts malformed schema-hash collision/prefix cases into validation errors instead of parser assertions.
- Adds a dedicated LYB memory fuzz target that exercises the length-aware memory parsing path.

## Bug

The LYB parser reads a binary format, but memory-backed inputs created with `ly_in_new_memory()` do not carry an explicit length. That API is suitable for NULL-terminated text formats, but LYB data can contain embedded NULL bytes and must be parsed using a bounded byte buffer.

Before this change, `lyb_read()` called `ly_in_read()` and `ly_in_skip()` without propagating short-read failures. For unbounded memory inputs, the low-level input code had no buffer length to check, so truncated LYB data passed through public memory parsing APIs could make the parser read past the allocated input buffer.

Affected paths included:

- LYB header and context-hash reads.
- Node type and flags reads.
- Variable-length count and size decoding.
- String and value payload reads.
- Schema hash collision-chain decoding.

Malformed inputs could also reach assertion-only schema hash checks, causing aborts instead of clean parser errors.

## Fix

This PR makes LYB binary memory parsing explicit and fail-closed:

- `struct ly_in` now records whether an input is bounded by `length`.
- File and file-descriptor inputs are marked bounded because they are backed by mapped file lengths.
- New `ly_in_new_memory_len()` API creates bounded memory inputs.
- New `lyd_parse_data_mem_len()` wraps bounded memory parsing for callers that parse data directly from byte buffers.
- `ly_in_read()`, `ly_in_peek()`, and `ly_in_skip()` check bounded inputs before reading or advancing.
- `lyd_parse_lyb()` rejects `LY_IN_MEMORY` inputs that do not have an explicit length.
- `lyb_read()`, `lyb_read_count()`, and `lyb_read_size()` now return `LY_ERR`, and their callers propagate errors.
- Truncated LYB data now reports `Unexpected end of LYB data.` instead of reading past the input buffer.

This keeps existing text-format memory parsing behavior intact while requiring LYB callers to use a length-aware memory input path.

## Files

- `src/in_internal.h`
- `src/in.h`
- `src/in.c`
- `src/parser_data.h`
- `src/tree_data.c`
- `src/parser_lyb.c`
- `tests/fuzz/CMakeLists.txt`
- `tests/fuzz/lyd_parse_mem_lyb.c`

## Tests

Added fuzz coverage:

- `lyd_parse_mem_lyb`

Local verification performed:

- Built libyang with ASan/UBSan enabled.
- Confirmed a legacy unbounded memory LYB repro now fails closed with `LYB memory input requires an explicit input length.`
- Confirmed bounded memory LYB parsing accepts a valid LYB sample.
- Confirmed truncated bounded-memory LYB samples return parser errors without ASan reports.
- Confirmed file-backed `yanglint` parsing of the same truncated LYB samples returns cleanly without sanitizer reports or assertion failures.

Not run locally:

```bash
ctest
```

CMocka was not available in my local environment, so the full upstream test suite should be run by CI or by a maintainer with the project test dependencies installed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant