Bug #18001
closedAddressSanitizer finding heap-buffer-overflow in iseq_peephole_optimize
Description
Summary
Using Ruby's coverage
functionality and loading crafted Ruby will lead to memory usage errors in iseq_peephole_optimize
.
Background
We've used Valgrind in the Nokogiri test suite for many years; and since Ruby 2.7 we've been seeing an iseq_peephole_optimize
memory usage error.
Because it didn't seem related to Nokogiri's code (the error was emitted before the test suite loaded), we've been ignoring it (see, for example, the valgrind suppression file we use for Ruby 2.7).
I recently started playing with AddressSanitizer as an alternative to Valgrind, and much to my surprise saw the exact same error being raised by ASan. Both tools agree that something is wrong, and so I decided to spend some time trying to isolate and reproduce it.
Reproducing
In Nokogiri's test suite, the issue happens only when using both SimpleCov and Minitest together. With a little bit of work, I narrowed this down to a simple reproduction.
You can see these files and the instructions at https://gist.github.com/flavorjones/ad958336c7fd09c92cee3a9bd911d628, but in summary:
- Build Ruby with
CFLAGS=-fsanitize=address
and turn off leak detection - Run these two files:
#! /usr/bin/env ruby
require "coverage"
Coverage.start
STDERR.puts "before require"
require_relative "foo"
STDERR.puts "after require"
and
# foo.rb
STDERR.puts "in file"
class Foo
def bar
baz do |x|
next unless Integer === x
end
end
end
The output should be something like:
before require
=================================================================
==862472==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6160005b2590 at pc 0x558b3ec6bd8d bp 0x7fffb9eb3280 sp 0x7fffb9eb3270
READ of size 4 at 0x6160005b2590 thread T0
#0 0x558b3ec6bd8c in iseq_peephole_optimize /home/flavorjones/code/oss/ruby/compile.c:3091
#1 0x558b3ec6f6ed in iseq_optimize /home/flavorjones/code/oss/ruby/compile.c:3576
#2 0x558b3ec61c76 in iseq_setup_insn /home/flavorjones/code/oss/ruby/compile.c:1452
#3 0x558b3ec5ef9e in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:855
#4 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#5 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#6 0x558b3ec867c2 in compile_iter /home/flavorjones/code/oss/ruby/compile.c:6877
#7 0x558b3ec8e6b8 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7936
#8 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#9 0x558b3ec5e857 in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:795
#10 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#11 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#12 0x558b3ec95f42 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:8884
#13 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#14 0x558b3ec8e474 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7908
#15 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#16 0x558b3ec5e71d in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:787
#17 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#18 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#19 0x558b3ec96815 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:8949
#20 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#21 0x558b3ec8e474 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7908
#22 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#23 0x558b3ec5e963 in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:801
#24 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#25 0x558b3e85298b in rb_iseq_new_top /home/flavorjones/code/oss/ruby/iseq.c:838
#26 0x558b3e866037 in load_iseq_eval /home/flavorjones/code/oss/ruby/load.c:589
#27 0x558b3e868c8e in require_internal /home/flavorjones/code/oss/ruby/load.c:1070
#28 0x558b3e869461 in rb_require_string /home/flavorjones/code/oss/ruby/load.c:1148
#29 0x558b3e8675db in rb_f_require_relative /home/flavorjones/code/oss/ruby/load.c:857
#30 0x558b3eb819ed in ractor_safe_call_cfunc_1 /home/flavorjones/code/oss/ruby/vm_insnhelper.c:2847
#31 0x558b3eb838e5 in vm_call_cfunc_with_frame /home/flavorjones/code/oss/ruby/vm_insnhelper.c:3023
#32 0x558b3eb8cc36 in vm_sendish /home/flavorjones/code/oss/ruby/vm_insnhelper.c:4596
#33 0x558b3eb9ac52 in vm_exec_core /home/flavorjones/code/oss/ruby/insns.def:775
#34 0x558b3ebc9289 in rb_vm_exec /home/flavorjones/code/oss/ruby/vm.c:2173
#35 0x558b3ebcb906 in rb_iseq_eval_main /home/flavorjones/code/oss/ruby/vm.c:2421
#36 0x558b3e79b431 in rb_ec_exec_node /home/flavorjones/code/oss/ruby/eval.c:317
#37 0x558b3e79b755 in ruby_run_node /home/flavorjones/code/oss/ruby/eval.c:375
#38 0x558b3e783758 in main main.c:47
#39 0x7f2be47930b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#40 0x558b3e78356d in _start (/home/flavorjones/.rbenv/versions/3.1.0.dev-with-asan/bin/ruby+0xf656d)
0x6160005b2590 is located 0 bytes to the right of 528-byte region [0x6160005b2380,0x6160005b2590)
allocated by thread T0 here:
#0 0x7f2be4ccbbc8 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x558b3e7ec8e0 in objspace_xmalloc0 /home/flavorjones/code/oss/ruby/gc.c:11379
#2 0x558b3e7ecc9f in ruby_xmalloc2_body /home/flavorjones/code/oss/ruby/gc.c:11623
#3 0x558b3e7f466f in ruby_xmalloc2 /home/flavorjones/code/oss/ruby/gc.c:13571
#4 0x558b3e8508ea in new_arena /home/flavorjones/code/oss/ruby/iseq.c:581
#5 0x558b3e850c93 in prepare_iseq_build /home/flavorjones/code/oss/ruby/iseq.c:619
#6 0x558b3e852e11 in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:889
#7 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#8 0x558b3ec867c2 in compile_iter /home/flavorjones/code/oss/ruby/compile.c:6877
#9 0x558b3ec8e6b8 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7936
#10 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#11 0x558b3ec5e857 in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:795
#12 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#13 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#14 0x558b3ec95f42 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:8884
#15 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#16 0x558b3ec8e474 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7908
#17 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#18 0x558b3ec5e71d in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:787
#19 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#20 0x558b3ec612e2 in new_child_iseq /home/flavorjones/code/oss/ruby/compile.c:1335
#21 0x558b3ec96815 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:8949
#22 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#23 0x558b3ec8e474 in iseq_compile_each0 /home/flavorjones/code/oss/ruby/compile.c:7908
#24 0x558b3ec8de78 in iseq_compile_each /home/flavorjones/code/oss/ruby/compile.c:7863
#25 0x558b3ec5e963 in rb_iseq_compile_node /home/flavorjones/code/oss/ruby/compile.c:801
#26 0x558b3e852e2e in rb_iseq_new_with_opt /home/flavorjones/code/oss/ruby/iseq.c:891
#27 0x558b3e85298b in rb_iseq_new_top /home/flavorjones/code/oss/ruby/iseq.c:838
#28 0x558b3e866037 in load_iseq_eval /home/flavorjones/code/oss/ruby/load.c:589
#29 0x558b3e868c8e in require_internal /home/flavorjones/code/oss/ruby/load.c:1070
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/flavorjones/code/oss/ruby/compile.c:3091 in iseq_peephole_optimize
Shadow bytes around the buggy address:
0x0c2c800ae460: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c2c800ae470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae4a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c2c800ae4b0: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c2c800ae4c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c2c800ae4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae4e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae4f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2c800ae500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==862472==ABORTING
Versions
I haven't gone through this exercise with ASan for other supported Ruby versions, but we've been seeing this error with Valgrind in Ruby 2.7, 3.0, and with the current 3.1.0.dev up to 72a4e1d.
Updated by mdalessio (Mike Dalessio) over 3 years ago
- Description updated (diff)
Updated by xtkoba (Tee KOBAYASHI) over 3 years ago
For me
require "coverage"
Coverage.start
causes an ASan error:
$ ruby -v
ruby 3.1.0dev (2021-06-10T23:31:51Z master 9210f8df7f) [x86_64-linux]
$ ruby -e 'require "coverage"; Coverage.start'
=================================================================
==30193==ERROR: AddressSanitizer: use-after-poison on address 0x7f5142328008 at pc 0x56443fa5f61e bp 0x7ffed157bfd0 sp 0x7ffed157bfc8
READ of size 8 at 0x7f5142328008 thread T0
#0 0x56443fa5f61d in RB_BUILTIN_TYPE /var/tmp/ruby-trunk/asan/../include/ruby/internal/value_type.h:159:30
#1 0x56443fa93297 in objspace_each_objects_try /var/tmp/ruby-trunk/asan/../gc.c:3554:40
#2 0x56443fa1927c in rb_ensure /var/tmp/ruby-trunk/asan/../eval.c:1170:11
#3 0x56443fa5c1a5 in objspace_each_objects /var/tmp/ruby-trunk/asan/../gc.c:3666:5
#4 0x56443fa5bbf1 in rb_objspace_each_objects /var/tmp/ruby-trunk/asan/../gc.c:3622:5
#5 0x56443fb24eef in rb_iseq_trace_set_all /var/tmp/ruby-trunk/asan/../iseq.c:3403:5
#6 0x5644400394df in update_global_event_hook /var/tmp/ruby-trunk/asan/../vm_trace.c:90:2
#7 0x564440035ac8 in hook_list_connect /var/tmp/ruby-trunk/asan/../vm_trace.c:131:9
#8 0x5644400312f3 in connect_event_hook /var/tmp/ruby-trunk/asan/../vm_trace.c:142:5
#9 0x5644400313a2 in rb_add_event_hook2 /var/tmp/ruby-trunk/asan/../vm_trace.c:177:5
#10 0x56443fee0edc in rb_set_coverages /var/tmp/ruby-trunk/asan/../thread.c:5799:5
#11 0x7f513b53575f in rb_coverage_start /var/tmp/ruby-trunk/asan/ext/coverage/../../../ext/coverage/coverage.c:72:2
#12 0x56444000f5de in call_cfunc_m1 /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:2613:12
#13 0x564440005445 in vm_call_cfunc_with_frame /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:2943:11
#14 0x56443fff0476 in vm_call_cfunc /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:2964:12
#15 0x56443ffef79b in vm_call_method_each_type /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:3433:16
#16 0x56443ffeecf0 in vm_call_method /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:3526:20
#17 0x56443ffb7c24 in vm_call_general /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:3569:12
#18 0x56443ffe09a0 in vm_sendish /var/tmp/ruby-trunk/asan/../vm_insnhelper.c:4516:15
#19 0x56443ff9da84 in vm_exec_core /var/tmp/ruby-trunk/asan/../insns.def:773:11
#20 0x56443ffcc767 in rb_vm_exec /var/tmp/ruby-trunk/asan/../vm.c:2160:22
#21 0x56443ffcfe74 in rb_iseq_eval_main /var/tmp/ruby-trunk/asan/../vm.c:2417:11
#22 0x56443fa14c09 in rb_ec_exec_node /var/tmp/ruby-trunk/asan/../eval.c:320:2
#23 0x56443fa1469e in ruby_run_node /var/tmp/ruby-trunk/asan/../eval.c:379:30
#24 0x56443f931b7d in main /var/tmp/ruby-trunk/asan/../main.c:47:9
#25 0x7f5144efa7ec in __libc_start_main /var/tmp/portage/sys-libs/glibc-2.33/work/glibc-2.33/csu/../csu/libc-start.c:332:16
#26 0x56443f885689 in _start (/var/tmp/ruby/asan/bin/ruby+0x388689)
Address 0x7f5142328008 is a wild pointer.
SUMMARY: AddressSanitizer: use-after-poison /var/tmp/ruby-trunk/asan/../include/ruby/internal/value_type.h:159:30 in RB_BUILTIN_TYPE
Shadow bytes around the buggy address:
0x0feaa845cfb0: 00 f7 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00
0x0feaa845cfc0: f7 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7
0x0feaa845cfd0: 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7 00
0x0feaa845cfe0: 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7 00 00
0x0feaa845cff0: 00 00 f7 00 00 00 00 f7 00 00 00 00 00 00 00 00
=>0x0feaa845d000: 00[f7]00 00 00 00 f7 00 00 00 00 f7 00 00 00 00
0x0feaa845d010: f7 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7
0x0feaa845d020: 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7 00
0x0feaa845d030: 00 00 00 f7 00 00 00 00 f7 00 00 00 00 f7 00 00
0x0feaa845d040: 00 00 f7 00 00 00 00 f7 00 00 00 00 f7 00 00 00
0x0feaa845d050: 00 f7 00 00 00 00 f7 00 00 00 00 f7 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==30193==ABORTING
Updated by tankf33der (Mike P) over 3 years ago
What are exactly steps to compile ruby under ASan?
Updated by eileencodes (Eileen Uchitelle) over 3 years ago
Bug fix here https://github.com/ruby/ruby/pull/4601
Updated by eileencodes (Eileen Uchitelle) over 3 years ago
- Status changed from Open to Closed
Applied in changeset git|31f4d262736c224a37e7c630a0790d40b11cdd57.
Check type of instruction - can be INSN or ADJUST
If the type is ADJUST we don't want to treat it like an INSN so we have
to check the type before reading from insn_info.events
.
[Bug #18001] [ruby-core:104371]
Co-authored-by: Aaron Patterson tenderlove@ruby-lang.org
Updated by mdalessio (Mike Dalessio) over 3 years ago
Thank you, Eileen!
Updated by mame (Yusuke Endoh) over 3 years ago
It looks that the patch makes sense. Thank you!