[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/
Issue #22388 has been updated by nobu (Nobuyoshi Nakada). [ ] `Enumerator::Lazy#flat_map` [ ] `Set#&` ---------------------------------------- Bug #22388: Use-after-free when an Enumerable retains its `each` block https://bugs.ruby-lang.org/issues/22388#change-119288 * Author: nobu (Nobuyoshi Nakada) * Status: Closed * 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/
Issue #22388 has been updated by k0kubun (Takashi Kokubun). Backport changed from 3.3: REQUIRED, 3.4: REQUIRED, 4.0: REQUIRED to 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE ruby_4_0 commit:9568b5dfce563ec3ec574d5dc8b5c81713d86825, commit:bd154f04350ac0f42c03fb1dac4036ca9cb09d3b, commit:566e6dc8c0e5893723fbfe6ef0ff9cc0b23a9013, commit:ccccb04486f17dcea7c258d40a35d94d2a531ca6. ---------------------------------------- Bug #22388: Use-after-free when an Enumerable retains its `each` block https://bugs.ruby-lang.org/issues/22388#change-119450 * Author: nobu (Nobuyoshi Nakada) * Status: Closed * Backport: 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE ---------------------------------------- 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/
Issue #22388 has been updated by nagachika (Tomoyuki Chikanaga). Backport changed from 3.3: REQUIRED, 3.4: REQUIRED, 4.0: DONE to 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ruby_3_4 commit:357795cca8084e0c8da9f24bec9aac268644d48a, commit:f7e692b6cd76092f42361dab0bed77a3fef3955f merged revision(s) commit:9267f6fba084710cff11b660e8758aec29886682, commit:3ea4bc3f79a913e03ee1b1ea3e21572ee8036095, commit:badce4d07961e8c9c6ac68f15ae7b01ba1985421. ---------------------------------------- Bug #22388: Use-after-free when an Enumerable retains its `each` block https://bugs.ruby-lang.org/issues/22388#change-119473 * Author: nobu (Nobuyoshi Nakada) * Status: Closed * Backport: 3.3: REQUIRED, 3.4: DONE, 4.0: DONE ---------------------------------------- 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/
participants (3)
-
k0kubun (Takashi Kokubun) -
nagachika (Tomoyuki Chikanaga) -
nobu (Nobuyoshi Nakada)