Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:libyang
User: [email protected]
Usertags: pu

[ Reason ]
Fix CVE-2026-41401 and CVE-2026-44673.

[ Impact ]
Users are vulnerable to heap use-after-free write vulnerability (crash or
potential code execution, CVE-2026-41401) and a heap buffer overflow
(crash or heap corruption, CVE-2026-44673).

[ Tests ]
Both patches have a test included. One from upstream, one developed for this
stable update.

[ Risks ]
The production code changes are trivial and tested thoroughly via the
regression tests.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
The CVE-2026-41401 fix is taken from upstream directly, CVE-2026-44673 is
backported as the affected parser has changed compared to the upstream change
and the code has moved.
diff -Nru libyang-3.12.2/debian/changelog libyang-3.12.2/debian/changelog
--- libyang-3.12.2/debian/changelog     2025-05-02 04:15:49.000000000 +0200
+++ libyang-3.12.2/debian/changelog     2026-10-18 01:45:29.000000000 +0200
@@ -1,3 +1,15 @@
+libyang (3.12.2-1+deb13u1) trixie; urgency=medium
+
+  * Apply CVE-2026-41401 fix (Closes: #1149179).
+  * Backport CVE-2026-44673 fix (LYB parser integer overflow / heap
+    buffer overflow in lyb_read_string() and lyb_read_term_value()):
+    adapted from upstream commit 73da1db8 (Closes: #1137026).
+  * Add a regression test (test_cve_2026_44673 in test_lyb) that crafts
+    the overflow LYB lengths from the advisory and checks they are now
+    rejected instead of overflowing a heap allocation.
+
+ -- Bastian Germann <[email protected]>  Sun, 18 Oct 2026 01:45:29 +0200
+
 libyang (3.12.2-1) sid; urgency=medium
 
   * Simplifying watch file.
diff -Nru libyang-3.12.2/debian/patches/CVE-2026-41401.patch 
libyang-3.12.2/debian/patches/CVE-2026-41401.patch
--- libyang-3.12.2/debian/patches/CVE-2026-41401.patch  1970-01-01 
01:00:00.000000000 +0100
+++ libyang-3.12.2/debian/patches/CVE-2026-41401.patch  2026-10-18 
01:19:24.000000000 +0200
@@ -0,0 +1,31 @@
+Origin: upstream, 54c3276d871023da266d4ed3ceaee7e8d71d0b04
+From: Michal Vasko <[email protected]>
+Date: Thu, 26 Mar 2026 08:33:44 +0100
+Subject: parser common BUGFIX invalid metadata removal
+---
+diff --git a/src/parser_common.c b/src/parser_common.c
+index 39fb4ab0ab..f11fce5604 100644
+--- a/src/parser_common.c
++++ b/src/parser_common.c
+@@ -313,8 +313,8 @@ lyd_parser_set_data_flags(struct lyd_node *node, struct 
lyd_meta **meta, struct
+             next_meta = meta2->next;
+ 
+             /* delete the metadata */
+-            if (meta != &node->meta) {
+-                *meta = (*meta)->next;
++            if ((meta != &node->meta) && (meta2 == *meta)) {
++                *meta = next_meta;
+             }
+             lyd_free_meta_single(meta2);
+             if (prev_meta) {
+diff --git a/tests/fuzz/corpus/lyd_parse_mem_xml/advisory2026_03_26 
b/tests/fuzz/corpus/lyd_parse_mem_xml/advisory2026_03_26
+new file mode 100644
+index 0000000000..7e3b6c39ee
+--- /dev/null
++++ b/tests/fuzz/corpus/lyd_parse_mem_xml/advisory2026_03_26
+@@ -0,0 +1,5 @@
++<int32 xmlns="urn:tests:types"
++       xmlns:yang="urn:ietf:params:xml:ns:yang:1"
++       xmlns:dflt="urn:ietf:params:xml:ns:netconf:default:1.0"
++       yang:operation="create"
++       dflt:default="true">5</int32>
diff -Nru libyang-3.12.2/debian/patches/CVE-2026-44673.patch 
libyang-3.12.2/debian/patches/CVE-2026-44673.patch
--- libyang-3.12.2/debian/patches/CVE-2026-44673.patch  1970-01-01 
01:00:00.000000000 +0100
+++ libyang-3.12.2/debian/patches/CVE-2026-44673.patch  2026-10-18 
01:41:46.000000000 +0200
@@ -0,0 +1,220 @@
+Origin: upstream, 73da1db8fc14802e4f8fc64d022011a6f9d514db
+From: Michal Vasko <[email protected]>
+Date: Tue, 7 Apr 2026 12:41:29 +0200
+Subject: parser lyb BUGFIX avoid uint overflow
+
+Backport of upstream commit 73da1db8 (CVE-2026-44673) to libyang 3.12.2.
+
+The upstream change targets the newer 5.x LYB parser (struct lylyb_parse_ctx
+with a bit-based lyb_read()/lyb_read_size()), which does not exist in the
+3.12.2 code generation. In 3.12.2, lyb_read_string() already reads the
+length into a uint64_t, so the captured fix is adapted into explicit length
+bounds that close the same integer-overflow / heap-overflow paths here:
+
+- lyb_read_string(): reject len == UINT64_MAX, the only value for which
+  "(len + 1)" wraps to 0 -> malloc(0) succeeded non-NULL followed by an
+  out-of-bounds write in "*str[len] = '\0'".
+- lyb_read_term_value(): reject *term_value_len >= UINT32_MAX before it is
+  truncated into the uint32_t allocated_size, which previously produced
+  malloc(0) (or a small allocation) followed by an out-of-bounds write of
+  up to ~4 GiB and in "*term_value[allocated_size - 1]".
+
+Function signatures and context differ from the original patch.
+---
+diff --git a/src/parser_lyb.c b/src/parser_lyb.c
+index 1622b5d9943a..2fba3adf9b64 100644
+--- a/src/parser_lyb.c
++++ b/src/parser_lyb.c
+@@ -217,6 +217,9 @@
+ 
+     lyb_read_number(&len, sizeof len, len_size, lybctx);
+ 
++    /* len + 1 wraps to 0 when len == UINT64_MAX, causing malloc(0) followed 
by an out-of-bounds write */
++    LY_CHECK_ERR_RET(len == UINT64_MAX, LOGERR(lybctx->ctx, LY_EINVAL, "LYB 
string length overflow."), LY_EINVAL);
++
+     *str = malloc((len + 1) * sizeof **str);
+     LY_CHECK_ERR_RET(!*str, LOGMEM(lybctx->ctx), LY_EMEM);
+ 
+@@ -281,6 +284,11 @@
+         *term_value_len = lyb_data_len;
+     }
+ 
++    /* allocated_size is uint32_t; bound the length before adding 1 to 
prevent an integer overflow
++     * into a small allocation followed by an out-of-bounds write (and 4 GiB 
allocations) */
++    LY_CHECK_ERR_RET(*term_value_len >= UINT32_MAX,
++            LOGERR(lybctx->ctx, LY_EINVAL, "LYB value size overflow."), 
LY_EINVAL);
++
+     /* Allocate memory. */
+     allocated_size = *term_value_len + 1;
+     *term_value = malloc(allocated_size * sizeof **term_value);
+
+diff --git a/tests/utests/data/test_lyb.c b/tests/utests/data/test_lyb.c
+index 
26f3e738fb08191ca07afe9303c31998f96b5803..598dd595c2a460b05e5db220aa0ba208f5f7d772
 100644
+--- a/tests/utests/data/test_lyb.c
++++ b/tests/utests/data/test_lyb.c
+@@ -2810,6 +2810,157 @@
+ 
+ #endif
+ 
++/**
++ * @brief Find an 8-byte little-endian LYB length field preceding the ASCII 
@p marker and decode it.
++ *
++ * The LYB printer stores string/value sizes as 8-byte little-endian integers 
directly before the
++ * stored bytes, so the position of the size field of the searched value 
string is found by
++ * locating the value bytes.
++ *
++ * @param[in] lyb LYB data.
++ * @param[in] lyb_len Length of @p lyb in bytes.
++ * @param[in] marker Value string to search for.
++ * @param[out] len_val Decoded 8-byte length stored right before the marker.
++ * @return Offset of the 8-byte little-endian length field, or (size_t)-1 if 
@p marker not found.
++ */
++static size_t
++lyb_find_len_field(const char *lyb, size_t lyb_len, const char *marker, 
uint64_t *len_val)
++{
++    size_t i, mlen;
++
++    mlen = strlen(marker);
++    if (mlen > lyb_len) {
++        return (size_t)-1;
++    }
++
++    /* the length field (8B, little-endian) is written right before the value 
bytes */
++    for (i = 8; i + mlen <= lyb_len; ++i) {
++        if (!memcmp(lyb + i, marker, mlen)) {
++            *len_val = ((uint64_t)(uint8_t)lyb[i - 8]) | 
((uint64_t)(uint8_t)lyb[i - 7] << 8) |
++                    ((uint64_t)(uint8_t)lyb[i - 6] << 16) | 
((uint64_t)(uint8_t)lyb[i - 5] << 24) |
++                    ((uint64_t)(uint8_t)lyb[i - 4] << 32) | 
((uint64_t)(uint8_t)lyb[i - 3] << 40) |
++                    ((uint64_t)(uint8_t)lyb[i - 2] << 48) | 
((uint64_t)(uint8_t)lyb[i - 1] << 56);
++            return i - 8;
++        }
++    }
++
++    return (size_t)-1;
++}
++
++/**
++ * @brief Regression test for CVE-2026-44673 (GHSA-vw2p-pq79-92xh).
++ *
++ * lyb_read_string()/lyb_read_term_value() in src/parser_lyb.c previously 
accepted an
++ * attacker-controlled length whose +1 wrapped to 0 (or was truncated into a 
uint32_t),
++ * resulting in malloc(0)/small allocation followed by an out-of-bounds write 
when parsing
++ * a crafted LYB blob. A valid blob is printed here and its length field is 
then corrupted
++ * with the overflow value from the advisory; parsing must be rejected with 
an error instead
++ * of overflowing the allocated buffer.
++ */
++static void
++test_cve_2026_44673(void **state)
++{
++    const char *mod_yang =
++            "module mod {\n"
++            "  namespace \"urn:test\";\n"
++            "  prefix m;\n"
++            "  leaf lf { type string; }\n"
++            "}\n";
++    const char *leaf_xml = "<lf 
xmlns=\"urn:test\">CVE202644673LEAFMARKER</lf>";
++    const char *opaq_xml = "<opaqnode 
xmlns=\"urn:unknown\">CVE202644673OPAQVALMARKER</opaqnode>";
++    const char *leaf_marker = "CVE202644673LEAFMARKER";
++    const char *opaq_marker = "CVE202644673OPAQVALMARKER";
++    struct lyd_node *tree;
++    char *lyb;
++    size_t lyb_len, len_pos;
++    uint64_t len_val;
++    LY_ERR rc;
++
++    UTEST_ADD_MODULE(mod_yang, LYS_IN_YANG, NULL, NULL);
++
++    /* --- string leaf: corrupt the 8-byte term value size to UINT32_MAX 
(advisory str_len) --- */
++
++    /* build a valid LYB blob containing a string leaf value */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, leaf_xml, LYD_XML, LYD_PARSE_ONLY, 
0, &tree);
++    assert_int_equal(rc, LY_SUCCESS);
++    assert_non_null(tree);
++
++    rc = lyd_print_mem(&lyb, tree, LYD_LYB, LYD_PRINT_WITHSIBLINGS);
++    assert_int_equal(rc, LY_SUCCESS);
++    lyd_free_all(tree);
++
++    lyb_len = (size_t)lyd_lyb_data_length(lyb);
++    assert_true(lyb_len > 0);
++
++    /* the unmodified blob parses fine */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, lyb, LYD_LYB, LYD_PARSE_ONLY, 0, 
&tree);
++    assert_int_equal(rc, LY_SUCCESS);
++    assert_non_null(tree);
++    lyd_free_all(tree);
++
++    /* find the 8-byte little-endian term value size preceding the value */
++    len_pos = lyb_find_len_field(lyb, lyb_len, leaf_marker, &len_val);
++    assert_int_not_equal(len_pos, (size_t)-1);
++    assert_int_equal(len_val, strlen(leaf_marker));
++
++    /* corrupt the size to UINT32_MAX (0xFFFFFFFF) */
++    memset(lyb + len_pos, 0xFF, 4);
++
++    /* must be rejected cleanly instead of overflowing the allocation */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, lyb, LYD_LYB, LYD_PARSE_ONLY, 0, 
&tree);
++    assert_int_not_equal(rc, LY_SUCCESS);
++    assert_null(tree);
++    free(lyb);
++    lyb = NULL;
++
++    /* the malicious blob is intentionally rejected, its error message is 
expected */
++    ly_err_clean(UTEST_LYCTX, NULL);
++
++    /* --- opaque node value: corrupt the 8-byte string length to UINT64_MAX 
--- */
++
++    /* build a valid LYB blob containing an opaque node value */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, opaq_xml, LYD_XML, LYD_PARSE_ONLY | 
LYD_PARSE_OPAQ, 0, &tree);
++    assert_int_equal(rc, LY_SUCCESS);
++    assert_non_null(tree);
++
++    rc = lyd_print_mem(&lyb, tree, LYD_LYB, LYD_PRINT_WITHSIBLINGS);
++    assert_int_equal(rc, LY_SUCCESS);
++    lyd_free_all(tree);
++
++    lyb_len = (size_t)lyd_lyb_data_length(lyb);
++    assert_true(lyb_len > 0);
++
++    /* the unmodified blob parses fine */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, lyb, LYD_LYB, LYD_PARSE_ONLY | 
LYD_PARSE_OPAQ, 0, &tree);
++    assert_int_equal(rc, LY_SUCCESS);
++    assert_non_null(tree);
++    lyd_free_all(tree);
++
++    /* find the 8-byte little-endian string length preceding the value */
++    len_pos = lyb_find_len_field(lyb, lyb_len, opaq_marker, &len_val);
++    assert_int_not_equal(len_pos, (size_t)-1);
++    assert_int_equal(len_val, strlen(opaq_marker));
++
++    /* corrupt the length to UINT64_MAX, the only value for which "(len + 1)" 
wraps to 0 */
++    memset(lyb + len_pos, 0xFF, 8);
++
++    /* must be rejected cleanly instead of overflowing the allocation */
++    tree = NULL;
++    rc = lyd_parse_data_mem(UTEST_LYCTX, lyb, LYD_LYB, LYD_PARSE_ONLY | 
LYD_PARSE_OPAQ, 0, &tree);
++    assert_int_not_equal(rc, LY_SUCCESS);
++    assert_null(tree);
++    free(lyb);
++    lyb = NULL;
++
++    /* the malicious blob is intentionally rejected, its error message is 
expected */
++    ly_err_clean(UTEST_LYCTX, NULL);
++}
++
+ int
+ main(void)
+ {
+@@ -2822,6 +2973,7 @@
+         UTEST(test_statements, setup),
+         UTEST(test_opaq, setup),
+         UTEST(test_collisions, setup),
++        UTEST(test_cve_2026_44673, setup),
+ #if 0
+         cmocka_unit_test_setup_teardown(test_types, setup_f, teardown_f),
+         cmocka_unit_test_setup_teardown(test_annotations, setup_f, 
teardown_f),
diff -Nru libyang-3.12.2/debian/patches/series 
libyang-3.12.2/debian/patches/series
--- libyang-3.12.2/debian/patches/series        1970-01-01 01:00:00.000000000 
+0100
+++ libyang-3.12.2/debian/patches/series        2026-10-18 01:22:45.000000000 
+0200
@@ -0,0 +1,2 @@
+CVE-2026-41401.patch
+CVE-2026-44673.patch

Reply via email to