dalli 5.0.6 → 5.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54f211dbcca14abc18e7e9ffc7e9fe5c992c1e95afab2b6a64e46b6ec32efa8a
4
- data.tar.gz: f655e7c63cc622876ae47400afd59139eea3b8e19f6585af223fafccad94e659
3
+ metadata.gz: 10a8b21fed4728b6f98929e983262dc1fc32c59711fce78a84b0df82b0348ca1
4
+ data.tar.gz: 0253d48293d3b482e8362830acd9323873302e202cb230bca3fc1b1e09b1b457
5
5
  SHA512:
6
- metadata.gz: de1470d516e61e2b970c0cfaa0de3afe572124081b300ce643405813c3ba232be08da32fbc3748fbc86b39e2aaea0d6e5803d34cd9722f50c3fc47b325970c67
7
- data.tar.gz: 1835dc76b3cfebe33a4a8eca38d37dcfa8af84495e0d5af523402ddd4ffa1e3a2a8c808ce4c6e2605325f8d6eded6faa4a574022680528016f091e330e987a3e
6
+ metadata.gz: bda1d416cf1f88514d0181ed2e7f7b53e1b935fb07901398cb91183f63e42d0ab7c9e54c177264cf77fc793ead7b8183d79cf3c335d8dee11aae5049a6f0fff9
7
+ data.tar.gz: d81bec09db2c23cdc95204b0bec5c491fbcb60e12823245b9d9d338502c98b2e6af5f6e58a12b11c081889ea5d63cb079b6e8ef2fb869db147f67219db86ac55
data/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ Dalli Changelog
4
4
  Unreleased
5
5
  ==========
6
6
 
7
+ 5.0.7
8
+ ==========
9
+
10
+ Security:
11
+
12
+ - Fix memcached command injection through numeric arguments (GHSA-6wmv-xq9m-fmp7)
13
+ - The `default` argument of `incr`/`decr`, and `fetch_with_lock`'s `lock_ttl` and `recache_threshold`, were written into the meta protocol command without conversion, so a String containing CRLF injected additional memcached commands (e.g. `set`, `flush_all`) on the connection
14
+ - These arguments must now be Integers, or Strings of decimal digits; anything else raises `ArgumentError` before a request is sent
15
+ - As defense in depth, `RequestFormatter` now converts every numeric flag it writes (`D`, `J`, `N`, `R`, `T`) to an Integer
16
+ - Thanks to oss-security-shop for the report
17
+
7
18
  5.0.6
8
19
  ==========
9
20
 
data/lib/dalli/client.rb CHANGED
@@ -225,6 +225,8 @@ module Dalli
225
225
  def fetch_with_lock(key, ttl: nil, lock_ttl: 30, recache_threshold: nil, req_options: nil, &block)
226
226
  raise ArgumentError, 'Block is required for fetch_with_lock' unless block_given?
227
227
 
228
+ validate_integer!(:lock_ttl, lock_ttl)
229
+ validate_integer!(:recache_threshold, recache_threshold)
228
230
  key = key.to_s
229
231
  key = @key_manager.validate_key(key)
230
232
 
@@ -400,6 +402,8 @@ module Dalli
400
402
  ##
401
403
  # Incr adds the given amount to the counter on the memcached server.
402
404
  # Amt must be a positive integer value.
405
+ # Default, if given, must be an Integer (or a String of decimal digits);
406
+ # anything else raises ArgumentError.
403
407
  #
404
408
  # If default is nil, the counter must already exist or the operation
405
409
  # will fail and will return nil. Otherwise this method will return
@@ -412,6 +416,7 @@ module Dalli
412
416
  # If the value already exists, it must have been set with raw: true
413
417
  def incr(key, amt = 1, ttl = nil, default = nil)
414
418
  check_positive!(amt)
419
+ validate_integer!(:default, default)
415
420
 
416
421
  perform(:incr, key, amt.to_i, ttl_or_default(ttl), default)
417
422
  end
@@ -419,6 +424,8 @@ module Dalli
419
424
  ##
420
425
  # Decr subtracts the given amount from the counter on the memcached server.
421
426
  # Amt must be a positive integer value.
427
+ # Default, if given, must be an Integer (or a String of decimal digits);
428
+ # anything else raises ArgumentError.
422
429
  #
423
430
  # memcached counters are unsigned and cannot hold negative values. Calling
424
431
  # decr on a counter which is 0 will just return 0.
@@ -434,6 +441,7 @@ module Dalli
434
441
  # If the value already exists, it must have been set with raw: true
435
442
  def decr(key, amt = 1, ttl = nil, default = nil)
436
443
  check_positive!(amt)
444
+ validate_integer!(:default, default)
437
445
 
438
446
  perform(:decr, key, amt.to_i, ttl_or_default(ttl), default)
439
447
  end
@@ -617,6 +625,19 @@ module Dalli
617
625
  raise ArgumentError, "Positive values only: #{amt}" if amt.negative?
618
626
  end
619
627
 
628
+ # Numeric arguments that become meta protocol flags (GHSA-6wmv-xq9m-fmp7).
629
+ # RequestFormatter converts them to Integer as the wire-level backstop;
630
+ # checking here too raises a clean ArgumentError before the request
631
+ # starts, instead of Protocol::Base#request logging it as unexpected and
632
+ # closing the connection.
633
+ def validate_integer!(name, value)
634
+ return if value.nil?
635
+
636
+ value.is_a?(String) ? Integer(value, 10) : Integer(value)
637
+ rescue ArgumentError, TypeError, FloatDomainError
638
+ raise ArgumentError, "#{name} must be an Integer, got #{value.inspect}"
639
+ end
640
+
620
641
  def cas_core(key, always_set, ttl = nil, req_options = nil)
621
642
  (value, cas) = perform(:cas, key)
622
643
  return if value.nil? && !always_set
@@ -43,10 +43,11 @@ module Dalli
43
43
  # This saves 2 bytes per request and skips parsing on response.
44
44
  cmd << (skip_flags ? ' v' : ' v f') if value
45
45
  cmd << ' c' if return_cas
46
- cmd << " T#{ttl}" if ttl
46
+ cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
47
47
  cmd << ' k q s' if quiet # Return the key in the response if quiet
48
- cmd << " N#{vivify_ttl}" if vivify_ttl # Thundering herd: vivify on miss
49
- cmd << " R#{recache_ttl}" if recache_ttl # Thundering herd: win recache if TTL below threshold
48
+ cmd << " N#{integer_flag(:vivify_ttl, vivify_ttl)}" if vivify_ttl # Thundering herd: vivify on miss
49
+ # Thundering herd: win recache if TTL below threshold
50
+ cmd << " R#{integer_flag(:recache_ttl, recache_ttl)}" if recache_ttl
50
51
  cmd << ' h' if return_hit_status # Return hit status (0 or 1)
51
52
  cmd << ' l' if return_last_access # Return seconds since last access
52
53
  cmd << ' u' if skip_lru_bump # Don't bump LRU or update access stats
@@ -74,13 +75,14 @@ module Dalli
74
75
  cmd << ' b' if base64
75
76
  cmd << " F#{bitflags}" if bitflags
76
77
  cmd << cas_string(cas)
77
- cmd << " T#{ttl}" if ttl
78
+ cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
78
79
  cmd << " M#{mode_to_token(mode)}"
79
80
  cmd << ' q' if quiet
80
81
  cmd << TERMINATOR
81
82
  end
82
83
 
83
84
  def multi_meta_set(entries, ttl: nil)
85
+ ttl = integer_flag(:ttl, ttl) if ttl
84
86
  buffer = ''.b
85
87
  entries.each do |key, pair|
86
88
  value, bitflags = pair
@@ -104,7 +106,7 @@ module Dalli
104
106
  def meta_delete(key:, cas: nil, ttl: nil, quiet: false, stale: false)
105
107
  cmd = "md #{encoded_key(key)}"
106
108
  cmd << cas_string(cas)
107
- cmd << " T#{ttl}" if ttl
109
+ cmd << " T#{integer_flag(:ttl, ttl)}" if ttl
108
110
  cmd << ' I' if stale # Mark stale instead of deleting
109
111
  cmd << ' q' if quiet
110
112
  cmd << TERMINATOR
@@ -120,10 +122,10 @@ module Dalli
120
122
 
121
123
  def meta_arithmetic(key:, delta:, initial:, incr: true, cas: nil, ttl: nil, quiet: false)
122
124
  cmd = "ma #{encoded_key(key)} v"
123
- cmd << " D#{delta}" if delta
124
- cmd << " J#{initial}" if initial
125
+ cmd << " D#{integer_flag(:delta, delta)}" if delta
126
+ cmd << " J#{integer_flag(:initial, initial)}" if initial
125
127
  # Always set a TTL if an initial value is specified
126
- cmd << " N#{ttl || 0}" if ttl || initial
128
+ cmd << " N#{ttl ? integer_flag(:ttl, ttl) : 0}" if ttl || initial
127
129
  cmd << cas_string(cas)
128
130
  cmd << ' q' if quiet
129
131
  cmd << " M#{incr ? 'I' : 'D'}"
@@ -169,6 +171,18 @@ module Dalli
169
171
 
170
172
  private
171
173
 
174
+ # Numeric flag values are written straight into the command line, so a
175
+ # value that isn't an integer -- e.g. a String carrying CRLF -- would let
176
+ # the caller inject further memcached commands (GHSA-6wmv-xq9m-fmp7).
177
+ # Converting to Integer means only digits ever reach the wire. Strings
178
+ # are parsed as base 10 so "010" means 10, as memcached would read it,
179
+ # rather than octal 8.
180
+ def integer_flag(name, value)
181
+ value.is_a?(String) ? Integer(value, 10) : Integer(value)
182
+ rescue ArgumentError, TypeError, FloatDomainError
183
+ raise ArgumentError, "#{name} must be an Integer, got #{value.inspect}"
184
+ end
185
+
172
186
  def mode_to_token(mode)
173
187
  case mode
174
188
  when :add
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '5.0.6'
4
+ VERSION = '5.0.7'
5
5
 
6
6
  MIN_SUPPORTED_MEMCACHED_VERSION = '1.6'
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.6
4
+ version: 5.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
89
  requirements: []
90
- rubygems_version: 4.0.16
90
+ rubygems_version: 4.0.21
91
91
  specification_version: 4
92
92
  summary: High performance memcached client for Ruby
93
93
  test_files: []