Hello community, here is the log from the commit of package rubygem-rack for openSUSE:Factory checked in at 2019-12-21 12:31:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-rack (Old) and /work/SRC/openSUSE:Factory/.rubygem-rack.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-rack" Sat Dec 21 12:31:35 2019 rev:16 rq:758120 version:2.0.8 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-rack/rubygem-rack.changes 2019-04-08 10:40:32.523298437 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-rack.new.6675/rubygem-rack.changes 2019-12-21 12:32:03.167382393 +0100 @@ -1,0 +2,6 @@ +Thu Dec 19 08:55:14 UTC 2019 - David Kang <[email protected]> + +- updated to version 2.0.8 + * CVE-2019-16782: Possible information leak / session hijack vulnerability + +------------------------------------------------------------------- Old: ---- rack-2.0.7.gem New: ---- rack-2.0.8.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-rack.spec ++++++ --- /var/tmp/diff_new_pack.EWVfKh/_old 2019-12-21 12:32:04.135382854 +0100 +++ /var/tmp/diff_new_pack.EWVfKh/_new 2019-12-21 12:32:04.139382855 +0100 @@ -1,7 +1,7 @@ # # spec file for package rubygem-rack # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,7 +12,7 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # @@ -24,7 +24,7 @@ # Name: rubygem-rack -Version: 2.0.7 +Version: 2.0.8 Release: 0 %define mod_name rack %define mod_full_name %{mod_name}-%{version} @@ -41,7 +41,7 @@ BuildRequires: %{rubygem gem2rpm} BuildRequires: ruby-macros >= 5 BuildRequires: update-alternatives -Url: https://rack.github.io/ +URL: https://rack.github.io/ Source: https://rubygems.org/gems/%{mod_full_name}.gem Source1: rubygem-rack-rpmlintrc Source2: gem2rpm.yml ++++++ rack-2.0.7.gem -> rack-2.0.8.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SPEC new/SPEC --- old/SPEC 2019-04-02 18:54:06.000000000 +0200 +++ new/SPEC 2019-12-18 19:05:05.000000000 +0100 @@ -60,8 +60,8 @@ the presence or absence of the appropriate HTTP header in the request. See - {https://tools.ietf.org/html/rfc3875#section-4.1.18 - RFC3875 section 4.1.18} for + <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18"> + RFC3875 section 4.1.18</a> for specific behavior. In addition to this, the Rack environment must include these Rack-specific variables: @@ -98,12 +98,13 @@ Additional environment specifications have approved to standardized middleware APIs. None of these are required to be implemented by the server. -<tt>rack.session</tt>:: A hash like interface for storing request session data. +<tt>rack.session</tt>:: A hash like interface for storing + request session data. The store must implement: - store(key, value) (aliased as []=); - fetch(key, default = nil) (aliased as []); - delete(key); - clear; + store(key, value) (aliased as []=); + fetch(key, default = nil) (aliased as []); + delete(key); + clear; <tt>rack.logger</tt>:: A common object interface for logging messages. The object must implement: info(message, &block) Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/session/abstract/id.rb new/lib/rack/session/abstract/id.rb --- old/lib/rack/session/abstract/id.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/lib/rack/session/abstract/id.rb 2019-12-18 19:05:05.000000000 +0100 @@ -6,11 +6,38 @@ require 'rack/request' require 'rack/response' require 'securerandom' +require 'digest/sha2' module Rack module Session + class SessionId + ID_VERSION = 2 + + attr_reader :public_id + + def initialize(public_id) + @public_id = public_id + end + + def private_id + "#{ID_VERSION}::#{hash_sid(public_id)}" + end + + alias :cookie_value :public_id + + def empty?; false; end + def to_s; raise; end + def inspect; public_id.inspect; end + + private + + def hash_sid(sid) + Digest::SHA256.hexdigest(sid) + end + end + module Abstract # SessionHash is responsible to lazily load the session from store. @@ -357,7 +384,7 @@ req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE else cookie = Hash.new - cookie[:value] = data + cookie[:value] = cookie_value(data) cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after] cookie[:expires] = Time.now + options[:max_age] if options[:max_age] set_cookie(req, res, cookie.merge!(options)) @@ -365,6 +392,10 @@ end public :commit_session + def cookie_value(data) + data + end + # Sets the cookie back to the client with session id. We skip the cookie # setting if the value didn't change (sid is the same) or expires was given. @@ -406,6 +437,40 @@ end end + class PersistedSecure < Persisted + class SecureSessionHash < SessionHash + def [](key) + if key == "session_id" + load_for_read! + id.public_id + else + super + end + end + end + + def generate_sid(*) + public_id = super + + SessionId.new(public_id) + end + + def extract_session_id(*) + public_id = super + public_id && SessionId.new(public_id) + end + + private + + def session_class + SecureSessionHash + end + + def cookie_value(data) + data.cookie_value + end + end + class ID < Persisted def self.inherited(klass) k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/session/cookie.rb new/lib/rack/session/cookie.rb --- old/lib/rack/session/cookie.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/lib/rack/session/cookie.rb 2019-12-18 19:05:05.000000000 +0100 @@ -45,7 +45,7 @@ # }) # - class Cookie < Abstract::Persisted + class Cookie < Abstract::PersistedSecure # Encode session cookies as Base64 class Base64 def encode(str) @@ -153,6 +153,15 @@ data end + class SessionId < DelegateClass(Session::SessionId) + attr_reader :cookie_value + + def initialize(session_id, cookie_value) + super(session_id) + @cookie_value = cookie_value + end + end + def write_session(req, session_id, session, options) session = session.merge("session_id" => session_id) session_data = coder.encode(session) @@ -165,7 +174,7 @@ req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.") nil else - session_data + SessionId.new(session_id, session_data) end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/session/memcache.rb new/lib/rack/session/memcache.rb --- old/lib/rack/session/memcache.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/lib/rack/session/memcache.rb 2019-12-18 19:05:05.000000000 +0100 @@ -19,7 +19,7 @@ # Note that memcache does drop data before it may be listed to expire. For # a full description of behaviour, please see memcache's documentation. - class Memcache < Abstract::ID + class Memcache < Abstract::PersistedSecure attr_reader :mutex, :pool DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \ @@ -42,15 +42,15 @@ def generate_sid loop do sid = super - break sid unless @pool.get(sid, true) + break sid unless @pool.get(sid.private_id, true) end end - def get_session(env, sid) - with_lock(env) do - unless sid and session = @pool.get(sid) + def find_session(req, sid) + with_lock(req) do + unless sid and session = get_session_with_fallback(sid) sid, session = generate_sid, {} - unless /^STORED/ =~ @pool.add(sid, session) + unless /^STORED/ =~ @pool.add(sid.private_id, session) raise "Session collision on '#{sid.inspect}'" end end @@ -58,25 +58,26 @@ end end - def set_session(env, session_id, new_session, options) + def write_session(req, session_id, new_session, options) expiry = options[:expire_after] expiry = expiry.nil? ? 0 : expiry + 1 - with_lock(env) do - @pool.set session_id, new_session, expiry + with_lock(req) do + @pool.set session_id.private_id, new_session, expiry session_id end end - def destroy_session(env, session_id, options) - with_lock(env) do - @pool.delete(session_id) + def delete_session(req, session_id, options) + with_lock(req) do + @pool.delete(session_id.public_id) + @pool.delete(session_id.private_id) generate_sid unless options[:drop] end end - def with_lock(env) - @mutex.lock if env[RACK_MULTITHREAD] + def with_lock(req) + @mutex.lock if req.multithread? yield rescue MemCache::MemCacheError, Errno::ECONNREFUSED if $VERBOSE @@ -88,6 +89,11 @@ @mutex.unlock if @mutex.locked? end + private + + def get_session_with_fallback(sid) + @pool.get(sid.private_id) || @pool.get(sid.public_id) + end end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/session/pool.rb new/lib/rack/session/pool.rb --- old/lib/rack/session/pool.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/lib/rack/session/pool.rb 2019-12-18 19:05:05.000000000 +0100 @@ -24,7 +24,7 @@ # ) # Rack::Handler::WEBrick.run sessioned - class Pool < Abstract::Persisted + class Pool < Abstract::PersistedSecure attr_reader :mutex, :pool DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false @@ -37,15 +37,15 @@ def generate_sid loop do sid = super - break sid unless @pool.key? sid + break sid unless @pool.key? sid.private_id end end def find_session(req, sid) with_lock(req) do - unless sid and session = @pool[sid] + unless sid and session = get_session_with_fallback(sid) sid, session = generate_sid, {} - @pool.store sid, session + @pool.store sid.private_id, session end [sid, session] end @@ -53,14 +53,15 @@ def write_session(req, session_id, new_session, options) with_lock(req) do - @pool.store session_id, new_session + @pool.store session_id.private_id, new_session session_id end end def delete_session(req, session_id, options) with_lock(req) do - @pool.delete(session_id) + @pool.delete(session_id.public_id) + @pool.delete(session_id.private_id) generate_sid unless options[:drop] end end @@ -71,6 +72,12 @@ ensure @mutex.unlock if @mutex.locked? end + + private + + def get_session_with_fallback(sid) + @pool[sid.private_id] || @pool[sid.public_id] + end end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack.rb new/lib/rack.rb --- old/lib/rack.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/lib/rack.rb 2019-12-18 19:05:05.000000000 +0100 @@ -18,7 +18,7 @@ VERSION.join(".") end - RELEASE = "2.0.7" + RELEASE = "2.0.8" # Return the Rack release as a dotted string. def self.release diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2019-04-02 18:54:06.000000000 +0200 +++ new/metadata 2019-12-18 19:05:05.000000000 +0100 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: rack version: !ruby/object:Gem::Version - version: 2.0.7 + version: 2.0.8 platform: ruby authors: - Leah Neukirchen autorequire: bindir: bin cert_chain: [] -date: 2019-04-02 00:00:00.000000000 Z +date: 2019-12-18 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: minitest @@ -274,60 +274,59 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubyforge_project: -rubygems_version: 2.6.13 +rubygems_version: 3.0.3 signing_key: specification_version: 4 summary: a modular Ruby webserver interface test_files: -- test/spec_auth_basic.rb -- test/spec_auth_digest.rb -- test/spec_body_proxy.rb -- test/spec_builder.rb -- test/spec_cascade.rb -- test/spec_cgi.rb -- test/spec_chunked.rb -- test/spec_common_logger.rb -- test/spec_conditional_get.rb -- test/spec_config.rb -- test/spec_content_length.rb -- test/spec_content_type.rb +- test/spec_multipart.rb - test/spec_deflater.rb -- test/spec_directory.rb +- test/spec_static.rb +- test/spec_session_cookie.rb +- test/spec_session_pool.rb - test/spec_etag.rb -- test/spec_events.rb -- test/spec_fastcgi.rb -- test/spec_file.rb +- test/spec_version.rb - test/spec_handler.rb -- test/spec_head.rb -- test/spec_lint.rb -- test/spec_lobster.rb -- test/spec_lock.rb -- test/spec_logger.rb -- test/spec_media_type.rb -- test/spec_method_override.rb +- test/spec_thin.rb +- test/spec_session_abstract_id.rb - test/spec_mime.rb -- test/spec_mock.rb -- test/spec_multipart.rb -- test/spec_null_logger.rb - test/spec_recursive.rb +- test/spec_null_logger.rb +- test/spec_media_type.rb +- test/spec_cgi.rb +- test/spec_method_override.rb +- test/spec_content_type.rb +- test/spec_session_abstract_session_hash.rb - test/spec_request.rb -- test/spec_response.rb -- test/spec_rewindable_input.rb +- test/spec_chunked.rb +- test/spec_show_exceptions.rb - test/spec_runtime.rb +- test/spec_fastcgi.rb +- test/spec_common_logger.rb +- test/spec_builder.rb +- test/spec_config.rb +- test/spec_utils.rb - test/spec_sendfile.rb +- test/spec_lobster.rb +- test/spec_lint.rb +- test/spec_conditional_get.rb +- test/spec_tempfile_reaper.rb +- test/spec_mock.rb - test/spec_server.rb -- test/spec_session_abstract_id.rb -- test/spec_session_abstract_session_hash.rb -- test/spec_session_cookie.rb -- test/spec_session_memcache.rb -- test/spec_session_pool.rb -- test/spec_show_exceptions.rb +- test/spec_directory.rb +- test/spec_webrick.rb +- test/spec_response.rb +- test/spec_file.rb - test/spec_show_status.rb -- test/spec_static.rb -- test/spec_tempfile_reaper.rb -- test/spec_thin.rb +- test/spec_body_proxy.rb +- test/spec_logger.rb +- test/spec_auth_digest.rb - test/spec_urlmap.rb -- test/spec_utils.rb -- test/spec_version.rb -- test/spec_webrick.rb +- test/spec_events.rb +- test/spec_cascade.rb +- test/spec_auth_basic.rb +- test/spec_head.rb +- test/spec_lock.rb +- test/spec_rewindable_input.rb +- test/spec_session_memcache.rb +- test/spec_content_length.rb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/spec_session_memcache.rb new/test/spec_session_memcache.rb --- old/test/spec_session_memcache.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/test/spec_session_memcache.rb 2019-12-18 19:05:05.000000000 +0100 @@ -226,15 +226,52 @@ req = Rack::MockRequest.new(pool) res0 = req.get("/") - session_id = (cookie = res0["Set-Cookie"])[session_match, 1] - ses0 = pool.pool.get(session_id, true) + session_id = Rack::Session::SessionId.new (cookie = res0["Set-Cookie"])[session_match, 1] + ses0 = pool.pool.get(session_id.private_id, true) req.get("/", "HTTP_COOKIE" => cookie) - ses1 = pool.pool.get(session_id, true) + ses1 = pool.pool.get(session_id.private_id, true) ses1.wont_equal ses0 end + it "can read the session with the legacy id" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + + res0 = req.get("/") + cookie = res0["Set-Cookie"] + session_id = Rack::Session::SessionId.new cookie[session_match, 1] + ses0 = pool.pool.get(session_id.private_id, true) + pool.pool.set(session_id.public_id, ses0, 0, true) + pool.pool.delete(session_id.private_id) + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"].must_be_nil + res1.body.must_equal '{"counter"=>2}' + pool.pool.get(session_id.private_id, true).wont_be_nil + end + + it "drops the session in the legacy id as well" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + drop = Rack::Utils::Context.new(pool, drop_session) + dreq = Rack::MockRequest.new(drop) + + res0 = req.get("/") + cookie = res0["Set-Cookie"] + session_id = Rack::Session::SessionId.new cookie[session_match, 1] + ses0 = pool.pool.get(session_id.private_id, true) + pool.pool.set(session_id.public_id, ses0, 0, true) + pool.pool.delete(session_id.private_id) + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].must_be_nil + res2.body.must_equal '{"counter"=>2}' + pool.pool.get(session_id.private_id, true).must_be_nil + pool.pool.get(session_id.public_id, true).must_be_nil + end + # anyone know how to do this better? it "cleanly merges sessions when multithreaded" do skip unless $DEBUG diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/test/spec_session_pool.rb new/test/spec_session_pool.rb --- old/test/spec_session_pool.rb 2019-04-02 18:54:06.000000000 +0200 +++ new/test/spec_session_pool.rb 2019-12-18 19:05:05.000000000 +0100 @@ -6,7 +6,7 @@ describe Rack::Session::Pool do session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key] - session_match = /#{session_key}=[0-9a-fA-F]+;/ + session_match = /#{session_key}=([0-9a-fA-F]+);/ incrementor = lambda do |env| env["rack.session"]["counter"] ||= 0 @@ -14,7 +14,7 @@ Rack::Response.new(env["rack.session"].inspect).to_a end - session_id = Rack::Lint.new(lambda do |env| + get_session_id = Rack::Lint.new(lambda do |env| Rack::Response.new(env["rack.session"].inspect).to_a end) @@ -143,6 +143,43 @@ pool.pool.size.must_equal 1 end + it "can read the session with the legacy id" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + + res0 = req.get("/") + cookie = res0["Set-Cookie"] + session_id = Rack::Session::SessionId.new cookie[session_match, 1] + ses0 = pool.pool[session_id.private_id] + pool.pool[session_id.public_id] = ses0 + pool.pool.delete(session_id.private_id) + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"].must_be_nil + res1.body.must_equal '{"counter"=>2}' + pool.pool[session_id.private_id].wont_be_nil + end + + it "drops the session in the legacy id as well" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + drop = Rack::Utils::Context.new(pool, drop_session) + dreq = Rack::MockRequest.new(drop) + + res0 = req.get("/") + cookie = res0["Set-Cookie"] + session_id = Rack::Session::SessionId.new cookie[session_match, 1] + ses0 = pool.pool[session_id.private_id] + pool.pool[session_id.public_id] = ses0 + pool.pool.delete(session_id.private_id) + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].must_be_nil + res2.body.must_equal '{"counter"=>2}' + pool.pool[session_id.private_id].must_be_nil + pool.pool[session_id.public_id].must_be_nil + end + # anyone know how to do this better? it "should merge sessions when multithreaded" do unless $DEBUG @@ -191,7 +228,7 @@ end it "does not return a cookie if cookie was not written (only read)" do - app = Rack::Session::Pool.new(session_id) + app = Rack::Session::Pool.new(get_session_id) res = Rack::MockRequest.new(app).get("/") res["Set-Cookie"].must_be_nil end
