Skip to content

Conversation

@oleg-vinted
Copy link

Pulling vinted#20, see that PR for context.

Reproduction script
#!/usr/bin/env ruby
#
# Reproduce the "bytes == watermark" corner-case in memcached_purge()
# without altering the watermark value.
#
# Works with the vinted-memcached 1.8.x gem that vendors libmemcached-0.32
#
# Mechanics
# 1.  Read the current ptr->root->io_bytes_watermark and the per-server
#     ptr->io_bytes_sent counters from the C structs.
# 2.  Choose a value length L such that the first SET command
#         "set a 0 0 L noreply\r\n".bytes + L + 2  # trailing CRLF
#     will bring io_bytes_sent up to exactly io_bytes_watermark.
# 3.  Send that first SET (with the noreply flag). After it the server's
#     io_bytes_sent == io_bytes_watermark and response_count == 0.
# 4.  Send a second tiny SET (also noreply). This triggers the
#     equality-case branch inside memcached_purge(), reproducing the busy-loop
#     bug in unpatched libmemcached.

require 'memcached'

puts "Process.pid: #{Process.pid} (use `kill -9 #{Process.pid}` to stop)"

KEY1 = 'a'
KEY2 = 'b'

client = Memcached.new('127.0.0.1:11211', noreply: true, no_block: true)

lib = Memcached.const_get(:Lib)
struct = client.instance_variable_get(:@struct)
server = client.send(:server_structs).first

watermark = struct.io_bytes_watermark
bytes_sent = server.io_bytes_sent # you could assume 0 for new connections instead
expected_bytes_count = 27 # expected length of the first SET command
value_len = watermark - bytes_sent - expected_bytes_count

puts "Current bytes sent: #{bytes_sent}"
puts "Current io_bytes_watermark: #{watermark}"
puts "Chosen value length: #{value_len} (total bytes will match watermark)"

# 1st write - drives io_bytes_sent == watermark
client.set(KEY1, 'x' * value_len, 0, false, 0)

# 2nd write - triggers the equality-case path
client.set(KEY2, 'y')

# With a vulnerable libmemcached build the script will now spin at 100% CPU
puts 'If this line prints, the loop did not trigger - your libmemcached is patched.'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants