Actions
Bug #22388
closedUse-after-free when an Enumerable retains its `each` block
Bug #22388:
Use-after-free when an Enumerable retains its `each` block
Description
Some Enumerable methods pass a pointer to stack-allocated state to rb_block_call. If an implementation of each retains the given block and invokes it after the Enumerable method has returned, the callback accesses invalid stack memory.
This affects Enumerable#min(n), #max(n), #min_by(n), and #max_by(n). It also affects Enumerable#sum.
The following example reproduces the problem:
class DeferredEach
include Enumerable
attr_reader :each_block
def each(&block)
@each_block = block
end
end
enum = DeferredEach.new
enum.min(2)
GC.start
enum.each_block.call(1)
The same problem exists in the other methods:
%i[min max min_by max_by].each do |method|
enum = DeferredEach.new
enum.public_send(method, 2) { |x| x }
GC.start
enum.each_block.call(1)
end
Enumerable#sum can be reproduced as follows:
Depending on the stack contents and GC timing, these examples may crash, raise an unrelated exception, or appear to work. The behavior is undefined because the retained callback refers to state whose lifetime ended when the original method returned.
Actions