Compare commits

...

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

3 changed files with 1 additions and 331 deletions

View File

@ -1,52 +0,0 @@
From d4ec4ab3aaf1fa736a6195c2d1317450dc1f2c4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tina=20M=C3=BCller?= <cpan2@tinita.de>
Date: Tue, 2 Oct 2018 22:56:00 +0200
Subject: [PATCH] Fix memory corruption error
Currently the following examples abort the program:
"- &anchor1 &anchor2 x"
double free or corruption (fasttop)
"- &anchor1 &anchor2 x"
double free or corruption (fasttop)
"x: &anchor1 &anchor2 x"
malloc(): memory corruption (fast)
"key1: &a value1
&b *a : value2"
double free or corruption (fasttop)
This fix will prevent the abort and falsely accept invalid YAML.
Instead the code should return an error/throw an exception, but I don't
know how to do this here.
So this is just the less optimal solution, avoiding a program abort.
---
handler.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/handler.c b/handler.c
index d9cf8ec..5de4359 100644
--- a/handler.c
+++ b/handler.c
@@ -32,6 +32,16 @@ syck_hdlr_add_anchor( SyckParser *p, char *a, SyckNode *n )
{
SyckNode *ntmp = NULL;
+ if (n->anchor != NULL) {
+ /*
+ * Note: An error should be returned here. But this is better than
+ * not checking at all because it will abort the program with a
+ * memory corruption error.
+ * Happens if you have two anchors after each other or an anchor
+ * before an alias
+ * */
+ return n;
+ }
n->anchor = a;
if ( p->bad_anchors != NULL )
{
--
2.53.0

View File

@ -1,268 +0,0 @@
From e8844a31c8cf0052914b198fc784ed4e6b8ae69e Mon Sep 17 00:00:00 2001
From: Toddr Bot <toddbot@rinaldo.us>
Date: Sat, 14 Mar 2026 07:18:00 +0000
Subject: [PATCH] fix: address all 4 C-layer audit findings from issue #67
- HIGH: Fix heap buffer overflow in emitter tag buffer. The 512-byte
fixed allocation overflowed via strcat(tag, ref) with long class names.
Now tracks buffer size and grows dynamically with Renew() when needed.
- MEDIUM: Fix base64 decoder reading past buffer end on trailing
newlines. Added s < send guard to the inner whitespace-skip loop.
- MEDIUM: Replace strtok(id, "/:") with savepv copy + strtok at all 6
call sites in the parser handler. strtok mutated n->type_id in place,
corrupting shared node data. Each site now operates on a local copy
that is freed after use.
- LOW: Fix memory leak in syck_hdlr_add_anchor when a node already has
an anchor. The incoming anchor string 'a' was leaked on early return.
Closes #67
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
emitter.c | 3 ++-
handler.c | 1 +
perl_common.h | 1 +
perl_syck.h | 36 +++++++++++++++++++++++++++++-------
t/tag-overflow.t | 44 ++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 77 insertions(+), 8 deletions(-)
create mode 100644 t/tag-overflow.t
diff --git a/emitter.c b/emitter.c
index cd8150b..da3c91b 100644
--- a/emitter.c
+++ b/emitter.c
@@ -80,7 +80,8 @@ syck_base64dec( char *s, long len, long *out_len )
}
}
while (s < send) {
- while (s[0] == '\r' || s[0] == '\n') { s++; }
+ 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;
diff --git a/handler.c b/handler.c
index 5de4359..48341a9 100644
--- a/handler.c
+++ b/handler.c
@@ -40,6 +40,7 @@ syck_hdlr_add_anchor( SyckParser *p, char *a, SyckNode *n )
* Happens if you have two anchors after each other or an anchor
* before an alias
* */
+ S_FREE(a);
return n;
}
n->anchor = a;
diff --git a/perl_common.h b/perl_common.h
index 4512237..bc9924b 100644
--- a/perl_common.h
+++ b/perl_common.h
@@ -37,6 +37,7 @@ struct emitter_xtra {
PerlIO* outio;
} out;
char* tag;
+ STRLEN tag_len;
char dump_code;
bool implicit_binary;
int ioerror;
diff --git a/perl_syck.h b/perl_syck.h
index 14cd311..1b00bd6 100644
--- a/perl_syck.h
+++ b/perl_syck.h
@@ -332,7 +332,8 @@ yaml_syck_parser_handler
} else if (strnEQ( n->data.str->ptr, REF_LITERAL, 1+REF_LITERAL_LENGTH)) {
/* type tag in a scalar ref */
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
if (lang == NULL || (strEQ(lang, "perl"))) {
@@ -341,6 +342,7 @@ yaml_syck_parser_handler
else {
sv = newSVpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), 0);
}
+ Safefree(id_copy);
} else if ( strEQ( id, "perl/scalar" ) || strnEQ( id, "perl/scalar:", 12 ) ) {
char *pkg = id + 12;
@@ -362,7 +364,8 @@ yaml_syck_parser_handler
} else if ( (strEQ(id, "perl/regexp") || strnEQ( id, "perl/regexp:", 12 ) ) ) {
dSP;
SV *val = newSVpvn(n->data.str->ptr, n->data.str->len);
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
ENTER;
@@ -396,6 +399,7 @@ yaml_syck_parser_handler
sv_bless(sv, gv_stashpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), TRUE));
}
}
+ Safefree(id_copy);
#endif /* PERL_LOADMOD_NOIMPORT */
#endif /* !YAML_IS_JSON */
} else {
@@ -427,7 +431,8 @@ yaml_syck_parser_handler
if (id) {
/* bless it if necessary */
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
if ( type != NULL ) {
@@ -435,7 +440,7 @@ yaml_syck_parser_handler
/* !perl/array:Foo::Bar blesses into Foo::Bar */
type += 6;
}
-
+
/* FIXME deprecated - here compatibility with @Foo::Bar style blessing */
while ( *type == '@' ) { type++; }
}
@@ -451,6 +456,7 @@ yaml_syck_parser_handler
sv_bless(sv, gv_stashpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), TRUE));
}
}
+ Safefree(id_copy);
}
#endif
break;
@@ -480,7 +486,8 @@ yaml_syck_parser_handler
}
else {
/* bless it if necessary */
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
if ( type != NULL && strnEQ(type, "ref:", 4)) {
@@ -496,6 +503,7 @@ yaml_syck_parser_handler
else {
sv_bless(sv, gv_stashpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), TRUE));
}
+ Safefree(id_copy);
}
}
}
@@ -527,7 +535,8 @@ yaml_syck_parser_handler
}
else {
/* bless it if necessary */
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
if ( type != NULL && strnEQ(type, "regexp:", 7)) {
@@ -544,6 +553,7 @@ yaml_syck_parser_handler
else {
sv_bless(sv, gv_stashpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), TRUE));
}
+ Safefree(id_copy);
}
}
}
@@ -579,7 +589,8 @@ yaml_syck_parser_handler
#ifndef YAML_IS_JSON
if (id) {
/* bless it if necessary */
- char *lang = strtok(id, "/:");
+ char *id_copy = savepv(id);
+ char *lang = strtok(id_copy, "/:");
char *type = strtok(NULL, "");
if ( type != NULL ) {
@@ -602,6 +613,7 @@ yaml_syck_parser_handler
sv_bless(sv, gv_stashpv(form((type == NULL) ? "%s" : "%s::%s", lang, type), TRUE));
}
}
+ Safefree(id_copy);
}
#endif
}
@@ -960,6 +972,15 @@ yaml_syck_emitter_handler
#endif
}
}
+ {
+ /* Grow tag buffer if ref won't fit (prevents heap overflow) */
+ STRLEN need = strlen(tag) + strlen(ref) + 1;
+ if (need > bonus->tag_len) {
+ Renew(bonus->tag, need, char);
+ bonus->tag_len = need;
+ tag = bonus->tag;
+ }
+ }
strcat(tag, ref);
}
#endif
@@ -1284,6 +1305,7 @@ DumpYAMLImpl
emitter->anchor_format = "%d";
New(801, bonus->tag, 512, char);
+ bonus->tag_len = 512;
*(bonus->tag) = '\0';
bonus->dump_code = SvTRUE(use_code) || SvTRUE(dump_code);
bonus->implicit_binary = SvTRUE(implicit_binary);
diff --git a/t/tag-overflow.t b/t/tag-overflow.t
new file mode 100644
index 0000000..8570f13
--- /dev/null
+++ b/t/tag-overflow.t
@@ -0,0 +1,44 @@
+use FindBin;
+BEGIN { push @INC, $FindBin::Bin }
+
+use TestYAML tests => 6;
+
+$YAML::Syck::LoadBlessed = 1;
+
+# Test that Dump handles objects with very long class names without crashing.
+# This exercises the dynamic tag buffer growth added to prevent a heap overflow
+# when class names exceed the initial 512-byte allocation.
+
+my $short_class = 'A' x 100;
+my $long_class = 'B' x 600; # exceeds initial 512-byte tag buffer
+my $huge_class = 'C' x 2000;
+
+# Short class name (fits in initial buffer)
+{
+ my $obj = bless {}, $short_class;
+ my $yaml = Dump($obj);
+ like($yaml, qr/!!perl\/hash:\Q$short_class\E/, "dump short class name ($short_class)");
+
+ my $rt = Load($yaml);
+ is(ref($rt), $short_class, "roundtrip short class name");
+}
+
+# Long class name (exceeds 512-byte buffer)
+{
+ my $obj = bless {}, $long_class;
+ my $yaml = Dump($obj);
+ like($yaml, qr/!!perl\/hash:\Q$long_class\E/, "dump long class name (600 chars)");
+
+ my $rt = Load($yaml);
+ is(ref($rt), $long_class, "roundtrip long class name");
+}
+
+# Huge class name
+{
+ my $obj = bless {}, $huge_class;
+ my $yaml = Dump($obj);
+ like($yaml, qr/!!perl\/hash:\Q$huge_class\E/, "dump huge class name (2000 chars)");
+
+ my $rt = Load($yaml);
+ is(ref($rt), $huge_class, "roundtrip huge class name");
+}
--
2.53.0

View File

@ -7,15 +7,11 @@
Name: perl-YAML-Syck
Version: 1.30
Release: 6%{?dist}
Release: 5%{?dist}
Summary: Fast, lightweight YAML loader and dumper
License: BSD and MIT
URL: http://search.cpan.org/dist/YAML-Syck/
Source0: http://www.cpan.org/authors/id/T/TO/TODDR/YAML-Syck-%{version}.tar.gz
# Fix memory corruption error
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
BuildRequires: coreutils
BuildRequires: findutils
BuildRequires: gcc
@ -62,8 +58,6 @@ structures to YAML strings, and the other way around.
%prep
%setup -q -n YAML-Syck-%{version}
%patch -P0 -p1
%patch -P1 -p1
# Unbundle core and unused modules
rm -rvf inc/{parent.pm,PerlIO.pm,Scalar/,Test/}
@ -91,10 +85,6 @@ make test
%{_mandir}/man3/YAML::Syck.3*
%changelog
* Wed Mar 25 2026 Jitka Plesnikova <jplesnik@redhat.com> - 1.30-6
- Resolves: RHEL-156475
- Fix CVE-2026-4177
* Fri Feb 09 2018 Fedora Release Engineering <releng@fedoraproject.org> - 1.30-5
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild