On Fri, Nov 22, 2013 at 12:04:54PM +0200, Christos Trochalakis wrote: > Package: ruby1.9.1 > Severity: grave > Tags: security > > Hi, > > The follow vulnerability was published for ruby: > > CVE-2013-4164: Heap Overflow in Floating Point Parsing > https://www.ruby-lang.org/en/news/2013/11/22/heap-overflow-in-floating-point-parsing-cve-2013-4164/
Patches for oldstable/stable attached. I don't use Ruby, these need review and testing in live setups. Cheers, Moritz
diff -Naur ruby1.8-1.8.7.302.orig//debian/patches/904-CVE-2013-4164.patch ruby1.8-1.8.7.302/debian/patches/904-CVE-2013-4164.patch --- ruby1.8-1.8.7.302.orig//debian/patches/904-CVE-2013-4164.patch 1970-01-01 01:00:00.000000000 +0100 +++ ruby1.8-1.8.7.302/debian/patches/904-CVE-2013-4164.patch 2013-11-25 13:47:13.070675130 +0100 @@ -0,0 +1,52 @@ +diff -aur ruby1.8-1.8.7.302.orig//util.c ruby1.8-1.8.7.302/util.c +--- ruby1.8-1.8.7.302.orig//util.c 2009-11-16 11:53:12.000000000 +0100 ++++ ruby1.8-1.8.7.302/util.c 2013-11-25 13:22:29.977023805 +0100 +@@ -888,6 +888,11 @@ + #else + #define MALLOC malloc + #endif ++#ifdef FREE ++extern void FREE(void*); ++#else ++#define FREE free ++#endif + + #ifndef Omit_Private_Memory + #ifndef PRIVATE_MEM +@@ -1172,7 +1177,7 @@ + #endif + + ACQUIRE_DTOA_LOCK(0); +- if ((rv = freelist[k]) != 0) { ++ if (k <= Kmax && (rv = freelist[k]) != 0) { + freelist[k] = rv->next; + } + else { +@@ -1182,7 +1187,7 @@ + #else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); +- if (pmem_next - private_mem + len <= PRIVATE_mem) { ++ if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } +@@ -1201,6 +1206,10 @@ + Bfree(Bigint *v) + { + if (v) { ++ if (v->k > Kmax) { ++ FREE(v); ++ return; ++ } + ACQUIRE_DTOA_LOCK(0); + v->next = freelist[v->k]; + freelist[v->k] = v; +@@ -2196,6 +2205,7 @@ + for (; c >= '0' && c <= '9'; c = *++s) { + have_dig: + nz++; ++ if (nf > DBL_DIG * 2) continue; + if (c -= '0') { + nf += nz; + for (i = 1; i < nz; i++) diff -Naur ruby1.8-1.8.7.302.orig//debian/patches/905-CVE-2013-1821.patch ruby1.8-1.8.7.302/debian/patches/905-CVE-2013-1821.patch --- ruby1.8-1.8.7.302.orig//debian/patches/905-CVE-2013-1821.patch 1970-01-01 01:00:00.000000000 +0100 +++ ruby1.8-1.8.7.302/debian/patches/905-CVE-2013-1821.patch 2013-11-25 13:47:16.506437919 +0100 @@ -0,0 +1,120 @@ +Description: Fix entity expansion DoS vulnerability in REXML + CVE-2013-1821 +Origin: upstream, http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=39384&view=patch +Bug-Debian: http://bugs.debian.org/702526 +Forwarded: not-needed +Author: Salvatore Bonaccorso <car...@debian.org> +Last-Update: 2013-03-09 + +--- a/lib/rexml/document.rb ++++ b/lib/rexml/document.rb +@@ -214,6 +214,18 @@ + return @@entity_expansion_limit + end + ++ @@entity_expansion_text_limit = 10_240 ++ ++ # Set the entity expansion limit. By default the limit is set to 10240. ++ def Document::entity_expansion_text_limit=( val ) ++ @@entity_expansion_text_limit = val ++ end ++ ++ # Get the entity expansion limit. By default the limit is set to 10000. ++ def Document::entity_expansion_text_limit ++ return @@entity_expansion_text_limit ++ end ++ + attr_reader :entity_expansion_count + + def record_entity_expansion +--- a/test/rexml/test_document.rb ++++ b/test/rexml/test_document.rb +@@ -63,4 +63,23 @@ + ensure + REXML::Document.entity_expansion_limit = 10000 + end ++ ++ def test_entity_string_limit ++ template = '<!DOCTYPE bomb [ <!ENTITY a "^" > ]> <bomb>$</bomb>' ++ len = 5120 # 5k per entity ++ template.sub!(/\^/, "B" * len) ++ ++ # 10k is OK ++ entities = '&a;' * 2 # 5k entity * 2 = 10k ++ xmldoc = REXML::Document.new(template.sub(/\$/, entities)) ++ assert_equal(len * 2, xmldoc.root.text.bytesize) ++ ++ # above 10k explodes ++ entities = '&a;' * 3 # 5k entity * 2 = 15k ++ xmldoc = REXML::Document.new(template.sub(/\$/, entities)) ++ assert_raises(RuntimeError) do ++ xmldoc.root.text ++ end ++ end ++ + end +--- a/lib/rexml/text.rb ++++ b/lib/rexml/text.rb +@@ -308,37 +308,35 @@ + + # Unescapes all possible entities + def Text::unnormalize( string, doctype=nil, filter=nil, illegal=nil ) +- rv = string.clone +- rv.gsub!( /\r\n?/, "\n" ) +- matches = rv.scan( REFERENCE ) +- return rv if matches.size == 0 +- rv.gsub!( NUMERICENTITY ) {|m| +- m=$1 +- m = "0#{m}" if m[0] == ?x +- [Integer(m)].pack('U*') ++ sum = 0 ++ string.gsub( /\r\n?/, "\n" ).gsub( REFERENCE ) { ++ s = Text.expand($&, doctype, filter) ++ if sum + s.bytesize > Document.entity_expansion_text_limit ++ raise "entity expansion has grown too large" ++ else ++ sum += s.bytesize ++ end ++ s + } +- matches.collect!{|x|x[0]}.compact! +- if matches.size > 0 +- if doctype +- matches.each do |entity_reference| +- unless filter and filter.include?(entity_reference) +- entity_value = doctype.entity( entity_reference ) +- re = /&#{entity_reference};/ +- rv.gsub!( re, entity_value ) if entity_value +- end +- end ++ end ++ ++ def Text.expand(ref, doctype, filter) ++ if ref[1] == ?# ++ if ref[2] == ?x ++ [ref[3...-1].to_i(16)].pack('U*') + else +- matches.each do |entity_reference| +- unless filter and filter.include?(entity_reference) +- entity_value = DocType::DEFAULT_ENTITIES[ entity_reference ] +- re = /&#{entity_reference};/ +- rv.gsub!( re, entity_value.value ) if entity_value +- end +- end ++ [ref[2...-1].to_i].pack('U*') + end +- rv.gsub!( /&/, '&' ) ++ elsif ref == '&' ++ '&' ++ elsif filter and filter.include?( ref[1...-1] ) ++ ref ++ elsif doctype ++ doctype.entity( ref[1...-1] ) or ref ++ else ++ entity_value = DocType::DEFAULT_ENTITIES[ ref[1...-1] ] ++ entity_value ? entity_value.value : ref + end +- rv + end + end + end diff -Naur ruby1.8-1.8.7.302.orig//debian/patches/906-CVE-2013-4073.patch ruby1.8-1.8.7.302/debian/patches/906-CVE-2013-4073.patch --- ruby1.8-1.8.7.302.orig//debian/patches/906-CVE-2013-4073.patch 1970-01-01 01:00:00.000000000 +0100 +++ ruby1.8-1.8.7.302/debian/patches/906-CVE-2013-4073.patch 2013-11-25 13:47:21.230111890 +0100 @@ -0,0 +1,79 @@ +Description: fix incorrect ssl hostname verification +Origin: upstream, https://github.com/ruby/ruby/commit/961bf7496ded3acfe847cf56fa90bbdcfd6e614f +Origin: backport, https://github.com/ruby/ruby/commit/a3a62f87e144be31b9ca8ad6415b207f43f4e126 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=714541 +Bug: https://bugs.ruby-lang.org/issues/8575 + +Index: ruby1.8-1.8.7.352/ext/openssl/lib/openssl/ssl-internal.rb +=================================================================== +--- ruby1.8-1.8.7.352.orig/ext/openssl/lib/openssl/ssl-internal.rb 2013-07-08 10:16:49.926709322 -0400 ++++ ruby1.8-1.8.7.352/ext/openssl/lib/openssl/ssl-internal.rb 2013-07-08 10:16:49.918709322 -0400 +@@ -90,14 +90,22 @@ + should_verify_common_name = true + cert.extensions.each{|ext| + next if ext.oid != "subjectAltName" +- ext.value.split(/,\s+/).each{|general_name| +- if /\ADNS:(.*)/ =~ general_name ++ ostr = OpenSSL::ASN1.decode(ext.to_der).value.last ++ sequence = OpenSSL::ASN1.decode(ostr.value) ++ sequence.value.each{|san| ++ case san.tag ++ when 2 # dNSName in GeneralName (RFC5280) + should_verify_common_name = false +- reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+") ++ reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+") + return true if /\A#{reg}\z/i =~ hostname +- elsif /\AIP Address:(.*)/ =~ general_name ++ when 7 # iPAddress in GeneralName (RFC5280) + should_verify_common_name = false +- return true if $1 == hostname ++ # follows GENERAL_NAME_print() in x509v3/v3_alt.c ++ if san.value.size == 4 ++ return true if san.value.unpack('C*').join('.') == hostname ++ elsif san.value.size == 16 ++ return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname ++ end + end + } + } +Index: ruby1.8-1.8.7.352/test/openssl/test_ssl.rb +=================================================================== +--- ruby1.8-1.8.7.352.orig/test/openssl/test_ssl.rb 2013-07-08 10:16:49.926709322 -0400 ++++ ruby1.8-1.8.7.352/test/openssl/test_ssl.rb 2013-07-08 10:16:49.922709322 -0400 +@@ -532,6 +532,36 @@ + end + end + end ++ ++ def test_verify_certificate_identity ++ [true, false].each do |criticality| ++ cert = create_null_byte_SAN_certificate(criticality) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com\0.evil.com')) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1')) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17')) ++ end ++ end ++ ++ # Create NULL byte SAN certificate ++ def create_null_byte_SAN_certificate(critical = false) ++ ef = OpenSSL::X509::ExtensionFactory.new ++ cert = OpenSSL::X509::Certificate.new ++ cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site" ++ ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17', critical) ++ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der) ++ san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo } ++ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der) ++ san_list_asn1.value[0].value = 'www.example.com\0.evil.com' ++ pos = critical ? 2 : 1 ++ ext_asn1.value[pos].value = san_list_asn1.to_der ++ real_ext = OpenSSL::X509::Extension.new ext_asn1 ++ cert.add_extension(real_ext) ++ cert ++ end ++ + end + + end diff -Naur ruby1.8-1.8.7.302.orig//debian/patches/series ruby1.8-1.8.7.302/debian/patches/series --- ruby1.8-1.8.7.302.orig//debian/patches/series 2013-11-25 13:46:28.000000000 +0100 +++ ruby1.8-1.8.7.302/debian/patches/series 2013-11-25 13:48:16.634288260 +0100 @@ -10,3 +10,6 @@ 100730_disable_getsetcontext_on_nptl.patch 100730_verbose-tests.patch 100901_threading_fixes.patch +904-CVE-2013-4164.patch +905-CVE-2013-1821.patch +906-CVE-2013-4073.patch
diff -Naur ruby1.8-1.8.7.358.orig/debian/patches/CVE-2013-4073_CVE-2013-4164.patch ruby1.8-1.8.7.358/debian/patches/CVE-2013-4073_CVE-2013-4164.patch --- ruby1.8-1.8.7.358.orig/debian/patches/CVE-2013-4073_CVE-2013-4164.patch 1970-01-01 01:00:00.000000000 +0100 +++ ruby1.8-1.8.7.358/debian/patches/CVE-2013-4073_CVE-2013-4164.patch 2013-11-25 14:06:19.484510093 +0100 @@ -0,0 +1,123 @@ +diff -Naur ruby1.8-1.8.7.358.orig/ext/openssl/lib/openssl/ssl-internal.rb ruby1.8-1.8.7.358/ext/openssl/lib/openssl/ssl-internal.rb +--- ruby1.8-1.8.7.358.orig/ext/openssl/lib/openssl/ssl-internal.rb 2010-05-25 01:58:49.000000000 +0200 ++++ ruby1.8-1.8.7.358/ext/openssl/lib/openssl/ssl-internal.rb 2013-11-25 14:05:20.239865492 +0100 +@@ -90,14 +90,22 @@ + should_verify_common_name = true + cert.extensions.each{|ext| + next if ext.oid != "subjectAltName" +- ext.value.split(/,\s+/).each{|general_name| +- if /\ADNS:(.*)/ =~ general_name ++ ostr = OpenSSL::ASN1.decode(ext.to_der).value.last ++ sequence = OpenSSL::ASN1.decode(ostr.value) ++ sequence.value.each{|san| ++ case san.tag ++ when 2 # dNSName in GeneralName (RFC5280) + should_verify_common_name = false +- reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+") ++ reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+") + return true if /\A#{reg}\z/i =~ hostname +- elsif /\AIP Address:(.*)/ =~ general_name ++ when 7 # iPAddress in GeneralName (RFC5280) + should_verify_common_name = false +- return true if $1 == hostname ++ # follows GENERAL_NAME_print() in x509v3/v3_alt.c ++ if san.value.size == 4 ++ return true if san.value.unpack('C*').join('.') == hostname ++ elsif san.value.size == 16 ++ return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname ++ end + end + } + } +diff -Naur ruby1.8-1.8.7.358.orig/test/openssl/test_ssl.rb ruby1.8-1.8.7.358/test/openssl/test_ssl.rb +--- ruby1.8-1.8.7.358.orig/test/openssl/test_ssl.rb 2012-02-08 07:09:40.000000000 +0100 ++++ ruby1.8-1.8.7.358/test/openssl/test_ssl.rb 2013-11-25 14:05:20.239865492 +0100 +@@ -547,6 +547,36 @@ + ssl.close + } + end ++ ++ def test_verify_certificate_identity ++ [true, false].each do |criticality| ++ cert = create_null_byte_SAN_certificate(criticality) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, 'www.example.com\0.evil.com')) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1')) ++ assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) ++ assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17')) ++ end ++ end ++ ++ # Create NULL byte SAN certificate ++ def create_null_byte_SAN_certificate(critical = false) ++ ef = OpenSSL::X509::ExtensionFactory.new ++ cert = OpenSSL::X509::Certificate.new ++ cert.subject = OpenSSL::X509::Name.parse "/DC=some/DC=site/CN=Some Site" ++ ext = ef.create_ext('subjectAltName', 'DNS:placeholder,IP:192.168.7.1,IP:13::17', critical) ++ ext_asn1 = OpenSSL::ASN1.decode(ext.to_der) ++ san_list_der = ext_asn1.value.reduce(nil) { |memo,val| val.tag == 4 ? val.value : memo } ++ san_list_asn1 = OpenSSL::ASN1.decode(san_list_der) ++ san_list_asn1.value[0].value = 'www.example.com\0.evil.com' ++ pos = critical ? 2 : 1 ++ ext_asn1.value[pos].value = san_list_asn1.to_der ++ real_ext = OpenSSL::X509::Extension.new ext_asn1 ++ cert.add_extension(real_ext) ++ cert ++ end ++ + end + + end +diff -Naur ruby1.8-1.8.7.358.orig/util.c ruby1.8-1.8.7.358/util.c +--- ruby1.8-1.8.7.358.orig/util.c 2010-11-22 08:21:34.000000000 +0100 ++++ ruby1.8-1.8.7.358/util.c 2013-11-25 14:05:32.808002236 +0100 +@@ -892,6 +892,11 @@ + #else + #define MALLOC malloc + #endif ++#ifdef FREE ++extern void FREE(void*); ++#else ++#define FREE free ++#endif + + #ifndef Omit_Private_Memory + #ifndef PRIVATE_MEM +@@ -1176,7 +1181,7 @@ + #endif + + ACQUIRE_DTOA_LOCK(0); +- if ((rv = freelist[k]) != 0) { ++ if (k <= Kmax && (rv = freelist[k]) != 0) { + freelist[k] = rv->next; + } + else { +@@ -1186,7 +1191,7 @@ + #else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); +- if (pmem_next - private_mem + len <= PRIVATE_mem) { ++ if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } +@@ -1205,6 +1210,10 @@ + Bfree(Bigint *v) + { + if (v) { ++ if (v->k > Kmax) { ++ FREE(v); ++ return; ++ } + ACQUIRE_DTOA_LOCK(0); + v->next = freelist[v->k]; + freelist[v->k] = v; +@@ -2200,6 +2209,7 @@ + for (; c >= '0' && c <= '9'; c = *++s) { + have_dig: + nz++; ++ if (nf > DBL_DIG * 2) continue; + if (c -= '0') { + nf += nz; + for (i = 1; i < nz; i++) diff -Naur ruby1.8-1.8.7.358.orig/debian/patches/series ruby1.8-1.8.7.358/debian/patches/series --- ruby1.8-1.8.7.358.orig/debian/patches/series 2013-03-12 08:32:40.000000000 +0100 +++ ruby1.8-1.8.7.358/debian/patches/series 2013-11-25 14:06:27.596598355 +0100 @@ -15,3 +15,4 @@ use-ldflags.patch CVE-2012-4481.patch CVE-2013-1821.patch +CVE-2013-4073_CVE-2013-4164.patch