Skip to content

SIGSEGV in lys_compile_uses() when a grouping with an extension is usesd at module top level #2571

Description

@pkbehera

Summary

lys_compile_uses() segfaults (NULL pointer dereference) when a grouping that has an
extension statement directly attached to it is usesd at module scope (i.e. the uses
statement has no enclosing schema node / parent == NULL). This is a NULL-pointer bug, not
a validation error — libyang should either reject this or (since it's legal YANG) handle it,
not crash.

Reproduces identically on:

  • v2.1.148 (built from source, CMake Release build)
  • v5.8.6 (current latest release as of this report, built from source, CMake Release build)

Both built on Ubuntu 24.04 / x86_64, gcc 13.3.0, against a locally-built PCRE2 10.42. Nothing
platform-specific is expected to matter here.

Minimal reproduction

module bug-repro {
  namespace "urn:example:bug-repro";
  prefix "br";

  extension foo {
    argument value;
  }

  grouping g1 {
    br:foo "bar";
    leaf x {
      type string;
    }
  }

  uses g1;
}
$ yanglint bug-repro.yang
Segmentation fault (core dumped)

No sysrepo, no other imports, no other modules involved — this is the whole reproducer. The
uses g1; at module top level is the trigger; grouping g1's own extension statement
(br:foo "bar";, directly in the grouping body, not on a child node) is the payload that gets
dereferenced through the NULL parent.

Backtrace (v5.8.6, debug build, gdb bt, on the minimal repro above)

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7c87340 in lys_compile_uses (ctx=0x7ffffffface0, uses_p=0x5555555bd0f0, parent=0x0,
    inherited_flags=0, child_set=0x0) at src/schema_compile_node.c:3941
3941        COMPILE_EXTS_GOTO(ctx, grp->exts, parent->exts, parent, rc, cleanup);
#0  0x00007ffff7c87340 in lys_compile_uses (ctx=0x7ffffffface0, uses_p=0x5555555bd0f0, parent=0x0,
    inherited_flags=0, child_set=0x0) at src/schema_compile_node.c:3941
#1  0x00007ffff7c8816d in lys_compile_node (ctx=0x7ffffffface0, pnode=0x5555555bd0f0, parent=0x0,
    inherited_flags=0, child_set=0x0) at src/schema_compile_node.c:4164
#2  0x00007ffff7c73b76 in lys_compile (mod=0x5555555bce00, unres=0x555555579480)
    at src/schema_compile.c:1777
#3  0x00007ffff7c72c67 in lys_compile_depset_r (ctx=0x5555555793e0, dep_set=0x5555555bd190,
    unres=0x555555579440) at src/schema_compile.c:1543
#4  0x00007ffff7c72e61 in lys_compile_depset_all (ctx=0x5555555793e0, unres=0x555555579440)
    at src/schema_compile.c:1610
#5  0x00007ffff7cab937 in lys_parse (ctx=0x5555555793e0, in=0x55555558a3f0, format=LYS_IN_YANG,
    features=0x7fffffffbea0, module=0x7fffffffbe98) at src/tree_schema.c:2878
#6  0x000055555555ac7c in cmd_add_exec (ctx=0x7fffffffbee0, yo=0x7fffffffc3c0,
    posv=0x7fffffffce22 "/tmp/ly_repro/bug-repro.yang") at tools/lint/cmd_add.c:192
#7  0x000055555555970c in process_files (argc=2, argv=0x7fffffffc768, optind=1,
    data_in_format=LYD_UNKNOWN, ctx=0x5555555793e0, yo=0x7fffffffc3c0) at tools/lint/main_ni.c:410
#8  0x000055555555a18f in process_args (argc=2, argv=0x7fffffffc768, yo=0x7fffffffc3c0,
    ctx=0x7fffffffc3b8) at tools/lint/main_ni.c:754
#9  0x000055555555a337 in main_ni (argc=2, argv=0x7fffffffc768) at tools/lint/main_ni.c:786
#10 0x00005555555616e6 in main (argc=2, argv=0x7fffffffc768) at tools/lint/main.c:51

parent is NULL (frame #0/#1's own argument), while grp->exts (inspected directly via gdb)
is non-empty — one lysc_ext_instance for br:foo, confirming the crash line's
COMPILE_EXTS_GOTO is actually attempting the copy, not short-circuiting on an empty array.

On our original, non-minimal repro (a real-world model, several groupings deep, still
ultimately usesd at module scope), the same crash occurs at the same line via a slightly
deeper call chain (lys_compile_useslys_compile_uses_childrenlys_compile_node
lys_compile_uses), but the root cause is identical: parent is NULL by the time this line
runs, for any uses that is (directly or transitively) at module top level. That backtrace,
for reference:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7c87340 in lys_compile_uses (ctx=0x7fffffff9c10, uses_p=0x5555555c8d90, parent=0x0,
    inherited_flags=4, child_set=0x7fffffff9b10) at src/schema_compile_node.c:3941
#0  lys_compile_uses (parent=0x0, ...) at src/schema_compile_node.c:3941
#1  lys_compile_node (parent=0x0, ...) at src/schema_compile_node.c:4164
#2  lys_compile_uses_children (parent=0x0, ...) at src/schema_compile_node.c:3784
#3  lys_compile_uses (parent=0x0, ...) at src/schema_compile_node.c:3899
#4  lys_compile_node (parent=0x0, ...) at src/schema_compile_node.c:4164
#5  lys_compile (...) at src/schema_compile.c:1777
#6  lys_compile_depset_r (...) at src/schema_compile.c:1543
#7  lys_compile_depset_all (...) at src/schema_compile.c:1610
#8  lys_parse (...) at src/tree_schema.c:2878

Root cause

// src/schema_compile_node.c, lys_compile_uses(), current release code:

    /* compile uses and grouping extensions into the parent */
    COMPILE_EXTS_GOTO(ctx, uses_p->exts, parent->exts, parent, rc, cleanup);
    COMPILE_EXTS_GOTO(ctx, grp->exts, parent->exts, parent, rc, cleanup);

Both lines assume parent is non-NULL (they dereference parent->exts and pass parent
itself into the macro). That assumption doesn't hold for a module-top-level uses (no
enclosing container/list/etc. — a perfectly legal construct), and for
uses_p->exts/grp->exts to actually be non-empty someone has to have put an extension
statement on the uses statement itself or on the grouping being used — also both perfectly
legal per RFC 7950 (extensions may appear on uses and on grouping).

Suggested fix

Guard both lines on parent being non-NULL; if there's no parent node to attach the compiled
extension instances to, there's nothing meaningful to do with them at module scope, so skipping
is reasonable (open to a better fix from someone who knows the compiler internals better — this
is what unblocked us, not necessarily the "correct" semantics):

-    /* compile uses and grouping extensions into the parent */
-    COMPILE_EXTS_GOTO(ctx, uses_p->exts, parent->exts, parent, rc, cleanup);
-    COMPILE_EXTS_GOTO(ctx, grp->exts, parent->exts, parent, rc, cleanup);
+    /* compile uses and grouping extensions into the parent, if any -- a top-level
+     * (module-scope) uses has no parent node to attach them to, so skip instead of
+     * crashing (parent==NULL was not guarded here) */
+    if (parent) {
+        COMPILE_EXTS_GOTO(ctx, uses_p->exts, parent->exts, parent, rc, cleanup);
+        COMPILE_EXTS_GOTO(ctx, grp->exts, parent->exts, parent, rc, cleanup);
+    }

Verified this resolves the crash on both the minimal repro above and our original,
much larger real-world model (dozens of imported OpenConfig + vendor modules), on both
v2.1.148 and v5.8.6, with no other observable regressions in our (fairly extensive) schema.

Environment

  • OS: Ubuntu 24.04.x, x86_64
  • Compiler: gcc 13.3.0
  • CMake build type: Release (crash reproduces there) and Debug (used for the backtrace above)
  • PCRE2: 10.42
  • libyang: reproduced on both v2.1.148 and v5.8.6 (git tags), built from source with default

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions