Fix four libsyck memory-safety CVEs in perl-YAML-Syck
Backport upstream commit 44c90a10 to fix four libsyck
memory-safety CVEs reachable from YAML::Syck::Load() on
untrusted input:
- CVE-2026-57075: out-of-bounds read in base64 decoder
- CVE-2026-57076: use-after-free of anchor key string
- CVE-2026-57077: out-of-bounds read in lexer newline scan
- CVE-2026-13713: use-after-free / double-free of anchor node
The patch includes per-CVE regression tests under t/cve-*.t.
CVE: CVE-2026-13713
Upstream patches:
- 44c90a109e.patch
Resolves: RHEL-211926
This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.
Assisted-by: Ymir
This commit is contained in:
parent
a9690a2ceb
commit
39addbca72
554
YAML-Syck-1.30-Fix-RHEL-211926.patch
Normal file
554
YAML-Syck-1.30-Fix-RHEL-211926.patch
Normal file
@ -0,0 +1,554 @@
|
||||
From f4a084777bd6bea73f2e0930de62c1e53e14bed4 Mon Sep 17 00:00:00 2001
|
||||
From: Todd Rinaldo <toddr@cpan.org>
|
||||
Date: Mon, 13 Jul 2026 09:20:12 -0500
|
||||
Subject: [PATCH] Fix four libsyck memory-safety CVEs reachable from
|
||||
YAML::Syck::Load()
|
||||
|
||||
Reported by Paul Johnson via the CPANSec coordinated-disclosure process.
|
||||
All four defects live in the bundled libsyck C library, are reachable from
|
||||
the default YAML::Syck::Load() path on untrusted input with no special flags
|
||||
(!!binary, anchors and block scalars are all on the default path), and are
|
||||
present through 1.46. Each has a small, self-contained fix plus a regression
|
||||
test under t/cve-*.t.
|
||||
|
||||
== The defects and fixes ==
|
||||
|
||||
CVE-2026-57075 - CWE-125 Out-of-bounds Read (base64 decoder)
|
||||
emitter.c: syck_base64dec() indexed the 256-entry static b64_xtable with a
|
||||
plain signed char, e.g. `b64_xtable[(int)s[0]]`. Where char is signed
|
||||
(x86-64, arm64) any !!binary byte >= 0x80 sign-extends to a NEGATIVE index
|
||||
and reads before the table. Non-crashing, but the read value can surface in
|
||||
the decoded !!binary result.
|
||||
Fix: cast each of the four index sites to (unsigned char) so a high byte is
|
||||
always a 0-255 index. b64_xtable[(int)b64_table[i]] at setup is left as-is;
|
||||
that table is pure ASCII.
|
||||
|
||||
CVE-2026-57076 - CWE-416 Use After Free (anchor key string)
|
||||
handler.c: an anchor name allocated by syck_strndup was used both as
|
||||
node->anchor (owned by the node, freed by syck_free_node) AND as the key in
|
||||
p->anchors. Freeing the node freed the table key, leaving a dangling key; an
|
||||
anchor redefinition then had st_delete/st_strcmp compare against freed
|
||||
memory.
|
||||
Fix: give the anchors and bad_anchors tables sole ownership of their key
|
||||
strings. Each table key becomes a private syck_strndup() copy, always a
|
||||
distinct allocation from any node->anchor, and syck_st_free_nodes() frees the
|
||||
key when its entry leaves the table at teardown. Because no pointer is then
|
||||
both a table key and a node->anchor, freeing a node can no longer dangle a
|
||||
key. (syck_.c, handler.c)
|
||||
|
||||
CVE-2026-57077 - CWE-125 Out-of-bounds Read (lexer newline scan)
|
||||
token.c: newline_len()/is_newline() dereferenced *ptr (and *(ptr+1) for
|
||||
\r\n) with no NUL terminator or bounds guarantee. During block-scalar lexing
|
||||
at a document boundary the scan ran one byte past the heap lexer buffer.
|
||||
This is an incomplete-fix follow-on to CVE-2025-11683, on a lexer path the
|
||||
earlier fix did not cover.
|
||||
Fix: give is_newline()/newline_len() an explicit `limit` parameter and check
|
||||
it before every read, then pass the live bound at each call site (YYLIMIT, or
|
||||
the scalar-buffer end for the one backward chomp walk, which also gains an
|
||||
explicit `fc >= ptr` lower-bound guard). Changing the helper signatures makes
|
||||
the compiler flag every call site, so none is missed - the failure mode that
|
||||
left CVE-2025-11683 incomplete.
|
||||
|
||||
CVE-2026-13713 - CWE-416 Use After Free / CWE-415 Double Free (anchor node)
|
||||
handler.c: when an anchor name is redefined or removed,
|
||||
syck_hdlr_remove_anchor / syck_hdlr_add_anchor freed the SyckNode stored
|
||||
under that name (syck_free_node). That node can still be live on the parser's
|
||||
value stack, so syck_hdlr_add_node reaches it again and frees it a second
|
||||
time. On a normal build the 48-byte node chunk is freed twice and the
|
||||
interpreter aborts - a remote-crash DoS from a 7-byte input.
|
||||
Fix: do not free an evicted anchor node inline. Add a parser-owned "retired"
|
||||
table; syck_retire_node() moves the evicted node there and syck_st_free()
|
||||
frees the table at teardown. Retired nodes keep node->anchor != NULL, so
|
||||
syck_hdlr_add_node never re-frees them, and each node is evicted at most once,
|
||||
so there is no double free at teardown. (handler.c, syck_.c, syck.h)
|
||||
|
||||
NOTE: the two anchor fixes are ordered and interdependent. CVE-2026-13713's
|
||||
fix relies on CVE-2026-57076's: only once table keys are private copies
|
||||
distinct from node->anchor is it safe to defer freeing an evicted node to
|
||||
teardown. Applied together here.
|
||||
|
||||
== Regression tests (t/cve-*.t, one file per CVE) ==
|
||||
|
||||
Each test Loads its documented trigger in-process (one CVE per file, so a
|
||||
crash only takes down that file, which the harness reports as failed) and
|
||||
wraps Load() in eval{} so the patched behaviour - an ordinary parse-error
|
||||
croak - passes while an uncatchable C-level abort fails the file. Verified in
|
||||
both directions on a normal build (no ASan):
|
||||
|
||||
cve-2026-13713 : unpatched -> file aborts (SIGABRT/SIGTRAP); patched -> pass.
|
||||
Provable on any build (it crashes).
|
||||
cve-2026-57075 : unpatched -> high-bit !!binary decodes to leaked bytes
|
||||
("A" on the author's build) instead of ""; patched -> "".
|
||||
Provable via output on any build.
|
||||
cve-2026-57076 : non-crashing UAF - silent on a normal build; proven by the
|
||||
ASan CI job (heap-use-after-free READ in st_strcmp).
|
||||
cve-2026-57077 : one-byte over-read - silent on a normal build; proven by the
|
||||
ASan CI job (heap-buffer-overflow READ in newline_len).
|
||||
|
||||
== Documentation and packaging ==
|
||||
|
||||
- CLAUDE.md: new "Security & CVE Work" section documenting the policy - one
|
||||
t/cve-<id>-<slug>.t per CVE, made to fail provably without the fix where
|
||||
possible, in-process (no forked perl; note on why fork() is unsafe on
|
||||
Windows), with silent defects proven by the ASan job.
|
||||
- MANIFEST: add the four t/cve-*.t files.
|
||||
|
||||
== Verification ==
|
||||
|
||||
- Full suite passes with the fixes: 72 files / 1176 tests (AUTOMATED_TESTING=1,
|
||||
leak tests active); no RSS growth over 200k iterations of the anchor paths.
|
||||
- The asan CI job (added separately in .github/workflows/testsuite.yml)
|
||||
confirmed all three ASan-visible defects on the unpatched tree with stack
|
||||
traces matching the report, and goes green with these fixes.
|
||||
|
||||
Credit: Paul Johnson <paul@pjcj.net>, via CPANSec.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||||
---
|
||||
emitter.c | 8 +++---
|
||||
handler.c | 34 ++++++++++++++++++++++----
|
||||
syck.h | 2 ++
|
||||
syck_.c | 23 ++++++++++++++---
|
||||
t/cve-2026-13713-anchor-node-uaf.t | 33 +++++++++++++++++++++++++
|
||||
t/cve-2026-57075-base64-oob-read.t | 32 ++++++++++++++++++++++++
|
||||
t/cve-2026-57076-anchor-key-uaf.t | 29 ++++++++++++++++++++++
|
||||
t/cve-2026-57077-newline-oob-read.t | 29 ++++++++++++++++++++++
|
||||
token.c | 38 ++++++++++++++++-------------
|
||||
9 files changed, 199 insertions(+), 29 deletions(-)
|
||||
create mode 100644 t/cve-2026-13713-anchor-node-uaf.t
|
||||
create mode 100644 t/cve-2026-57075-base64-oob-read.t
|
||||
create mode 100644 t/cve-2026-57076-anchor-key-uaf.t
|
||||
create mode 100644 t/cve-2026-57077-newline-oob-read.t
|
||||
|
||||
diff --git a/emitter.c b/emitter.c
|
||||
index da3c91b..ee6f9d5 100644
|
||||
--- a/emitter.c
|
||||
+++ b/emitter.c
|
||||
@@ -82,10 +82,10 @@ syck_base64dec( char *s, long len, long *out_len )
|
||||
while (s < send) {
|
||||
while (s < send && (s[0] == '\r' || s[0] == '\n')) { s++; }
|
||||
if (s >= send) break;
|
||||
- if ((a = b64_xtable[(int)s[0]]) == -1) break;
|
||||
- if ((b = b64_xtable[(int)s[1]]) == -1) break;
|
||||
- if ((c = b64_xtable[(int)s[2]]) == -1) break;
|
||||
- if ((d = b64_xtable[(int)s[3]]) == -1) break;
|
||||
+ if ((a = b64_xtable[(unsigned char)s[0]]) == -1) break;
|
||||
+ if ((b = b64_xtable[(unsigned char)s[1]]) == -1) break;
|
||||
+ if ((c = b64_xtable[(unsigned char)s[2]]) == -1) break;
|
||||
+ if ((d = b64_xtable[(unsigned char)s[3]]) == -1) break;
|
||||
*end++ = a << 2 | b >> 4;
|
||||
*end++ = b << 4 | c >> 2;
|
||||
*end++ = c << 6 | d;
|
||||
diff --git a/handler.c b/handler.c
|
||||
index 48341a9..1515ee6 100644
|
||||
--- a/handler.c
|
||||
+++ b/handler.c
|
||||
@@ -27,6 +27,21 @@ syck_hdlr_add_node( SyckParser *p, SyckNode *n )
|
||||
return id;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * A node evicted from the anchors table by redefinition may still be live on
|
||||
+ * the parser's value stack, so it cannot be freed here. Keep it for teardown.
|
||||
+ */
|
||||
+void
|
||||
+syck_retire_node( SyckParser *p, SyckNode *n )
|
||||
+{
|
||||
+ if ( p->retired == NULL )
|
||||
+ {
|
||||
+ p->retired = st_init_numtable();
|
||||
+ }
|
||||
+ st_insert( p->retired, (st_data_t)( p->retired->num_entries + 1 ),
|
||||
+ (st_data_t)n );
|
||||
+}
|
||||
+
|
||||
SyckNode *
|
||||
syck_hdlr_add_anchor( SyckParser *p, char *a, SyckNode *n )
|
||||
{
|
||||
@@ -64,10 +79,15 @@ syck_hdlr_add_anchor( SyckParser *p, char *a, SyckNode *n )
|
||||
{
|
||||
if ( ntmp != (void *)1 )
|
||||
{
|
||||
- syck_free_node( ntmp );
|
||||
+ syck_retire_node( p, ntmp );
|
||||
}
|
||||
+ st_insert( p->anchors, (st_data_t)a, (st_data_t)n );
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ st_insert( p->anchors, (st_data_t)syck_strndup( a, strlen( a ) ),
|
||||
+ (st_data_t)n );
|
||||
}
|
||||
- st_insert( p->anchors, (st_data_t)a, (st_data_t)n );
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -84,10 +104,12 @@ syck_hdlr_remove_anchor( SyckParser *p, char *a )
|
||||
{
|
||||
if ( ntmp != (void *)1 )
|
||||
{
|
||||
- syck_free_node( ntmp );
|
||||
+ syck_retire_node( p, ntmp );
|
||||
}
|
||||
+ S_FREE( atmp );
|
||||
}
|
||||
- st_insert( p->anchors, (st_data_t)a, (st_data_t)1 );
|
||||
+ st_insert( p->anchors, (st_data_t)syck_strndup( a, strlen( a ) ),
|
||||
+ (st_data_t)1 );
|
||||
}
|
||||
|
||||
SyckNode *
|
||||
@@ -113,7 +135,9 @@ syck_hdlr_get_anchor( SyckParser *p, char *a )
|
||||
if ( ! st_lookup( p->bad_anchors, (st_data_t)a, (st_data_t *)&n ) )
|
||||
{
|
||||
n = (p->bad_anchor_handler)( p, a );
|
||||
- st_insert( p->bad_anchors, (st_data_t)a, (st_data_t)n );
|
||||
+ st_insert( p->bad_anchors,
|
||||
+ (st_data_t)syck_strndup( a, strlen( a ) ),
|
||||
+ (st_data_t)n );
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/syck.h b/syck.h
|
||||
index cdcedb8..2ead8bc 100644
|
||||
--- a/syck.h
|
||||
+++ b/syck.h
|
||||
@@ -284,6 +284,8 @@ struct _syck_parser {
|
||||
} io;
|
||||
/* Symbol table for anchors */
|
||||
st_table *anchors, *bad_anchors;
|
||||
+ /* Nodes evicted from anchors by redefinition, freed at teardown */
|
||||
+ st_table *retired;
|
||||
/* Optional symbol table for SYMIDs */
|
||||
st_table *syms;
|
||||
/* Levels of indentation */
|
||||
diff --git a/syck_.c b/syck_.c
|
||||
index 8df83bf..92df03d 100644
|
||||
--- a/syck_.c
|
||||
+++ b/syck_.c
|
||||
@@ -166,6 +166,7 @@ syck_new_parser()
|
||||
p->syms = NULL;
|
||||
p->anchors = NULL;
|
||||
p->bad_anchors = NULL;
|
||||
+ p->retired = NULL;
|
||||
p->implicit_typing = 1;
|
||||
p->taguri_expansion = 0;
|
||||
p->bufsize = SYCK_BUFFERSIZE;
|
||||
@@ -195,11 +196,20 @@ syck_lookup_sym( SyckParser *p, SYMID id, char **data )
|
||||
return st_lookup( p->syms, id, (st_data_t *)data );
|
||||
}
|
||||
|
||||
-int
|
||||
-syck_st_free_nodes( char *key, SyckNode *n, char *arg )
|
||||
+enum st_retval
|
||||
+syck_st_free_nodes( st_data_t key, st_data_t value, st_data_t arg )
|
||||
{
|
||||
+ SyckNode *n = (SyckNode *)value;
|
||||
+ char *k = (char *)key;
|
||||
if ( n != (void *)1 ) syck_free_node( n );
|
||||
- n = NULL;
|
||||
+ S_FREE( k ); /* the anchor tables own their key strings */
|
||||
+ return ST_CONTINUE;
|
||||
+}
|
||||
+
|
||||
+enum st_retval
|
||||
+syck_st_free_retired( st_data_t key, st_data_t value, st_data_t arg )
|
||||
+{
|
||||
+ syck_free_node( (SyckNode *)value );
|
||||
return ST_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -222,6 +232,13 @@ syck_st_free( SyckParser *p )
|
||||
st_free_table( p->bad_anchors );
|
||||
p->bad_anchors = NULL;
|
||||
}
|
||||
+
|
||||
+ if ( p->retired != NULL )
|
||||
+ {
|
||||
+ st_foreach( p->retired, syck_st_free_retired, 0 );
|
||||
+ st_free_table( p->retired );
|
||||
+ p->retired = NULL;
|
||||
+ }
|
||||
}
|
||||
|
||||
void
|
||||
diff --git a/t/cve-2026-13713-anchor-node-uaf.t b/t/cve-2026-13713-anchor-node-uaf.t
|
||||
new file mode 100644
|
||||
index 0000000..7c7cfe7
|
||||
--- /dev/null
|
||||
+++ b/t/cve-2026-13713-anchor-node-uaf.t
|
||||
@@ -0,0 +1,33 @@
|
||||
+#!perl
|
||||
+
|
||||
+# CVE-2026-13713 - CWE-416 (Use After Free) / CWE-415 (Double Free)
|
||||
+#
|
||||
+# In the bundled libsyck, when an anchor name is redefined or removed,
|
||||
+# syck_hdlr_remove_anchor / syck_hdlr_add_anchor freed the SyckNode that was
|
||||
+# stored under that name (syck_free_node). That node could still be live on
|
||||
+# the parser's value stack, so the grammar reached it again in
|
||||
+# syck_hdlr_add_node (handler.c:17) and freed the same node a second time.
|
||||
+#
|
||||
+# Trigger (7 bytes): YAML::Syck::Load("[&a\x01\n&a")
|
||||
+#
|
||||
+# This is the one defect of the four that faults WITHOUT AddressSanitizer:
|
||||
+# on a normal build the 48-byte node chunk is freed twice and the interpreter
|
||||
+# aborts (heap corruption -> SIGABRT/SIGTRAP). So against an unpatched libsyck
|
||||
+# this whole test file crashes and the harness reports it as failed
|
||||
+# ("Dubious, test returned ... wstat ..."). Once patched, Load() raises an
|
||||
+# ordinary parse error (caught below) and the file passes cleanly.
|
||||
+
|
||||
+use strict;
|
||||
+use warnings;
|
||||
+use Test::More tests => 1;
|
||||
+use YAML::Syck ();
|
||||
+
|
||||
+# The unpatched behaviour is a C-level abort() that eval cannot catch: it takes
|
||||
+# the whole file down before the assertion below and the harness reports the
|
||||
+# file as failed. Patched, Load() croaks on the malformed input, so we assert
|
||||
+# it died with the expected parse error (verifying the graceful failure, not
|
||||
+# merely that the process survived).
|
||||
+eval { YAML::Syck::Load("[&a\x01\n&a") };
|
||||
+
|
||||
+like( $@, qr/syntax error/,
|
||||
+ 'CVE-2026-13713: Load("[&a\\x01\\n&a") croaked with a parse error, not a double-free crash' );
|
||||
diff --git a/t/cve-2026-57075-base64-oob-read.t b/t/cve-2026-57075-base64-oob-read.t
|
||||
new file mode 100644
|
||||
index 0000000..191cf9f
|
||||
--- /dev/null
|
||||
+++ b/t/cve-2026-57075-base64-oob-read.t
|
||||
@@ -0,0 +1,32 @@
|
||||
+#!perl
|
||||
+
|
||||
+# CVE-2026-57075 - CWE-125 (Out-of-bounds Read)
|
||||
+#
|
||||
+# syck_base64dec (emitter.c) indexed the 256-entry static b64_xtable with a
|
||||
+# plain signed char from the input:
|
||||
+# if ((a = b64_xtable[(int)s[0]]) == -1) break;
|
||||
+# Any !!binary byte >= 0x80 sign-extends to a NEGATIVE index and reads before
|
||||
+# the table. The fix casts each index to (unsigned char).
|
||||
+#
|
||||
+# Trigger: YAML::Syck::Load("--- !!binary \x80\x80\x80\x80")
|
||||
+#
|
||||
+# The read is non-crashing and its negative index lands in live static memory
|
||||
+# adjacent to b64_xtable rather than an AddressSanitizer redzone, so it neither
|
||||
+# crashes nor trips ASan through Load() (see the CPANSec report). It is still
|
||||
+# observable at runtime: with the fix every byte >= 0x80 maps to -1, so the
|
||||
+# decoder rejects the payload and yields an EMPTY string deterministically;
|
||||
+# without the fix the negative index reads adjacent memory and that garbage can
|
||||
+# surface in the decoded result (on the author's build it decoded to "A" rather
|
||||
+# than ""). So this assertion fails on an unpatched build. The exact unpatched
|
||||
+# value is layout-dependent, but the patched value is always "".
|
||||
+
|
||||
+use strict;
|
||||
+use warnings;
|
||||
+use Test::More tests => 1;
|
||||
+use YAML::Syck ();
|
||||
+
|
||||
+my $decoded = eval { YAML::Syck::Load("--- !!binary \x80\x80\x80\x80") };
|
||||
+
|
||||
+is( $decoded, '',
|
||||
+'CVE-2026-57075: high-bit-only !!binary decodes to empty string, no OOB leak'
|
||||
+);
|
||||
diff --git a/t/cve-2026-57076-anchor-key-uaf.t b/t/cve-2026-57076-anchor-key-uaf.t
|
||||
new file mode 100644
|
||||
index 0000000..62d064b
|
||||
--- /dev/null
|
||||
+++ b/t/cve-2026-57076-anchor-key-uaf.t
|
||||
@@ -0,0 +1,29 @@
|
||||
+#!perl
|
||||
+
|
||||
+# CVE-2026-57076 - CWE-416 (Use After Free)
|
||||
+#
|
||||
+# An anchor name string allocated by syck_strndup was freed in
|
||||
+# syck_hdlr_add_anchor (handler.c:43) while still stored as a live KEY in the
|
||||
+# parser's anchors table. On an anchor redefinition, syck_hdlr_remove_anchor
|
||||
+# calls st_delete, whose st_strcmp then compares against that freed key.
|
||||
+#
|
||||
+# Trigger (9 bytes): YAML::Syck::Load("- &a&V\n&a")
|
||||
+#
|
||||
+# This is a NON-crashing use-after-free: on a normal build the freed key is
|
||||
+# still readable and nothing faults, so this file cannot fail a plain build -
|
||||
+# it just confirms Load() completes. The defect is PROVEN by the ASan CI job
|
||||
+# (the `asan` job in .github/workflows/testsuite.yml), where this same Load
|
||||
+# aborts the process with:
|
||||
+# heap-use-after-free READ in st_strcmp, freed by syck_hdlr_add_anchor
|
||||
+# and the harness reports this file as failed.
|
||||
+
|
||||
+use strict;
|
||||
+use warnings;
|
||||
+use Test::More tests => 1;
|
||||
+use YAML::Syck ();
|
||||
+
|
||||
+# Under ASan (unpatched) this Load aborts the process; on a normal build it
|
||||
+# returns/croaks harmlessly. eval keeps a normal parse error from failing us.
|
||||
+eval { YAML::Syck::Load("- &a&V\n&a") };
|
||||
+
|
||||
+pass('CVE-2026-57076: Load("- &a&V\\n&a") completed without an ASan fault');
|
||||
diff --git a/t/cve-2026-57077-newline-oob-read.t b/t/cve-2026-57077-newline-oob-read.t
|
||||
new file mode 100644
|
||||
index 0000000..2ab6324
|
||||
--- /dev/null
|
||||
+++ b/t/cve-2026-57077-newline-oob-read.t
|
||||
@@ -0,0 +1,29 @@
|
||||
+#!perl
|
||||
+
|
||||
+# CVE-2026-57077 - CWE-125 (Out-of-bounds Read)
|
||||
+#
|
||||
+# newline_len()/is_newline() (token.c) dereferenced *ptr (and *(ptr+1) for
|
||||
+# \r\n) with no NUL terminator or bounds guarantee. During block-scalar
|
||||
+# lexing at a document boundary the scan pointer ran one byte past the heap
|
||||
+# lexer buffer. This is an incomplete-fix follow-on to CVE-2025-11683, on a
|
||||
+# lexer path that earlier fix did not cover.
|
||||
+#
|
||||
+# Trigger (6 bytes): YAML::Syck::Load("|\n---\n")
|
||||
+#
|
||||
+# A one-byte over-read that does not crash a normal build, so this file cannot
|
||||
+# fail a plain build - it just confirms Load() completes. The defect is PROVEN
|
||||
+# by the ASan CI job (the `asan` job in .github/workflows/testsuite.yml), where
|
||||
+# this same Load aborts the process with:
|
||||
+# heap-buffer-overflow READ in newline_len
|
||||
+# and the harness reports this file as failed.
|
||||
+
|
||||
+use strict;
|
||||
+use warnings;
|
||||
+use Test::More tests => 1;
|
||||
+use YAML::Syck ();
|
||||
+
|
||||
+# Under ASan (unpatched) this Load aborts the process; on a normal build it
|
||||
+# returns harmlessly. eval keeps a normal parse error from failing us.
|
||||
+eval { YAML::Syck::Load("|\n---\n") };
|
||||
+
|
||||
+pass('CVE-2026-57077: Load("|\\n---\\n") completed without an ASan fault');
|
||||
diff --git a/token.c b/token.c
|
||||
index 826f5c3..49c5dff 100644
|
||||
--- a/token.c
|
||||
+++ b/token.c
|
||||
@@ -39,7 +39,7 @@
|
||||
/*
|
||||
* Track line numbers
|
||||
*/
|
||||
-#define NEWLINE(ptr) YYLINEPTR = ptr + newline_len(ptr); if ( YYLINEPTR > YYLINECTPTR ) { YYLINE++; YYLINECTPTR = YYLINEPTR; }
|
||||
+#define NEWLINE(ptr) YYLINEPTR = ptr + newline_len(ptr, YYLIMIT); if ( YYLINEPTR > YYLINECTPTR ) { YYLINE++; YYLINECTPTR = YYLINEPTR; }
|
||||
|
||||
/*
|
||||
* I like seeing the level operations as macros...
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
/* concat the inline characters to the plain scalar */
|
||||
#define PLAIN_NOT_INL() \
|
||||
- if ( *(YYCURSOR - 1) == ' ' || is_newline( YYCURSOR - 1 ) ) \
|
||||
+ if ( *(YYCURSOR - 1) == ' ' || is_newline( YYCURSOR - 1, YYLIMIT ) ) \
|
||||
{ \
|
||||
YYCURSOR--; \
|
||||
} \
|
||||
@@ -176,7 +176,8 @@
|
||||
if ( nlDoWhat != NL_KEEP ) \
|
||||
{ \
|
||||
char *fc = n->data.str->ptr + n->data.str->len - 1; \
|
||||
- while ( is_newline( fc ) ) fc--; \
|
||||
+ while ( fc >= n->data.str->ptr && \
|
||||
+ is_newline( fc, n->data.str->ptr + n->data.str->len ) ) fc--; \
|
||||
if ( nlDoWhat != NL_CHOMP && fc < n->data.str->ptr + n->data.str->len - 1 ) \
|
||||
fc += 1; \
|
||||
n->data.str->len = fc - n->data.str->ptr + 1; \
|
||||
@@ -194,7 +195,7 @@
|
||||
NEWLINE(indent); \
|
||||
while ( indent < YYCURSOR ) \
|
||||
{ \
|
||||
- if ( is_newline( ++indent ) ) \
|
||||
+ if ( is_newline( ++indent, YYLIMIT ) ) \
|
||||
{ \
|
||||
NEWLINE(indent); \
|
||||
} \
|
||||
@@ -237,8 +238,8 @@ SyckParser *syck_parser_ptr = NULL;
|
||||
*/
|
||||
void eat_comments( SyckParser * );
|
||||
char escape_seq( char );
|
||||
-int is_newline( char *ptr );
|
||||
-int newline_len( char *ptr );
|
||||
+int is_newline( char *ptr, char *limit );
|
||||
+int newline_len( char *ptr, char *limit );
|
||||
int sycklex_yaml_utf8( YYSTYPE *, SyckParser * );
|
||||
int sycklex_bytecode_utf8( YYSTYPE *, SyckParser * );
|
||||
int syckwrap();
|
||||
@@ -892,7 +893,7 @@ yy70:
|
||||
++YYCURSOR;
|
||||
yy71:
|
||||
#line 482 "token.re"
|
||||
- { if ( is_newline( YYCURSOR - 1 ) )
|
||||
+ { if ( is_newline( YYCURSOR - 1, YYLIMIT ) )
|
||||
{
|
||||
YYCURSOR--;
|
||||
}
|
||||
@@ -1081,7 +1082,7 @@ yy82:
|
||||
#line 444 "token.re"
|
||||
{ ENSURE_YAML_IOPEN(lvl, YYTOKEN - YYLINEPTR, 1);
|
||||
FORCE_NEXT_TOKEN(YAML_IOPEN);
|
||||
- if ( *YYCURSOR == '#' || is_newline( YYCURSOR ) || is_newline( YYCURSOR - 1 ) )
|
||||
+ if ( *YYCURSOR == '#' || is_newline( YYCURSOR, YYLIMIT ) || is_newline( YYCURSOR - 1, YYLIMIT ) )
|
||||
{
|
||||
YYCURSOR--;
|
||||
ADD_LEVEL((YYTOKEN + 1) - YYLINEPTR, syck_lvl_seq);
|
||||
@@ -1602,7 +1603,7 @@ yy113:
|
||||
|
||||
while ( YYTOKEN < YYCURSOR )
|
||||
{
|
||||
- int nl_len = newline_len( YYTOKEN++ );
|
||||
+ int nl_len = newline_len( YYTOKEN++, YYLIMIT );
|
||||
if ( nl_len )
|
||||
{
|
||||
nl_count++;
|
||||
@@ -1845,7 +1846,7 @@ yy147:
|
||||
|
||||
while ( YYTOKEN < YYCURSOR )
|
||||
{
|
||||
- int nl_len = newline_len( YYTOKEN++ );
|
||||
+ int nl_len = newline_len( YYTOKEN++, YYLIMIT );
|
||||
if ( nl_len )
|
||||
{
|
||||
nl_count++;
|
||||
@@ -2007,7 +2008,7 @@ yy163:
|
||||
{
|
||||
while ( YYTOKEN < YYCURSOR )
|
||||
{
|
||||
- int nl_len = newline_len( YYTOKEN++ );
|
||||
+ int nl_len = newline_len( YYTOKEN++, YYLIMIT );
|
||||
if ( nl_len )
|
||||
{
|
||||
nl_count++;
|
||||
@@ -2536,7 +2537,7 @@ yy208:
|
||||
pacer = YYTOKEN;
|
||||
while ( pacer < YYCURSOR )
|
||||
{
|
||||
- int nl_len = newline_len( pacer++ );
|
||||
+ int nl_len = newline_len( pacer++, YYLIMIT );
|
||||
if ( nl_len )
|
||||
{
|
||||
nl_count++;
|
||||
@@ -2803,18 +2804,21 @@ escape_seq( char ch )
|
||||
}
|
||||
|
||||
int
|
||||
-is_newline( char *ptr )
|
||||
+is_newline( char *ptr, char *limit )
|
||||
{
|
||||
- return newline_len( ptr );
|
||||
+ return newline_len( ptr, limit );
|
||||
}
|
||||
|
||||
int
|
||||
-newline_len( char *ptr )
|
||||
+newline_len( char *ptr, char *limit )
|
||||
{
|
||||
+ if ( ptr >= limit )
|
||||
+ return 0;
|
||||
+
|
||||
if ( *ptr == '\n' )
|
||||
return 1;
|
||||
-
|
||||
- if ( *ptr == '\r' && *( ptr + 1 ) == '\n' )
|
||||
+
|
||||
+ if ( *ptr == '\r' && ptr + 1 < limit && *( ptr + 1 ) == '\n' )
|
||||
return 2;
|
||||
|
||||
return 0;
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
Name: perl-YAML-Syck
|
||||
Version: 1.30
|
||||
Release: 6%{?dist}
|
||||
Release: 7%{?dist}
|
||||
Summary: Fast, lightweight YAML loader and dumper
|
||||
License: BSD and MIT
|
||||
URL: http://search.cpan.org/dist/YAML-Syck/
|
||||
@ -16,6 +16,10 @@ Source0: http://www.cpan.org/authors/id/T/TO/TODDR/YAML-Syck-%{version}.t
|
||||
Patch0: YAML-Syck-1.33-Fix-memory-corruption-error.patch
|
||||
# Fix heap buffer overflow in the YAML emitter - CVE-2026-4177
|
||||
Patch1: YAML-Syck-1.37-Fix-CVE-2026-4177.patch
|
||||
# Fix four libsyck memory-safety CVEs - CVE-2026-13713, CVE-2026-57075,
|
||||
# CVE-2026-57076, CVE-2026-57077
|
||||
# https://github.com/toddr/YAML-Syck/commit/44c90a109ec3215ee7ce747bd11209835e123d8b
|
||||
Patch2: YAML-Syck-1.30-Fix-RHEL-211926.patch
|
||||
BuildRequires: coreutils
|
||||
BuildRequires: findutils
|
||||
BuildRequires: gcc
|
||||
@ -64,6 +68,7 @@ structures to YAML strings, and the other way around.
|
||||
%setup -q -n YAML-Syck-%{version}
|
||||
%patch -P0 -p1
|
||||
%patch -P1 -p1
|
||||
%patch -P2 -p1
|
||||
|
||||
# Unbundle core and unused modules
|
||||
rm -rvf inc/{parent.pm,PerlIO.pm,Scalar/,Test/}
|
||||
@ -91,6 +96,11 @@ make test
|
||||
%{_mandir}/man3/YAML::Syck.3*
|
||||
|
||||
%changelog
|
||||
* Mon Jul 20 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1.30-7
|
||||
- Fix four libsyck memory-safety CVEs: CVE-2026-13713, CVE-2026-57075,
|
||||
CVE-2026-57076, CVE-2026-57077
|
||||
- Resolves: RHEL-211926
|
||||
|
||||
* Wed Mar 25 2026 Jitka Plesnikova <jplesnik@redhat.com> - 1.30-6
|
||||
- Resolves: RHEL-156475
|
||||
- Fix CVE-2026-4177
|
||||
|
||||
Loading…
Reference in New Issue
Block a user