Skip to content

v5.2.1

Choose a tag to compare

@github-actions github-actions released this 05 Oct 22:19
· 21 commits to main since this release
8720c66

Security release. Fixes five vulnerabilities, released together in 5.2.1, 5.1.3, 5.0.9, 4.3.6 and 3.2.12: a pipelined get_multi returning another key's value (GHSA-p6pm-ch9v-44vx); unbounded retries and routing tokens that add meta flags (GHSA-4qp6-2jcr-596v); unbounded decompression and reply sizes (GHSA-3553-vcg5-72jw); per-request raw: true ignored on reads (GHSA-wr87-m4jw-29x5); and a forked child resending the parent's requests or ending its TLS session (GHSA-w39f-xq2m-4g8x). Upgrading is strongly recommended.

Security:

  • Fix pipelined get_multi returning one key's value for another after an error reply (GHSA-p6pm-ch9v-44vx)
    • The pipelined reply parser took any reply without a body for the end of the batch. An error reply for one key (CLIENT_ERROR, SERVER_ERROR, or EN from a proxy) ended it early, and the replies still on the connection were read as the replies to later commands, so a get could return another key's value
    • Keys are now measured in bytes as sent, after base64 encoding when the key needs it. A key under 250 characters but over 250 bytes on the wire (for example, one with many non-ASCII characters) was the easiest way to cause that error reply. Such keys are now truncated like other long keys; keys that worked before are unchanged
    • Affects the meta protocol since 3.2.0 (the default since 5.0.0); fixed in 5.2.1, 5.1.3, 5.0.9, 4.3.6 and 3.2.12
  • Reject routing tokens that add meta flags, and stop retrying an unresponsive server forever (GHSA-4qp6-2jcr-596v)
    • p_token and l_token now reject whitespace and control characters, not just CR, LF and NUL. A space let a token add meta flags to the request it was sent with: changing an item's TTL on a read, creating stub items on a miss, turning a delete into a stale tombstone, or reading a different key
    • Affects 5.1.0 and later; fixed in 5.2.1 and 5.1.3
    • A server that accepted connections but never answered (or dropped the connection on a request) was retried forever, hanging the caller, because each successful reconnect reset the failure count. Requests now fail after socket_max_failures attempts and the server is marked down, as when it can't be reached at all; it gets a full set of attempts again after down_retry_delay
    • Affects all versions; fixed in 5.2.1, 5.1.3, 5.0.9, 4.3.6 and 3.2.12
  • Limit decompressed and reply value sizes (GHSA-3553-vcg5-72jw)
    • Values flagged as compressed were inflated without limit, so a small stored value could expand to gigabytes on read, whatever the client's compress or serializer settings. The new decompressed_max_bytes option (default 128 MiB; nil disables it) makes a read that would pass it raise Dalli::UnmarshalError. Custom compressors whose decompress takes only the data keep working, without the limit
    • The value size in a reply was used to read or buffer that many bytes, so a malicious server could make the client allocate gigabytes. Sizes over 1 GiB, memcached's largest item, now raise Dalli::DalliError before reading
    • Affects all versions; fixed in 5.2.1, 5.1.3, 5.0.9, 4.3.6 and 3.2.12
  • Honor per-request raw: true on reads, and add Dalli::JSONSerializer (GHSA-wr87-m4jw-29x5)
    • get_multi, get_multi_cas, get_multi_with_metadata, get_cas and get_with_metadata ignored a per-request raw: true and deserialized the value according to its stored flags, so a caller who asked for raw bytes could still have Marshal.load run on data someone else wrote to memcached. They now return the stored bytes
    • The raw part affects get_with_metadata since 4.2.0, and the other methods since they gained per-request options in 5.1.0
    • serializer: JSON reads values with JSON.load, which on json gem versions before 3.0 creates an object of the class named in a stored json_class key when the json additions are loaded. The new Dalli::JSONSerializer reads with JSON.parse and only returns plain JSON types. The README and the Marshal security warning now recommend it; serializer: JSON itself is unchanged
    • The serializer: JSON part affects any version used with the json gem before 3.0
  • Keep a forked child from writing to or closing the parent's connection (GHSA-w39f-xq2m-4g8x)
    • A child forked while the parent had requests buffered but not yet sent, such as quiet writes inside a quiet block, sent them again when it closed the client or reconnected: quiet writes ran twice, and the parent's later replies could be read as the replies to other requests, returning one key's value for another. Dalli now buffers requests itself and a forked child discards them
    • Affects 4.2.0 and later; fixed in 5.2.1, 5.1.3, 5.0.9 and 4.3.6
    • With TLS, a forked child that closed the client (or reconnected after detecting the fork) sent a TLS close on the connection the parent was still using, ending the parent's session. A forked child now closes only its own copy of the socket
    • Affects all versions with TLS; fixed in 5.2.1, 5.1.3, 5.0.9, 4.3.6 and 3.2.12

Bug fixes:

  • An exception raised by a get_multi block (such as Timeout::Error) was treated as a network failure: swallowed, with the whole get_multi retried and keys yielded twice. It now reaches the caller

Notes:

  • Document Ruby bug 21195, a VM crash ([BUG] rb_sys_fail_path_in(io_fillbuf, ...) - errno == 0) when a socket read using IO#timeout is interrupted by a signal, in the README (#1189)
    • Affects Ruby 3.3.0–3.3.7 and 3.4.0–3.4.2; fixed in 3.3.8 and 3.4.3

Development:

  • Add property tests for request building and fuzz tests for the reply parsers
  • Add a security policy (SECURITY.md), give every workflow a least-privilege token, and pin third-party actions by commit SHA (#1190)
  • Fix flaky failover tests (#1184)
    • Their ports were inside Linux's ephemeral port range (32768-60999), so an earlier client connection could hold one as its local port. memcached then couldn't bind it on IPv4 and listened on IPv6 only, and the test's client marked that server down. They now use ports in the 26xxx range
    • The test helper now waits until memcached accepts connections, instead of sleeping a fixed 0.1s after starting it
  • Refresh apt's package index before installing libevent in CI, so a stale runner image can't break every memcached build (#1172)