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:
RHEL Packaging Agent 2026-07-20 06:05:39 +00:00
parent a9690a2ceb
commit 39addbca72
2 changed files with 565 additions and 1 deletions

View 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;

View File

@ -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