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 <[email protected]>
+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