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
signature.asc
Description: PGP signature