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_uses → lys_compile_uses_children → lys_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
Summary
lys_compile_uses()segfaults (NULL pointer dereference) when agroupingthat has anextension statement directly attached to it is
usesd at module scope (i.e. theusesstatement has no enclosing schema node /
parent == NULL). This is a NULL-pointer bug, nota validation error — libyang should either reject this or (since it's legal YANG) handle it,
not crash.
Reproduces identically on:
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
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 getsdereferenced through the NULL parent.
Backtrace (v5.8.6, debug build, gdb
bt, on the minimal repro above)parentisNULL(frame #0/#1's own argument), whilegrp->exts(inspected directly via gdb)is non-empty — one
lysc_ext_instanceforbr:foo, confirming the crash line'sCOMPILE_EXTS_GOTOis 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 slightlydeeper call chain (
lys_compile_uses→lys_compile_uses_children→lys_compile_node→lys_compile_uses), but the root cause is identical:parentis NULL by the time this lineruns, for any
usesthat is (directly or transitively) at module top level. That backtrace,for reference:
Root cause
Both lines assume
parentis non-NULL (they dereferenceparent->extsand passparentitself into the macro). That assumption doesn't hold for a module-top-level
uses(noenclosing container/list/etc. — a perfectly legal construct), and for
uses_p->exts/grp->extsto actually be non-empty someone has to have put an extensionstatement on the
usesstatement itself or on the grouping being used — also both perfectlylegal per RFC 7950 (extensions may appear on
usesand ongrouping).Suggested fix
Guard both lines on
parentbeing non-NULL; if there's no parent node to attach the compiledextension 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):
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