diff --git a/CVE-2022-4064-1.patch b/CVE-2022-4064-1.patch new file mode 100644 index 0000000..973d91b --- /dev/null +++ b/CVE-2022-4064-1.patch @@ -0,0 +1,74 @@ +diff --git a/lib/dalli/protocol/meta.rb b/lib/dalli/protocol/meta.rb +index 4d6c662d..b2e66c37 100644 +--- a/lib/dalli/protocol/meta.rb ++++ b/lib/dalli/protocol/meta.rb +@@ -44,6 +44,7 @@ def gat(key, ttl, options = nil) + end + + def touch(key, ttl) ++ ttl = TtlSanitizer.sanitize(ttl) + encoded_key, base64 = KeyRegularizer.encode(key) + req = RequestFormatter.meta_get(key: encoded_key, ttl: ttl, value: false, base64: base64) + write(req) +diff --git a/lib/dalli/protocol/meta/request_formatter.rb b/lib/dalli/protocol/meta/request_formatter.rb +index b36a1219..7e485fea 100644 +--- a/lib/dalli/protocol/meta/request_formatter.rb ++++ b/lib/dalli/protocol/meta/request_formatter.rb +@@ -31,7 +31,7 @@ def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, b + cmd << ' c' unless %i[append prepend].include?(mode) + cmd << ' b' if base64 + cmd << " F#{bitflags}" if bitflags +- cmd << " C#{cas}" if cas && !cas.zero? ++ cmd << cas_string(cas) + cmd << " T#{ttl}" if ttl + cmd << " M#{mode_to_token(mode)}" + cmd << ' q' if quiet +@@ -43,7 +43,7 @@ def self.meta_set(key:, value:, bitflags: nil, cas: nil, ttl: nil, mode: :set, b + def self.meta_delete(key:, cas: nil, ttl: nil, base64: false, quiet: false) + cmd = "md #{key}" + cmd << ' b' if base64 +- cmd << " C#{cas}" if cas && !cas.zero? ++ cmd << cas_string(cas) + cmd << " T#{ttl}" if ttl + cmd << ' q' if quiet + cmd + TERMINATOR +@@ -54,8 +54,9 @@ def self.meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, + cmd << ' b' if base64 + cmd << " D#{delta}" if delta + cmd << " J#{initial}" if initial +- cmd << " C#{cas}" if cas && !cas.zero? +- cmd << " N#{ttl}" if ttl ++ # Always set a TTL if an initial value is specified ++ cmd << " N#{ttl || 0}" if ttl || initial ++ cmd << cas_string(cas) + cmd << ' q' if quiet + cmd << " M#{incr ? 'I' : 'D'}" + cmd + TERMINATOR +@@ -75,7 +76,7 @@ def self.version + + def self.flush(delay: nil, quiet: false) + cmd = +'flush_all' +- cmd << " #{delay}" if delay ++ cmd << " #{parse_to_64_bit_int(delay, 0)}" if delay + cmd << ' noreply' if quiet + cmd + TERMINATOR + end +@@ -102,6 +103,18 @@ def self.mode_to_token(mode) + end + end + # rubocop:enable Metrics/MethodLength ++ ++ def self.cas_string(cas) ++ cas = parse_to_64_bit_int(cas, nil) ++ cas.nil? || cas.zero? ? '' : " C#{cas}" ++ end ++ ++ def self.parse_to_64_bit_int(val, default) ++ val.nil? ? nil : Integer(val) ++ rescue ArgumentError ++ # Sanitize to default if it isn't parsable as an integer ++ default ++ end + end + end + end diff --git a/CVE-2022-4064-2.patch b/CVE-2022-4064-2.patch new file mode 100644 index 0000000..9549aa6 --- /dev/null +++ b/CVE-2022-4064-2.patch @@ -0,0 +1,228 @@ +diff --git a/test/integration/test_network.rb b/test/integration/test_network.rb +index e0ea862c..bf20baae 100644 +--- a/test/integration/test_network.rb ++++ b/test/integration/test_network.rb +@@ -7,8 +7,8 @@ + describe "using the #{p} protocol" do + describe 'assuming a bad network' do + it 'handle no server available' do ++ dc = Dalli::Client.new 'localhost:19333' + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new 'localhost:19333' + dc.get 'foo' + end + end +@@ -16,8 +16,8 @@ + describe 'with a fake server' do + it 'handle connection reset' do + memcached_mock(->(sock) { sock.close }) do ++ dc = Dalli::Client.new('localhost:19123') + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new('localhost:19123') + dc.get('abc') + end + end +@@ -26,8 +26,8 @@ + it 'handle connection reset with unix socket' do + socket_path = MemcachedMock::UNIX_SOCKET_PATH + memcached_mock(->(sock) { sock.close }, :start_unix, socket_path) do ++ dc = Dalli::Client.new(socket_path) + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new(socket_path) + dc.get('abc') + end + end +@@ -35,8 +35,8 @@ + + it 'handle malformed response' do + memcached_mock(->(sock) { sock.write('123') }) do ++ dc = Dalli::Client.new('localhost:19123') + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new('localhost:19123') + dc.get('abc') + end + end +@@ -47,8 +47,8 @@ + sleep(0.6) + sock.close + }, :delayed_start) do ++ dc = Dalli::Client.new('localhost:19123') + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new('localhost:19123') + dc.get('abc') + end + end +@@ -59,8 +59,8 @@ + sleep(0.6) + sock.write('giraffe') + }) do ++ dc = Dalli::Client.new('localhost:19123') + assert_raises Dalli::RingError, message: 'No server available' do +- dc = Dalli::Client.new('localhost:19123') + dc.get('abc') + end + end +diff --git a/test/protocol/meta/test_request_formatter.rb b/test/protocol/meta/test_request_formatter.rb +index 1eef499b..755e7305 100644 +--- a/test/protocol/meta/test_request_formatter.rb ++++ b/test/protocol/meta/test_request_formatter.rb +@@ -77,11 +77,28 @@ + Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, cas: cas) + end + ++ it 'excludes CAS if set to 0' do ++ assert_equal "ms #{key} #{val.bytesize} c F#{bitflags} MS\r\n#{val}\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, cas: 0) ++ end ++ ++ it 'excludes non-numeric CAS values' do ++ assert_equal "ms #{key} #{val.bytesize} c F#{bitflags} MS\r\n#{val}\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, ++ cas: "\nset importantkey 1 1000 8\ninjected") ++ end ++ + it 'sets the quiet mode if configured' do + assert_equal "ms #{key} #{val.bytesize} c F#{bitflags} MS q\r\n#{val}\r\n", + Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, + quiet: true) + end ++ ++ it 'sets the base64 mode if configured' do ++ assert_equal "ms #{key} #{val.bytesize} c b F#{bitflags} MS\r\n#{val}\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_set(key: key, value: val, bitflags: bitflags, ++ base64: true) ++ end + end + + describe 'meta_delete' do +@@ -93,7 +110,7 @@ + Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key) + end + +- it 'returns incorporates CAS when passed cas' do ++ it 'incorporates CAS when passed cas' do + assert_equal "md #{key} C#{cas}\r\n", + Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, cas: cas) + end +@@ -102,6 +119,87 @@ + assert_equal "md #{key} q\r\n", + Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, quiet: true) + end ++ ++ it 'excludes CAS when set to 0' do ++ assert_equal "md #{key}\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, cas: 0) ++ end ++ ++ it 'excludes non-numeric CAS values' do ++ assert_equal "md #{key}\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, ++ cas: "\nset importantkey 1 1000 8\ninjected") ++ end ++ ++ it 'sets the base64 mode if configured' do ++ assert_equal "md #{key} b\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_delete(key: key, base64: true) ++ end ++ end ++ ++ describe 'meta_arithmetic' do ++ let(:key) { SecureRandom.hex(4) } ++ let(:delta) { rand(500..999) } ++ let(:initial) { rand(500..999) } ++ let(:cas) { rand(500..999) } ++ let(:ttl) { rand(500..999) } ++ ++ it 'returns the expected string with the default N flag when passed non-nil key, delta, and initial' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N0 MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial) ++ end ++ ++ it 'excludes the J and N flags when initial is nil and ttl is not set' do ++ assert_equal "ma #{key} v D#{delta} MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: nil) ++ end ++ ++ it 'omits the D flag is delta is nil' do ++ assert_equal "ma #{key} v J#{initial} N0 MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: nil, initial: initial) ++ end ++ ++ it 'uses ttl for the N flag when ttl passed explicitly along with an initial value' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N#{ttl} MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ ttl: ttl) ++ end ++ ++ it 'incorporates CAS when passed cas' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N0 C#{cas} MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ cas: cas) ++ end ++ ++ it 'excludes CAS when CAS is set to 0' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N0 MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ cas: 0) ++ end ++ ++ it 'includes the N flag when ttl passed explicitly with a nil initial value' do ++ assert_equal "ma #{key} v D#{delta} N#{ttl} MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: nil, ++ ttl: ttl) ++ end ++ ++ it 'swaps from MI to MD when the incr value is explicitly false' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N0 MD\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ incr: false) ++ end ++ ++ it 'includes the quiet flag when specified' do ++ assert_equal "ma #{key} v D#{delta} J#{initial} N0 q MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ quiet: true) ++ end ++ ++ it 'sets the base64 mode if configured' do ++ assert_equal "ma #{key} v b D#{delta} J#{initial} N0 MI\r\n", ++ Dalli::Protocol::Meta::RequestFormatter.meta_arithmetic(key: key, delta: delta, initial: initial, ++ base64: true) ++ end + end + + describe 'meta_noop' do +@@ -130,6 +228,11 @@ + assert_equal "flush_all #{delay}\r\n", Dalli::Protocol::Meta::RequestFormatter.flush(delay: delay) + end + ++ it 'santizes the delay argument' do ++ delay = "\nset importantkey 1 1000 8\ninjected" ++ assert_equal "flush_all 0\r\n", Dalli::Protocol::Meta::RequestFormatter.flush(delay: delay) ++ end ++ + it 'adds noreply with a delay and quiet argument' do + delay = rand(1000..1999) + assert_equal "flush_all #{delay} noreply\r\n", +diff --git a/test/test_rack_session.rb b/test/test_rack_session.rb +index e56a098e..93860345 100644 +--- a/test/test_rack_session.rb ++++ b/test/test_rack_session.rb +@@ -54,15 +54,15 @@ + let(:incrementor) { Rack::Lint.new(incrementor_proc) } + + it 'faults on no connection' do ++ rsd = Rack::Session::Dalli.new(incrementor, memcache_server: 'nosuchserver') + assert_raises Dalli::RingError do +- rsd = Rack::Session::Dalli.new(incrementor, memcache_server: 'nosuchserver') + rsd.data.with { |c| c.set('ping', '') } + end + end + + it 'connects to existing server' do ++ rsd = Rack::Session::Dalli.new(incrementor, namespace: 'test:rack:session') + assert_silent do +- rsd = Rack::Session::Dalli.new(incrementor, namespace: 'test:rack:session') + rsd.data.with { |c| c.set('ping', '') } + end + end diff --git a/rubygem-dalli.spec b/rubygem-dalli.spec index 98def41..d95e59c 100644 --- a/rubygem-dalli.spec +++ b/rubygem-dalli.spec @@ -2,7 +2,7 @@ %global enable_test 0 Name: rubygem-%{gem_name} Version: 3.2.2 -Release: 1 +Release: 2 Summary: High performance memcached client for Ruby License: MIT URL: https://github.com/petergoldstein/dalli @@ -10,6 +10,8 @@ Source0: https://rubygems.org/gems/%{gem_name}-%{version}.gem Source1: %{gem_name}-%{version}-tests.tgz Patch0: rubygem-dalli-2.7.6-Use-assert_nil-in-tests.patch Patch1: rubygem-dalli-2.7.6-Fix-memcached-1.5.4-compatibility.patch +Patch2: CVE-2022-4064-1.patch +Patch3: CVE-2022-4064-2.patch BuildRequires: ruby(release) rubygems-devel %if 0%{enable_test} > 0 BuildRequires: memcached rubygem(minitest) rubygem(mocha) rubygem(rails) @@ -29,6 +31,7 @@ Documentation for %{name} %prep %setup -q -n %{gem_name}-%{version} +%patch2 -p1 %build gem build ../%{gem_name}-%{version}.gemspec @@ -45,6 +48,7 @@ pushd .%{gem_instdir} tar xzvf %{SOURCE1} cat %{PATCH0} | patch -p1 cat %{PATCH1} | patch -p1 +cat %{PATCH3} | patch -p1 sed -i '/bundler/ s/^/#/' test/helper.rb ruby -Ilib:test -e "Dir.glob('./test/test_*.rb').sort.each{ |x| require x }" popd @@ -64,6 +68,9 @@ popd %{gem_instdir}/Gemfile %changelog +* Thu Dec 15 2022 liyuxiang - 3.2.2-2 +- fix CVE-2022-4064 + * Tue Jun 28 2022 baizhonggui - 3.2.2-1 - update to 3.2.2