Project

General

Profile

Actions

Bug #22388

closed

Use-after-free when an Enumerable retains its `each` block

Bug #22388: Use-after-free when an Enumerable retains its `each` block

Added by nobu (Nobuyoshi Nakada) about 10 hours ago. Updated about 9 hours ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:126878]

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:

enum = DeferredEach.new
enum.sum { |x| x * 2 }

GC.start
enum.each_block.call(1)

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

Also available in: PDF Atom