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

