From: "nobu (Nobuyoshi Nakada) via ruby-core" Date: 2026-09-29T10:25:55+00:00 Subject: [ruby-core:126878] [Ruby Bug#22388] Use-after-free when an Enumerable retains its `each` block Issue #22388 has been reported by nobu (Nobuyoshi Nakada). ---------------------------------------- Bug #22388: Use-after-free when an Enumerable retains its `each` block https://bugs.ruby-lang.org/issues/22388 * Author: nobu (Nobuyoshi Nakada) * Status: Open * Backport: 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED ---------------------------------------- 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: ````ruby 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: ````ruby %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: ````ruby 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. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/