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!( /&amp;/, '&' )
++      elsif ref == '&amp;'
++        '&'
++      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

Reply via email to