Hi security team,

I have prepared a security update for ruby2.3.

It includes all the pending recent CVE's, plus a fix for a bug that
causes runaway child processes hogging the CPU, noticed at least in
puppet.

The test suite still passes both during build, and under autopkgtest. I
am running these packages on my workstation since yesterday. The patches
are targeted enough that I don't expect any regressions.

As I explained before, unfortunately the patch management for ruby2.3 is
not optimal, so I attach both the debdiff and the individual patches
that I applied to the git repository. The later will make your review
work easier.

You can also inspect the git repository:
https://anonscm.debian.org/cgit/collab-maint/ruby.git/log/?h=debian/stretch
diff --git a/debian/changelog b/debian/changelog
index 068662c7..bad775fd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,24 @@
+ruby2.3 (2.3.3-1+deb9u2) stretch-security; urgency=high
+
+  * asn1: fix out-of-bounds read in decoding constructed objects
+    [CVE-2017-14033] (Closes: #875928)
+    Original patch by Kazuki Yamaguchi; backported from the standalone openssl package
+  * lib/webrick/log.rb: sanitize any type of logs
+    [CVE-2017-10784] (Closes: #875931)
+    Original patch by Yusuke Endoh; backported to Ruby 2.3 by Usaku NAKAMURA
+  * fix Buffer underrun vulnerability in Kernel.sprintf
+    [CVE-2017-0898] (Closes: #875936)
+    Backported to Ruby 2.3 by Usaku NAKAMURA
+  * Whitelist classes and symbols that are in Gem spec YAML
+    [CVE-2017-0903] (Closes: #879231)
+    Original patch by Aaron Patterson; backported from the standalone Rubygems
+    package
+  * thread_pthread.c: do not wakeup inside child processes
+    Avoid child Ruby processed being stuck in a busy loop (Closes: #876377)
+    Original patch by Eric Wong
+
+ -- Antonio Terceiro <terce...@debian.org>  Sun, 22 Oct 2017 12:45:48 -0200
+
 ruby2.3 (2.3.3-1+deb9u1) stretch-security; urgency=high
 
   * Fix arbitrary heap exposure problem in the JSON library (Closes: #873906)
diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c
index 89da5949..44444012 100644
--- a/ext/openssl/ossl_asn1.c
+++ b/ext/openssl/ossl_asn1.c
@@ -870,19 +870,18 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length,
 {
     VALUE value, asn1data, ary;
     int infinite;
-    long off = *offset;
+    long available_len, off = *offset;
 
     infinite = (j == 0x21);
     ary = rb_ary_new();
 
-    while (length > 0 || infinite) {
+    available_len = infinite ? max_len : length;
+    while (available_len > 0) {
 	long inner_read = 0;
-	value = ossl_asn1_decode0(pp, max_len, &off, depth + 1, yield, &inner_read);
+	value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read);
 	*num_read += inner_read;
-	max_len -= inner_read;
+	available_len -= inner_read;
 	rb_ary_push(ary, value);
-	if (length > 0)
-	    length -= inner_read;
 
 	if (infinite &&
 	    NUM2INT(ossl_asn1_get_tag(value)) == V_ASN1_EOC &&
@@ -973,7 +972,7 @@ ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth,
     if(j & V_ASN1_CONSTRUCTED) {
 	*pp += hlen;
 	off += hlen;
-	asn1data = int_ossl_asn1_decode0_cons(pp, length, len, &off, depth, yield, j, tag, tag_class, &inner_read);
+	asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read);
 	inner_read += hlen;
     }
     else {
diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 9c0219ce..3528a15f 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -602,7 +602,7 @@ def self.load_yaml
 
     unless test_syck
       begin
-        gem 'psych', '>= 1.2.1'
+        gem 'psych', '>= 2.0.0'
       rescue Gem::LoadError
         # It's OK if the user does not have the psych gem installed.  We will
         # attempt to require the stdlib version
@@ -626,6 +626,7 @@ def self.load_yaml
     end
 
     require 'yaml'
+    require 'rubygems/safe_yaml'
 
     # If we're supposed to be using syck, then we may have to force
     # activate it via the YAML::ENGINE API.
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index de90cbfd..2bcd830f 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -332,7 +332,7 @@ def load_file(filename)
     return {} unless filename and File.exist? filename
 
     begin
-      content = YAML.load(File.read(filename))
+      content = Gem::SafeYAML.load(File.read(filename))
       unless content.kind_of? Hash
         warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
         return {}
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 0d9adba2..ab49ea2d 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -466,7 +466,7 @@ def read_checksums gem
 
     @checksums = gem.seek 'checksums.yaml.gz' do |entry|
       Zlib::GzipReader.wrap entry do |gz_io|
-        YAML.load gz_io.read
+        Gem::SafeYAML.safe_load gz_io.read
       end
     end
   end
diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb
index 5e722baa..071f7141 100644
--- a/lib/rubygems/package/old.rb
+++ b/lib/rubygems/package/old.rb
@@ -101,7 +101,7 @@ def file_list io # :nodoc:
       header << line
     end
 
-    YAML.load header
+    Gem::SafeYAML.safe_load header
   end
 
   ##
diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb
new file mode 100644
index 00000000..b98cfaa5
--- /dev/null
+++ b/lib/rubygems/safe_yaml.rb
@@ -0,0 +1,48 @@
+module Gem
+
+  ###
+  # This module is used for safely loading YAML specs from a gem.  The
+  # `safe_load` method defined on this module is specifically designed for
+  # loading Gem specifications.  For loading other YAML safely, please see
+  # Psych.safe_load
+
+  module SafeYAML
+    WHITELISTED_CLASSES = %w(
+      Symbol
+      Time
+      Date
+      Gem::Dependency
+      Gem::Platform
+      Gem::Requirement
+      Gem::Specification
+      Gem::Version
+      Gem::Version::Requirement
+      YAML::Syck::DefaultKey
+      Syck::DefaultKey
+    )
+
+    WHITELISTED_SYMBOLS = %w(
+      development
+      runtime
+    )
+
+    if ::YAML.respond_to? :safe_load
+      def self.safe_load input
+        ::YAML.safe_load(input, WHITELISTED_CLASSES, WHITELISTED_SYMBOLS, true)
+      end
+
+      def self.load input
+        ::YAML.safe_load(input, [::Symbol])
+      end
+    else
+      warn "YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0)."
+      def self.safe_load input, *args
+        ::YAML.load input
+      end
+
+      def self.load input
+        ::YAML.load input
+      end
+    end
+  end
+end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 2519b96b..748b3367 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -1101,7 +1101,7 @@ def self.from_yaml(input)
     Gem.load_yaml
 
     input = normalize_yaml_input input
-    spec = YAML.load input
+    spec = Gem::SafeYAML.safe_load input
 
     if spec && spec.class == FalseClass then
       raise Gem::EndOfYAMLException
diff --git a/lib/webrick/httpstatus.rb b/lib/webrick/httpstatus.rb
index 8664da26..ff9f1820 100644
--- a/lib/webrick/httpstatus.rb
+++ b/lib/webrick/httpstatus.rb
@@ -23,10 +23,6 @@ module HTTPStatus
     ##
     # Root of the HTTP status class hierarchy
     class Status < StandardError
-      def initialize(*args) # :nodoc:
-        args[0] = AccessLog.escape(args[0]) unless args.empty?
-        super(*args)
-      end
       class << self
         attr_reader :code, :reason_phrase # :nodoc:
       end
diff --git a/lib/webrick/log.rb b/lib/webrick/log.rb
index 7542d8f7..41e907cd 100644
--- a/lib/webrick/log.rb
+++ b/lib/webrick/log.rb
@@ -118,10 +118,10 @@ def debug?; @level >= DEBUG; end
     # * Otherwise it will return +arg+.inspect.
     def format(arg)
       if arg.is_a?(Exception)
-        "#{arg.class}: #{arg.message}\n\t" <<
+        "#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" <<
         arg.backtrace.join("\n\t") << "\n"
       elsif arg.respond_to?(:to_str)
-        arg.to_str
+        AccessLog.escape(arg.to_str)
       else
         arg.inspect
       end
diff --git a/sprintf.c b/sprintf.c
index b022c5de..a9bbc8fb 100644
--- a/sprintf.c
+++ b/sprintf.c
@@ -1147,6 +1147,8 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
 		fval = RFLOAT_VALUE(rb_Float(val));
 		if (isnan(fval) || isinf(fval)) {
 		    const char *expr;
+		    int elen;
+		    char sign = '\0';
 
 		    if (isnan(fval)) {
 			expr = "NaN";
@@ -1155,33 +1157,28 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
 			expr = "Inf";
 		    }
 		    need = (int)strlen(expr);
-		    if ((!isnan(fval) && fval < 0.0) || (flags & FPLUS))
-			need++;
+		    elen = need;
+		    i = 0;
+		    if (!isnan(fval) && fval < 0.0)
+			sign = '-';
+		    else if (flags & (FPLUS|FSPACE))
+			sign = (flags & FPLUS) ? '+' : ' ';
+		    if (sign)
+			++need;
 		    if ((flags & FWIDTH) && need < width)
 			need = width;
 
-		    CHECK(need + 1);
-		    snprintf(&buf[blen], need + 1, "%*s", need, "");
+		    FILL(' ', need);
 		    if (flags & FMINUS) {
-			if (!isnan(fval) && fval < 0.0)
-			    buf[blen++] = '-';
-			else if (flags & FPLUS)
-			    buf[blen++] = '+';
-			else if (flags & FSPACE)
-			    blen++;
-			memcpy(&buf[blen], expr, strlen(expr));
+			if (sign)
+			    buf[blen - need--] = sign;
+			memcpy(&buf[blen - need], expr, elen);
 		    }
 		    else {
-			if (!isnan(fval) && fval < 0.0)
-			    buf[blen + need - strlen(expr) - 1] = '-';
-			else if (flags & FPLUS)
-			    buf[blen + need - strlen(expr) - 1] = '+';
-			else if ((flags & FSPACE) && need > width)
-			    blen++;
-			memcpy(&buf[blen + need - strlen(expr)], expr,
-			       strlen(expr));
+			if (sign)
+			    buf[blen - elen - 1] = sign;
+			memcpy(&buf[blen - elen], expr, elen);
 		    }
-		    blen += strlen(&buf[blen]);
 		    break;
 		}
 
diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb
index fd2118d8..109fd95f 100644
--- a/test/openssl/test_asn1.rb
+++ b/test/openssl/test_asn1.rb
@@ -596,6 +596,29 @@ def test_recursive_octet_string_parse
     assert_equal(false, asn1.value[3].infinite_length)
   end
 
+  def test_decode_constructed_overread
+    test = %w{ 31 06 31 02 30 02 05 00 }
+    #                          ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 2, ret.size
+    assert_equal 17, ret[0][6]
+    assert_equal 17, ret[1][6]
+
+    test = %w{ 31 80 30 03 00 00 }
+    #                    ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 1, ret.size
+    assert_equal 17, ret[0][6]
+  end
+
   private
 
   def assert_universal(tag, asn1)
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index 8aa0cbfe..618edbe2 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -84,6 +84,18 @@ def test_nan
     assert_equal("NaN", sprintf("%-f", nan))
     assert_equal("+NaN", sprintf("%+f", nan))
 
+    assert_equal("NaN", sprintf("%3f", nan))
+    assert_equal("NaN", sprintf("%-3f", nan))
+    assert_equal("+NaN", sprintf("%+3f", nan))
+
+    assert_equal(" NaN", sprintf("% 3f", nan))
+    assert_equal(" NaN", sprintf("%- 3f", nan))
+    assert_equal("+NaN", sprintf("%+ 3f", nan))
+
+    assert_equal(" NaN", sprintf("% 03f", nan))
+    assert_equal(" NaN", sprintf("%- 03f", nan))
+    assert_equal("+NaN", sprintf("%+ 03f", nan))
+
     assert_equal("     NaN", sprintf("%8f", nan))
     assert_equal("NaN     ", sprintf("%-8f", nan))
     assert_equal("    +NaN", sprintf("%+8f", nan))
@@ -107,6 +119,26 @@ def test_inf
     assert_equal("Inf", sprintf("%-f", inf))
     assert_equal("+Inf", sprintf("%+f", inf))
 
+    assert_equal(" Inf", sprintf("% f", inf))
+    assert_equal(" Inf", sprintf("%- f", inf))
+    assert_equal("+Inf", sprintf("%+ f", inf))
+
+    assert_equal(" Inf", sprintf("% 0f", inf))
+    assert_equal(" Inf", sprintf("%- 0f", inf))
+    assert_equal("+Inf", sprintf("%+ 0f", inf))
+
+    assert_equal("Inf", sprintf("%3f", inf))
+    assert_equal("Inf", sprintf("%-3f", inf))
+    assert_equal("+Inf", sprintf("%+3f", inf))
+
+    assert_equal(" Inf", sprintf("% 3f", inf))
+    assert_equal(" Inf", sprintf("%- 3f", inf))
+    assert_equal("+Inf", sprintf("%+ 3f", inf))
+
+    assert_equal(" Inf", sprintf("% 03f", inf))
+    assert_equal(" Inf", sprintf("%- 03f", inf))
+    assert_equal("+Inf", sprintf("%+ 03f", inf))
+
     assert_equal("     Inf", sprintf("%8f", inf))
     assert_equal("Inf     ", sprintf("%-8f", inf))
     assert_equal("    +Inf", sprintf("%+8f", inf))
@@ -127,6 +159,26 @@ def test_inf
     assert_equal("-Inf", sprintf("%-f", -inf))
     assert_equal("-Inf", sprintf("%+f", -inf))
 
+    assert_equal("-Inf", sprintf("% f", -inf))
+    assert_equal("-Inf", sprintf("%- f", -inf))
+    assert_equal("-Inf", sprintf("%+ f", -inf))
+
+    assert_equal("-Inf", sprintf("% 0f", -inf))
+    assert_equal("-Inf", sprintf("%- 0f", -inf))
+    assert_equal("-Inf", sprintf("%+ 0f", -inf))
+
+    assert_equal("-Inf", sprintf("%4f", -inf))
+    assert_equal("-Inf", sprintf("%-4f", -inf))
+    assert_equal("-Inf", sprintf("%+4f", -inf))
+
+    assert_equal("-Inf", sprintf("% 4f", -inf))
+    assert_equal("-Inf", sprintf("%- 4f", -inf))
+    assert_equal("-Inf", sprintf("%+ 4f", -inf))
+
+    assert_equal("-Inf", sprintf("% 04f", -inf))
+    assert_equal("-Inf", sprintf("%- 04f", -inf))
+    assert_equal("-Inf", sprintf("%+ 04f", -inf))
+
     assert_equal("    -Inf", sprintf("%8f", -inf))
     assert_equal("-Inf    ", sprintf("%-8f", -inf))
     assert_equal("    -Inf", sprintf("%+8f", -inf))
diff --git a/test/webrick/test_httpauth.rb b/test/webrick/test_httpauth.rb
index 4376b918..5560740b 100644
--- a/test/webrick/test_httpauth.rb
+++ b/test/webrick/test_httpauth.rb
@@ -98,6 +98,42 @@ def test_basic_auth3
     }
   end
 
+  def test_bad_username_with_control_characters
+    log_tester = lambda {|log, access_log|
+      assert_equal(2, log.length)
+      assert_match(/ERROR Basic WEBrick's realm: foo\\ebar: the user is not allowed./, log[0])
+      assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[1])
+    }
+    TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
+      realm = "WEBrick's realm"
+      path = "/basic_auth"
+
+      Tempfile.create("test_webrick_auth") {|tmpfile|
+        tmpfile.close
+        tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
+        tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
+        tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
+        tmp_pass.flush
+
+        htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
+        users = []
+        htpasswd.each{|user, pass| users << user }
+        server.mount_proc(path){|req, res|
+          auth = WEBrick::HTTPAuth::BasicAuth.new(
+            :Realm => realm, :UserDB => htpasswd,
+            :Logger => server.logger
+          )
+          auth.authenticate(req, res)
+          res.body = "hoge"
+        }
+        http = Net::HTTP.new(addr, port)
+        g = Net::HTTP::Get.new(path)
+        g.basic_auth("foo\ebar", "passwd")
+        http.request(g){|res| assert_not_equal("hoge", res.body, log.call) }
+      }
+    }
+  end
+
   DIGESTRES_ = /
     ([a-zA-Z\-]+)
       [ \t]*(?:\r\n[ \t]*)*
diff --git a/thread_pthread.c b/thread_pthread.c
index 4aa2d620..023e96e6 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1313,17 +1313,21 @@ void
 rb_thread_wakeup_timer_thread(void)
 {
     /* must be safe inside sighandler, so no mutex */
-    ATOMIC_INC(timer_thread_pipe.writing);
-    rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.normal[1]);
-    ATOMIC_DEC(timer_thread_pipe.writing);
+    if (timer_thread_pipe.owner_process == getpid()) {
+	ATOMIC_INC(timer_thread_pipe.writing);
+	rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.normal[1]);
+	ATOMIC_DEC(timer_thread_pipe.writing);
+    }
 }
 
 static void
 rb_thread_wakeup_timer_thread_low(void)
 {
-    ATOMIC_INC(timer_thread_pipe.writing);
-    rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.low[1]);
-    ATOMIC_DEC(timer_thread_pipe.writing);
+    if (timer_thread_pipe.owner_process == getpid()) {
+	ATOMIC_INC(timer_thread_pipe.writing);
+	rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.low[1]);
+	ATOMIC_DEC(timer_thread_pipe.writing);
+    }
 }
 
 /* VM-dependent API is not available for this function */
From c0da1978714b1dae7e98e9621fcf3044d4ee03bc Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k...@rhe.jp>
Date: Mon, 19 Sep 2016 15:38:44 +0900
Subject: [PATCH 1/5] asn1: fix out-of-bounds read in decoding constructed
 objects [CVE-2017-14033]

OpenSSL::ASN1.{decode,decode_all,traverse} have a bug of out-of-bounds
read. int_ossl_asn1_decode0_cons() does not give the correct available
length to ossl_asn1_decode() when decoding the inner components of a
constructed object. This can cause out-of-bounds read if a crafted input
given.

Reference: https://hackerone.com/reports/170316

------------------------------------------------------------------------

This patch has been backported from the original patch from the
standlone openssl Ruby package

https://github.com/ruby/openssl/commit/1648afef33c1d97fb203c82291b8a61269e85d3b

Backport-by: Antonio Terceiro <terce...@debian.org>
Closes: #875928
---
 ext/openssl/ossl_asn1.c   | 13 ++++++-------
 test/openssl/test_asn1.rb | 23 +++++++++++++++++++++++
 2 files changed, 29 insertions(+), 7 deletions(-)

diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c
index 89da5949..44444012 100644
--- a/ext/openssl/ossl_asn1.c
+++ b/ext/openssl/ossl_asn1.c
@@ -870,19 +870,18 @@ int_ossl_asn1_decode0_cons(unsigned char **pp, long max_len, long length,
 {
     VALUE value, asn1data, ary;
     int infinite;
-    long off = *offset;
+    long available_len, off = *offset;
 
     infinite = (j == 0x21);
     ary = rb_ary_new();
 
-    while (length > 0 || infinite) {
+    available_len = infinite ? max_len : length;
+    while (available_len > 0) {
 	long inner_read = 0;
-	value = ossl_asn1_decode0(pp, max_len, &off, depth + 1, yield, &inner_read);
+	value = ossl_asn1_decode0(pp, available_len, &off, depth + 1, yield, &inner_read);
 	*num_read += inner_read;
-	max_len -= inner_read;
+	available_len -= inner_read;
 	rb_ary_push(ary, value);
-	if (length > 0)
-	    length -= inner_read;
 
 	if (infinite &&
 	    NUM2INT(ossl_asn1_get_tag(value)) == V_ASN1_EOC &&
@@ -973,7 +972,7 @@ ossl_asn1_decode0(unsigned char **pp, long length, long *offset, int depth,
     if(j & V_ASN1_CONSTRUCTED) {
 	*pp += hlen;
 	off += hlen;
-	asn1data = int_ossl_asn1_decode0_cons(pp, length, len, &off, depth, yield, j, tag, tag_class, &inner_read);
+	asn1data = int_ossl_asn1_decode0_cons(pp, length - hlen, len, &off, depth, yield, j, tag, tag_class, &inner_read);
 	inner_read += hlen;
     }
     else {
diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb
index fd2118d8..109fd95f 100644
--- a/test/openssl/test_asn1.rb
+++ b/test/openssl/test_asn1.rb
@@ -596,6 +596,29 @@ def test_recursive_octet_string_parse
     assert_equal(false, asn1.value[3].infinite_length)
   end
 
+  def test_decode_constructed_overread
+    test = %w{ 31 06 31 02 30 02 05 00 }
+    #                          ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 2, ret.size
+    assert_equal 17, ret[0][6]
+    assert_equal 17, ret[1][6]
+
+    test = %w{ 31 80 30 03 00 00 }
+    #                    ^ <- invalid
+    raw = [test.join].pack("H*")
+    ret = []
+    assert_raise(OpenSSL::ASN1::ASN1Error) {
+      OpenSSL::ASN1.traverse(raw) { |x| ret << x }
+    }
+    assert_equal 1, ret.size
+    assert_equal 17, ret[0][6]
+  end
+
   private
 
   def assert_universal(tag, asn1)
-- 
2.15.0.rc1

From bb3d557c0053285831024ed74442ed0539654c92 Mon Sep 17 00:00:00 2001
From: Usaku NAKAMURA <u...@garbagecollect.jp>
Date: Thu, 14 Sep 2017 11:16:23 +0000
Subject: [PATCH 2/5] lib/webrick/log.rb: sanitize any type of logs
 [CVE-2017-10784]

It had failed to sanitize some type of exception messages.  Reported and
patched by Yusuke Endoh (mame) at https://hackerone.com/reports/223363

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@59897 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

------------------------------------------------------------------------

Source: https://github.com/ruby/ruby/commit/6617c41292b7d1e097abb8fdb0cab9ddd83c77e7
Backport-by: Antonio Terceiro <terce...@debian.org>
Closes: #875931
---
 lib/webrick/httpstatus.rb     |  4 ----
 lib/webrick/log.rb            |  4 ++--
 test/webrick/test_httpauth.rb | 36 ++++++++++++++++++++++++++++++++++++
 3 files changed, 38 insertions(+), 6 deletions(-)

diff --git a/lib/webrick/httpstatus.rb b/lib/webrick/httpstatus.rb
index 8664da26..ff9f1820 100644
--- a/lib/webrick/httpstatus.rb
+++ b/lib/webrick/httpstatus.rb
@@ -23,10 +23,6 @@ module HTTPStatus
     ##
     # Root of the HTTP status class hierarchy
     class Status < StandardError
-      def initialize(*args) # :nodoc:
-        args[0] = AccessLog.escape(args[0]) unless args.empty?
-        super(*args)
-      end
       class << self
         attr_reader :code, :reason_phrase # :nodoc:
       end
diff --git a/lib/webrick/log.rb b/lib/webrick/log.rb
index 7542d8f7..41e907cd 100644
--- a/lib/webrick/log.rb
+++ b/lib/webrick/log.rb
@@ -118,10 +118,10 @@ def debug?; @level >= DEBUG; end
     # * Otherwise it will return +arg+.inspect.
     def format(arg)
       if arg.is_a?(Exception)
-        "#{arg.class}: #{arg.message}\n\t" <<
+        "#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" <<
         arg.backtrace.join("\n\t") << "\n"
       elsif arg.respond_to?(:to_str)
-        arg.to_str
+        AccessLog.escape(arg.to_str)
       else
         arg.inspect
       end
diff --git a/test/webrick/test_httpauth.rb b/test/webrick/test_httpauth.rb
index 4376b918..5560740b 100644
--- a/test/webrick/test_httpauth.rb
+++ b/test/webrick/test_httpauth.rb
@@ -98,6 +98,42 @@ def test_basic_auth3
     }
   end
 
+  def test_bad_username_with_control_characters
+    log_tester = lambda {|log, access_log|
+      assert_equal(2, log.length)
+      assert_match(/ERROR Basic WEBrick's realm: foo\\ebar: the user is not allowed./, log[0])
+      assert_match(/ERROR WEBrick::HTTPStatus::Unauthorized/, log[1])
+    }
+    TestWEBrick.start_httpserver({}, log_tester) {|server, addr, port, log|
+      realm = "WEBrick's realm"
+      path = "/basic_auth"
+
+      Tempfile.create("test_webrick_auth") {|tmpfile|
+        tmpfile.close
+        tmp_pass = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
+        tmp_pass.set_passwd(realm, "webrick", "supersecretpassword")
+        tmp_pass.set_passwd(realm, "foo", "supersecretpassword")
+        tmp_pass.flush
+
+        htpasswd = WEBrick::HTTPAuth::Htpasswd.new(tmpfile.path)
+        users = []
+        htpasswd.each{|user, pass| users << user }
+        server.mount_proc(path){|req, res|
+          auth = WEBrick::HTTPAuth::BasicAuth.new(
+            :Realm => realm, :UserDB => htpasswd,
+            :Logger => server.logger
+          )
+          auth.authenticate(req, res)
+          res.body = "hoge"
+        }
+        http = Net::HTTP.new(addr, port)
+        g = Net::HTTP::Get.new(path)
+        g.basic_auth("foo\ebar", "passwd")
+        http.request(g){|res| assert_not_equal("hoge", res.body, log.call) }
+      }
+    }
+  end
+
   DIGESTRES_ = /
     ([a-zA-Z\-]+)
       [ \t]*(?:\r\n[ \t]*)*
-- 
2.15.0.rc1

From a5837e174094f8b7bbcdd41910fef9202a42a836 Mon Sep 17 00:00:00 2001
From: Usaku NAKAMURA <u...@garbagecollect.jp>
Date: Fri, 30 Jun 2017 10:47:34 +0000
Subject: [PATCH 3/5] fix Buffer underrun vulnerability in Kernel.sprintf
 [CVE-2017-0898]

	merge revision(s) 58453,58454: [Backport #13499]

	Fix space flag when Inf/NaN and width==3

	* sprintf.c (rb_str_format): while `"% 2f"` and `"% 4f"` result in
	  `" Inf"` and `" Inf"` respectively, `"% 3f"` results in
	  `"Inf"` (no space).
	Refactor "%f" % Inf/NaN

	* sprintf.c (rb_str_format): as for non-finite float, calculate
	  the exact needed size with the space flag.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_3@59218 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

------------------------------------------------------------------------

Source: https://bugs.ruby-lang.org/issues/13499
Backport-by: Antonio Terceiro
Closes: #875936
---
 sprintf.c                 | 37 ++++++++++++++++-----------------
 test/ruby/test_sprintf.rb | 52 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 20 deletions(-)

diff --git a/sprintf.c b/sprintf.c
index b022c5de..a9bbc8fb 100644
--- a/sprintf.c
+++ b/sprintf.c
@@ -1147,6 +1147,8 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
 		fval = RFLOAT_VALUE(rb_Float(val));
 		if (isnan(fval) || isinf(fval)) {
 		    const char *expr;
+		    int elen;
+		    char sign = '\0';
 
 		    if (isnan(fval)) {
 			expr = "NaN";
@@ -1155,33 +1157,28 @@ rb_str_format(int argc, const VALUE *argv, VALUE fmt)
 			expr = "Inf";
 		    }
 		    need = (int)strlen(expr);
-		    if ((!isnan(fval) && fval < 0.0) || (flags & FPLUS))
-			need++;
+		    elen = need;
+		    i = 0;
+		    if (!isnan(fval) && fval < 0.0)
+			sign = '-';
+		    else if (flags & (FPLUS|FSPACE))
+			sign = (flags & FPLUS) ? '+' : ' ';
+		    if (sign)
+			++need;
 		    if ((flags & FWIDTH) && need < width)
 			need = width;
 
-		    CHECK(need + 1);
-		    snprintf(&buf[blen], need + 1, "%*s", need, "");
+		    FILL(' ', need);
 		    if (flags & FMINUS) {
-			if (!isnan(fval) && fval < 0.0)
-			    buf[blen++] = '-';
-			else if (flags & FPLUS)
-			    buf[blen++] = '+';
-			else if (flags & FSPACE)
-			    blen++;
-			memcpy(&buf[blen], expr, strlen(expr));
+			if (sign)
+			    buf[blen - need--] = sign;
+			memcpy(&buf[blen - need], expr, elen);
 		    }
 		    else {
-			if (!isnan(fval) && fval < 0.0)
-			    buf[blen + need - strlen(expr) - 1] = '-';
-			else if (flags & FPLUS)
-			    buf[blen + need - strlen(expr) - 1] = '+';
-			else if ((flags & FSPACE) && need > width)
-			    blen++;
-			memcpy(&buf[blen + need - strlen(expr)], expr,
-			       strlen(expr));
+			if (sign)
+			    buf[blen - elen - 1] = sign;
+			memcpy(&buf[blen - elen], expr, elen);
 		    }
-		    blen += strlen(&buf[blen]);
 		    break;
 		}
 
diff --git a/test/ruby/test_sprintf.rb b/test/ruby/test_sprintf.rb
index 8aa0cbfe..618edbe2 100644
--- a/test/ruby/test_sprintf.rb
+++ b/test/ruby/test_sprintf.rb
@@ -84,6 +84,18 @@ def test_nan
     assert_equal("NaN", sprintf("%-f", nan))
     assert_equal("+NaN", sprintf("%+f", nan))
 
+    assert_equal("NaN", sprintf("%3f", nan))
+    assert_equal("NaN", sprintf("%-3f", nan))
+    assert_equal("+NaN", sprintf("%+3f", nan))
+
+    assert_equal(" NaN", sprintf("% 3f", nan))
+    assert_equal(" NaN", sprintf("%- 3f", nan))
+    assert_equal("+NaN", sprintf("%+ 3f", nan))
+
+    assert_equal(" NaN", sprintf("% 03f", nan))
+    assert_equal(" NaN", sprintf("%- 03f", nan))
+    assert_equal("+NaN", sprintf("%+ 03f", nan))
+
     assert_equal("     NaN", sprintf("%8f", nan))
     assert_equal("NaN     ", sprintf("%-8f", nan))
     assert_equal("    +NaN", sprintf("%+8f", nan))
@@ -107,6 +119,26 @@ def test_inf
     assert_equal("Inf", sprintf("%-f", inf))
     assert_equal("+Inf", sprintf("%+f", inf))
 
+    assert_equal(" Inf", sprintf("% f", inf))
+    assert_equal(" Inf", sprintf("%- f", inf))
+    assert_equal("+Inf", sprintf("%+ f", inf))
+
+    assert_equal(" Inf", sprintf("% 0f", inf))
+    assert_equal(" Inf", sprintf("%- 0f", inf))
+    assert_equal("+Inf", sprintf("%+ 0f", inf))
+
+    assert_equal("Inf", sprintf("%3f", inf))
+    assert_equal("Inf", sprintf("%-3f", inf))
+    assert_equal("+Inf", sprintf("%+3f", inf))
+
+    assert_equal(" Inf", sprintf("% 3f", inf))
+    assert_equal(" Inf", sprintf("%- 3f", inf))
+    assert_equal("+Inf", sprintf("%+ 3f", inf))
+
+    assert_equal(" Inf", sprintf("% 03f", inf))
+    assert_equal(" Inf", sprintf("%- 03f", inf))
+    assert_equal("+Inf", sprintf("%+ 03f", inf))
+
     assert_equal("     Inf", sprintf("%8f", inf))
     assert_equal("Inf     ", sprintf("%-8f", inf))
     assert_equal("    +Inf", sprintf("%+8f", inf))
@@ -127,6 +159,26 @@ def test_inf
     assert_equal("-Inf", sprintf("%-f", -inf))
     assert_equal("-Inf", sprintf("%+f", -inf))
 
+    assert_equal("-Inf", sprintf("% f", -inf))
+    assert_equal("-Inf", sprintf("%- f", -inf))
+    assert_equal("-Inf", sprintf("%+ f", -inf))
+
+    assert_equal("-Inf", sprintf("% 0f", -inf))
+    assert_equal("-Inf", sprintf("%- 0f", -inf))
+    assert_equal("-Inf", sprintf("%+ 0f", -inf))
+
+    assert_equal("-Inf", sprintf("%4f", -inf))
+    assert_equal("-Inf", sprintf("%-4f", -inf))
+    assert_equal("-Inf", sprintf("%+4f", -inf))
+
+    assert_equal("-Inf", sprintf("% 4f", -inf))
+    assert_equal("-Inf", sprintf("%- 4f", -inf))
+    assert_equal("-Inf", sprintf("%+ 4f", -inf))
+
+    assert_equal("-Inf", sprintf("% 04f", -inf))
+    assert_equal("-Inf", sprintf("%- 04f", -inf))
+    assert_equal("-Inf", sprintf("%+ 04f", -inf))
+
     assert_equal("    -Inf", sprintf("%8f", -inf))
     assert_equal("-Inf    ", sprintf("%-8f", -inf))
     assert_equal("    -Inf", sprintf("%+8f", -inf))
-- 
2.15.0.rc1

From b9531c7fbffa1dd343288992425ca439c60b2fb8 Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patter...@gmail.com>
Date: Fri, 6 Oct 2017 11:11:40 -0700
Subject: [PATCH 4/5] Whitelist classes and symbols that are in Gem spec YAML
 [CVE-2017-0903]

This patch adds a method for loading YAML specs from a gem and
whitelists classes and symbols that are allowed in the spec.  Then it
changes calls to YAML.load to call the whitelisted "safe" loader
instead.

------------------------------------------------------------------------

Backport-by: Antonio Terceiro <terce...@debian.org.
Source: https://github.com/rubygems/rubygems/commit/510b1638ac9bba3ceb7a5d73135dafff9e5bab49
Closes: #879231
---
 lib/rubygems.rb               |  3 ++-
 lib/rubygems/config_file.rb   |  2 +-
 lib/rubygems/package.rb       |  2 +-
 lib/rubygems/package/old.rb   |  2 +-
 lib/rubygems/safe_yaml.rb     | 48 +++++++++++++++++++++++++++++++++++++++++++
 lib/rubygems/specification.rb |  2 +-
 6 files changed, 54 insertions(+), 5 deletions(-)
 create mode 100644 lib/rubygems/safe_yaml.rb

diff --git a/lib/rubygems.rb b/lib/rubygems.rb
index 9c0219ce..3528a15f 100644
--- a/lib/rubygems.rb
+++ b/lib/rubygems.rb
@@ -602,7 +602,7 @@ def self.load_yaml
 
     unless test_syck
       begin
-        gem 'psych', '>= 1.2.1'
+        gem 'psych', '>= 2.0.0'
       rescue Gem::LoadError
         # It's OK if the user does not have the psych gem installed.  We will
         # attempt to require the stdlib version
@@ -626,6 +626,7 @@ def self.load_yaml
     end
 
     require 'yaml'
+    require 'rubygems/safe_yaml'
 
     # If we're supposed to be using syck, then we may have to force
     # activate it via the YAML::ENGINE API.
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index de90cbfd..2bcd830f 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -332,7 +332,7 @@ def load_file(filename)
     return {} unless filename and File.exist? filename
 
     begin
-      content = YAML.load(File.read(filename))
+      content = Gem::SafeYAML.load(File.read(filename))
       unless content.kind_of? Hash
         warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
         return {}
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index 0d9adba2..ab49ea2d 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -466,7 +466,7 @@ def read_checksums gem
 
     @checksums = gem.seek 'checksums.yaml.gz' do |entry|
       Zlib::GzipReader.wrap entry do |gz_io|
-        YAML.load gz_io.read
+        Gem::SafeYAML.safe_load gz_io.read
       end
     end
   end
diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb
index 5e722baa..071f7141 100644
--- a/lib/rubygems/package/old.rb
+++ b/lib/rubygems/package/old.rb
@@ -101,7 +101,7 @@ def file_list io # :nodoc:
       header << line
     end
 
-    YAML.load header
+    Gem::SafeYAML.safe_load header
   end
 
   ##
diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb
new file mode 100644
index 00000000..b98cfaa5
--- /dev/null
+++ b/lib/rubygems/safe_yaml.rb
@@ -0,0 +1,48 @@
+module Gem
+
+  ###
+  # This module is used for safely loading YAML specs from a gem.  The
+  # `safe_load` method defined on this module is specifically designed for
+  # loading Gem specifications.  For loading other YAML safely, please see
+  # Psych.safe_load
+
+  module SafeYAML
+    WHITELISTED_CLASSES = %w(
+      Symbol
+      Time
+      Date
+      Gem::Dependency
+      Gem::Platform
+      Gem::Requirement
+      Gem::Specification
+      Gem::Version
+      Gem::Version::Requirement
+      YAML::Syck::DefaultKey
+      Syck::DefaultKey
+    )
+
+    WHITELISTED_SYMBOLS = %w(
+      development
+      runtime
+    )
+
+    if ::YAML.respond_to? :safe_load
+      def self.safe_load input
+        ::YAML.safe_load(input, WHITELISTED_CLASSES, WHITELISTED_SYMBOLS, true)
+      end
+
+      def self.load input
+        ::YAML.safe_load(input, [::Symbol])
+      end
+    else
+      warn "YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0)."
+      def self.safe_load input, *args
+        ::YAML.load input
+      end
+
+      def self.load input
+        ::YAML.load input
+      end
+    end
+  end
+end
diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb
index 2519b96b..748b3367 100644
--- a/lib/rubygems/specification.rb
+++ b/lib/rubygems/specification.rb
@@ -1101,7 +1101,7 @@ def self.from_yaml(input)
     Gem.load_yaml
 
     input = normalize_yaml_input input
-    spec = YAML.load input
+    spec = Gem::SafeYAML.safe_load input
 
     if spec && spec.class == FalseClass then
       raise Gem::EndOfYAMLException
-- 
2.15.0.rc1

From 50d860d0bd7834e95214a2b1ff5b8e0ede7910a1 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalper...@yhbt.net>
Date: Sat, 30 Sep 2017 21:50:42 +0000
Subject: [PATCH 5/5] thread_pthread.c: do not wakeup inside child processes

* thread_pthread.c (rb_thread_wakeup_timer_thread): check
  ownership before incrementing
  (rb_thread_wakeup_timer_thread_low): ditto
  [Bug #13794] [ruby-core:83064]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60079 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

------------------------------------------------------------------------

Source: https://bugs.ruby-lang.org/issues/13794
Backport-by: Antonio Terceiro <terce...@debian.org>
Closes: #876377
---
 thread_pthread.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/thread_pthread.c b/thread_pthread.c
index 4aa2d620..023e96e6 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1313,17 +1313,21 @@ void
 rb_thread_wakeup_timer_thread(void)
 {
     /* must be safe inside sighandler, so no mutex */
-    ATOMIC_INC(timer_thread_pipe.writing);
-    rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.normal[1]);
-    ATOMIC_DEC(timer_thread_pipe.writing);
+    if (timer_thread_pipe.owner_process == getpid()) {
+	ATOMIC_INC(timer_thread_pipe.writing);
+	rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.normal[1]);
+	ATOMIC_DEC(timer_thread_pipe.writing);
+    }
 }
 
 static void
 rb_thread_wakeup_timer_thread_low(void)
 {
-    ATOMIC_INC(timer_thread_pipe.writing);
-    rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.low[1]);
-    ATOMIC_DEC(timer_thread_pipe.writing);
+    if (timer_thread_pipe.owner_process == getpid()) {
+	ATOMIC_INC(timer_thread_pipe.writing);
+	rb_thread_wakeup_timer_thread_fd(&timer_thread_pipe.low[1]);
+	ATOMIC_DEC(timer_thread_pipe.writing);
+    }
 }
 
 /* VM-dependent API is not available for this function */
-- 
2.15.0.rc1

Attachment: signature.asc
Description: PGP signature

Reply via email to