CVE-2009-3627: Improper Input Validation (CWE-20)
value: The decode_entities function in util.c in HTML-Parser before 3.63 allows context-dependent attackers to cause a denial of service (infinite loop) via an incomplete SGML numeric character reference, which triggers generation of an invalid UTF-8 character.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Published: 2009-10-29T14:30Z
Repository: https://github.com/gisle/html-parser
Language: Other
Files changed: t/entities.t, util.c
Commit date: 2009-10-22 21:49:23 +0200
Commit message: decode_entities confused by trailing incomplete entity

Mark Martinec reported crashed when running SpamAssassin, given a
particular HTML junk mail to parse.  The problem was caused by
HTML::Parsers decode_entities function confusing itself when it
encountered strings with incomplete entities at the end of the string.

Diff: diff --git a/t/entities.t b/t/entities.t
index 7f6a29a..e96501c 100644
--- a/t/entities.t
+++ b/t/entities.t
@@ -1,8 +1,8 @@
 use HTML::Entities qw(decode_entities encode_entities encode_entities_numeric);
 
-use Test::More tests => 16;
+use Test::More tests => 17;
 
 $a = "V&aring;re norske tegn b&oslash;r &#230res";
 
 decode_entities($a);
 
@@ -69,10 +69,12 @@ is(decode_entities("abc&def&ghi&abc;&def;"), "abc&def&ghi&abc;&def;");
 
 # Decoding of &apos;
 is(decode_entities("&apos;"), "'");
 is(encode_entities("'", "'"), "&#39;");
 
+is(decode_entities("Attention Home&#959&#969n&#1257rs...1&#1109t T&#1110&#1084e E&#957&#1257&#1075"),
+  "Attention Home\x{3BF}\x{3C9}n\x{4E9}rs...1\x{455}t T\x{456}\x{43C}e E\x{3BD}\x{4E9}\x{433}");
 
 __END__
 # Quoted from rfc1866.txt
 
 14. Proposed Entities
diff --git a/util.c b/util.c
index 28fec78..6f56a2b 100644
--- a/util.c
+++ b/util.c
@@ -92,18 +92,18 @@ decode_entities(pTHX_ SV* sv, HV* entity2char, bool expand_prefix)
 	    continue;
 
 	ent_start = s;
 	repl = 0;
 
-	if (*s == '#') {
+	if (s < end && *s == '#') {
 	    UV num = 0;
 	    UV prev = 0;
 	    int ok = 0;
 	    s++;
-	    if (*s == 'x' || *s == 'X') {
+	    if (s < end && (*s == 'x' || *s == 'X')) {
 		s++;
-		while (*s) {
+		while (s < end) {
 		    char *tmp = strchr(PL_hexdigit, *s);
 		    if (!tmp)
 			break;
 		    num = num << 4 | ((tmp - PL_hexdigit) & 15);
 		    if (prev && num <= prev) {
@@ -115,11 +115,11 @@ decode_entities(pTHX_ SV* sv, HV* entity2char, bool expand_prefix)
 		    s++;
 		    ok = 1;
 		}
 	    }
 	    else {
-		while (isDIGIT(*s)) {
+		while (s < end && isDIGIT(*s)) {
 		    num = num * 10 + (*s - '0');
 		    if (prev && num < prev) {
 			/* overflow */
 			ok = 0;
 			break;
@@ -178,11 +178,11 @@ decode_entities(pTHX_ SV* sv, HV* entity2char, bool expand_prefix)
 #endif
 	    }
 	}
 	else {
 	    char *ent_name = s;
-	    while (isALNUM(*s))
+	    while (s < end && isALNUM(*s))
 		s++;
 	    if (ent_name != s && entity2char) {
 		SV** svp;
 		if (              (svp = hv_fetch(entity2char, ent_name, s - ent_name, 0)) ||
 		    (*s == ';' && (svp = hv_fetch(entity2char, ent_name, s - ent_name + 1, 0)))
@@ -214,11 +214,11 @@ decode_entities(pTHX_ SV* sv, HV* entity2char, bool expand_prefix)
 #endif
 	}
 
 	if (repl) {
 	    char *repl_allocated = 0;
-	    if (*s == ';')
+	    if (s < end && *s == ';')
 		s++;
 	    t--;  /* '&' already copied, undo it */
 
 #ifdef UNICODE_HTML_PARSER
 	    if (*s != '&') {

Vulnerable code: use Test::More tests => 16;
	if (*s == '#') {
	    if (*s == 'x' || *s == 'X') {
		while (*s) {
		while (isDIGIT(*s)) {
	    while (isALNUM(*s))
	    if (*s == ';')
Fixed code: use Test::More tests => 17;
is(decode_entities("Attention Home&#959&#969n&#1257rs...1&#1109t T&#1110&#1084e E&#957&#1257&#1075"),
  "Attention Home\x{3BF}\x{3C9}n\x{4E9}rs...1\x{455}t T\x{456}\x{43C}e E\x{3BD}\x{4E9}\x{433}");
	if (s < end && *s == '#') {
	    if (s < end && (*s == 'x' || *s == 'X')) {
		while (s < end) {
		while (s < end && isDIGIT(*s)) {
	    while (s < end && isALNUM(*s))
	    if (s < end && *s == ';')