Bug #21516
openSegfault in String#succ! on 32-bit i686
Description
I noticed segfaults in the test suite of Ruby 3.4.5, related to String#succ!
A very easy reproducer is:
./miniruby -e 'puts "ZZZZ999".succ!'
$ ./miniruby -e 'puts "ZZZZ999".succ!'
-e:1: [BUG] Segmentation fault at 0x9e968041
ruby 3.4.5 (2025-07-16 revision 20cda200d3) +PRISM [x86_64-linux-x32]
-- Control frame information -----------------------------------------------
c:0003 p:---- s:0011 e:000010 CFUNC :succ!
c:0002 p:0005 s:0007 e:000005 EVAL -e:1 [FINISH]
c:0001 p:0000 s:0003 E:0017b8 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
-e:1:in ''
-e:1:in 'succ!'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- Machine register context ------------------------------------------------
GS: 0x00000063 FS: 0x00000000 ES: 0x0000002b DS: 0x0000002b EDI: 0x9e968049
ESI: 0x9e968041 EBP: 0x5896c1a0 ESP: 0xff97d638 EBX: 0x9e968046 EDX: 0x9e968049
ECX: 0x5896c1a0 EAX: 0x9e968041 TRA: 0x0000000e ERR: 0x00000004 EIP: 0x5681ea07
CS: 0x00000023 EFL: 0x00010293 UES: 0xff97d638 SS: 0x0000002b
-- C level backtrace information -------------------------------------------
/builddir/ruby/ruby/miniruby(rb_print_backtrace+0x14) [0x568ae631] /builddir/ruby/ruby/vm_dump.c:823
/builddir/ruby/ruby/miniruby(rb_vm_bugreport) /builddir/ruby/ruby/vm_dump.c:1155
/builddir/ruby/ruby/miniruby(rb_bug_for_fatal_signal+0x86) [0x566e4ee6] /builddir/ruby/ruby/error.c:1130
/builddir/ruby/ruby/miniruby(sigsegv+0x4f) [0x5680e98f] /builddir/ruby/ruby/signal.c:934
linux-gate.so.1(_kernel_rt_sigreturn+0x0) [0xf7f475a0]
/builddir/ruby/ruby/miniruby(search_nonascii+0x17) [0x5681ea07] /builddir/ruby/ruby/string.c:728
/builddir/ruby/ruby/miniruby(coderange_scan+0x59) [0x56821d09] /builddir/ruby/ruby/string.c:767
/builddir/ruby/ruby/miniruby(rbimpl_fl_unset_raw_raw+0x0) [0x56824715] /builddir/ruby/ruby/string.c:895
/builddir/ruby/ruby/miniruby(RB_FL_UNSET_RAW) ./include/ruby/internal/fl_type.h:669
/builddir/ruby/ruby/miniruby(RB_ENC_CODERANGE_SET) ./include/ruby/internal/encoding/coderange.h:131
/builddir/ruby/ruby/miniruby(rb_enc_str_coderange) /builddir/ruby/ruby/string.c:911
/builddir/ruby/ruby/miniruby(str_succ+0x69c) [0x5682545c] /builddir/ruby/ruby/string.c:5364
/builddir/ruby/ruby/miniruby(rb_str_succ_bang+0x15) [0x56829f85] /builddir/ruby/ruby/string.c:5380
/builddir/ruby/ruby/miniruby(vm_call_cfunc_with_frame+0x106) [0x568860e6] /builddir/ruby/ruby/vm_insnhelper.c:3794
/builddir/ruby/ruby/miniruby(vm_call_method_each_type+0x76) [0x56894636] /builddir/ruby/ruby/vm_insnhelper.c:4772
/builddir/ruby/ruby/miniruby(vm_sendish+0xa2) [0x5689fecb] /builddir/ruby/ruby/vm_insnhelper.c:5961
/builddir/ruby/ruby/miniruby(vm_exec_core) /builddir/ruby/ruby/insns.def:898
/builddir/ruby/ruby/miniruby(vm_exec_loop+0x38) [0x56893d68] /builddir/ruby/ruby/vm.c:2622
/builddir/ruby/ruby/miniruby(rb_vm_exec) /builddir/ruby/ruby/vm.c:2598
/builddir/ruby/ruby/miniruby(rb_ec_exec_node+0x7f) [0x566eb0ef] /builddir/ruby/ruby/eval.c:281
/builddir/ruby/ruby/miniruby(ruby_run_node+0x58) [0x566ee558] /builddir/ruby/ruby/eval.c:319
/builddir/ruby/ruby/miniruby(main+0x70) [0x56648460] ./main.c:43
-- Other runtime information -----------------------------------------------
-
Loaded script: -e
-
Loaded features:
0 enumerator.so
1 thread.rb
2 fiber.so
3 rational.so
4 complex.so
5 ruby2_keywords.rb -
Process memory map:
56622000-56642000 r--p 00000000 fe:02 9210459 /builddir/ruby/ruby/miniruby
56642000-56941000 r-xp 00020000 fe:02 9210459 /builddir/ruby/ruby/miniruby
56941000-56b4c000 r--p 0031f000 fe:02 9210459 /builddir/ruby/ruby/miniruby
56b4c000-56b54000 r--p 0052a000 fe:02 9210459 /builddir/ruby/ruby/miniruby
56b54000-56b55000 rw-p 00532000 fe:02 9210459 /builddir/ruby/ruby/miniruby
56b55000-56b5e000 rw-p 00000000 00:00 0
58967000-58abb000 rw-p 00000000 00:00 0 [heap]
f43b8000-f5a48000 r--s 00000000 fe:02 9210459 /builddir/ruby/ruby/miniruby
f5a48000-f5a4b000 r--p 00000000 fe:02 11307369 /usr/lib/libgcc_s.so.1
f5a4b000-f5a79000 r-xp 00003000 fe:02 11307369 /usr/lib/libgcc_s.so.1
f5a79000-f5a7e000 r--p 00031000 fe:02 11307369 /usr/lib/libgcc_s.so.1
f5a7e000-f5a7f000 r--p 00035000 fe:02 11307369 /usr/lib/libgcc_s.so.1
f5a7f000-f5a80000 rw-p 00036000 fe:02 11307369 /usr/lib/libgcc_s.so.1
f5a80000-f5ab0000 rw-p 00000000 00:00 0 [anon:Ruby:GC:default:heap_page_body_allocate]
f5abf000-f5ac0000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5ac0000-f5b11000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5b11000-f5b12000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5b12000-f5b63000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5b63000-f5b64000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5b64000-f5bb5000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5bb5000-f5bb6000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5bb6000-f5c07000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5c07000-f5c08000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5c08000-f5c59000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5c59000-f5c5a000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5c5a000-f5cab000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5cab000-f5cac000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5cac000-f5cfd000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5cfd000-f5cfe000 ---p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5cfe000-f5d4f000 rw-p 00000000 00:00 0 [anon:Ruby:fiber_pool_allocate_memory]
f5d4f000-f5d50000 ---p 00000000 00:00 0
f5d50000-f6550000 rw-p 00000000 00:00 0
f6550000-f6590000 rw-p 00000000 00:00 0 [anon:Ruby:GC:default:heap_page_body_allocate]
f659f000-f759f000 rw-p 00000000 00:00 0 [anon:Ruby:Init_default_shapes:shape_cache]
f759f000-f765f000 rw-p 00000000 00:00 0 [anon:Ruby:Init_default_shapes:shape_list]
f765f000-f7690000 rw-p 00000000 00:00 0
f7690000-f76a0000 rw-p 00000000 00:00 0 [anon:Ruby:GC:default:heap_page_body_allocate]
f76a9000-f772a000 rw-p 00000000 00:00 0
f772a000-f79a4000 r--p 00074000 fe:02 11307480 /usr/lib/locale/locale-archive
f79a4000-f7ba4000 r--p 00000000 fe:02 11307480 /usr/lib/locale/locale-archive
f7ba4000-f7ba6000 rw-p 00000000 00:00 0
f7ba6000-f7bc9000 r--p 00000000 fe:02 11307064 /usr/lib/libc.so.6
f7bc9000-f7d69000 r-xp 00023000 fe:02 11307064 /usr/lib/libc.so.6
f7d69000-f7dc5000 r--p 001c3000 fe:02 11307064 /usr/lib/libc.so.6
f7dc5000-f7dc7000 r--p 0021f000 fe:02 11307064 /usr/lib/libc.so.6
f7dc7000-f7dc8000 rw-p 00221000 fe:02 11307064 /usr/lib/libc.so.6
f7dc8000-f7dd2000 rw-p 00000000 00:00 0
f7dd2000-f7de0000 r--p 00000000 fe:02 11307059 /usr/lib/libm.so.6
f7de0000-f7ead000 r-xp 0000e000 fe:02 11307059 /usr/lib/libm.so.6
f7ead000-f7edf000 r--p 000db000 fe:02 11307059 /usr/lib/libm.so.6
f7edf000-f7ee0000 r--p 0010c000 fe:02 11307059 /usr/lib/libm.so.6
f7ee0000-f7ee1000 rw-p 0010d000 fe:02 11307059 /usr/lib/libm.so.6
f7ee1000-f7ee2000 r--p 00000000 fe:02 11307767 /usr/lib/libcrypt.so.2.0.0
f7ee2000-f7efe000 r-xp 00001000 fe:02 11307767 /usr/lib/libcrypt.so.2.0.0
f7efe000-f7f18000 r--p 0001d000 fe:02 11307767 /usr/lib/libcrypt.so.2.0.0
f7f18000-f7f19000 r--p 00036000 fe:02 11307767 /usr/lib/libcrypt.so.2.0.0
f7f19000-f7f1a000 rw-p 00037000 fe:02 11307767 /usr/lib/libcrypt.so.2.0.0
f7f1a000-f7f22000 rw-p 00000000 00:00 0
f7f22000-f7f24000 r--p 00000000 fe:02 11307365 /usr/lib/libz.so.1.3.1
f7f24000-f7f34000 r-xp 00002000 fe:02 11307365 /usr/lib/libz.so.1.3.1
f7f34000-f7f3a000 r--p 00012000 fe:02 11307365 /usr/lib/libz.so.1.3.1
f7f3a000-f7f3b000 r--p 00017000 fe:02 11307365 /usr/lib/libz.so.1.3.1
f7f3b000-f7f3c000 rw-p 00018000 fe:02 11307365 /usr/lib/libz.so.1.3.1
f7f41000-f7f43000 rw-p 00000000 00:00 0
f7f43000-f7f45000 r--p 00000000 00:00 0 [vvar]
f7f45000-f7f47000 r--p 00000000 00:00 0 [vvar_vclock]
f7f47000-f7f49000 r-xp 00000000 00:00 0 [vdso]
f7f49000-f7f4a000 r--p 00000000 fe:02 11307070 /usr/lib/ld-linux.so.2
f7f4a000-f7f72000 r-xp 00001000 fe:02 11307070 /usr/lib/ld-linux.so.2
f7f72000-f7f7d000 r--p 00029000 fe:02 11307070 /usr/lib/ld-linux.so.2
f7f7d000-f7f7f000 r--p 00034000 fe:02 11307070 /usr/lib/ld-linux.so.2
f7f7f000-f7f80000 rw-p 00036000 fe:02 11307070 /usr/lib/ld-linux.so.2
ff95d000-ff97e000 rw-p 00000000 00:00 0 [stack]
Segmentation fault
Backtrace in gdb:
Thread 1 "miniruby" received signal SIGSEGV, Segmentation fault.
search_nonascii (p=, e=0xb3b91049 <error: Cannot access memory at address 0xb3b91049>) at string.c:729
729 if (*s & NONASCII_MASK) {
(gdb) bt
#0 search_nonascii (p=, e=0xb3b91049 <error: Cannot access memory at address 0xb3b91049>) at string.c:729
#1 0x56754d09 in coderange_scan (p=0xb3b91041 <error: Cannot access memory at address 0xb3b91041>, len=8, enc=0x56a961a0)
at string.c:767
#2 0x56757715 in enc_coderange_scan (str=4122077840, enc=) at ./include/ruby/internal/core/rstring.h:430
#3 rb_enc_str_coderange (str=4122077840) at string.c:910
#4 0x5675845c in str_succ (str=str@entry=4122077840) at string.c:5364
#5 0x5675cf85 in rb_str_succ_bang (str=4122077840) at string.c:5380
#6 0x567b90e6 in vm_call_cfunc_with_frame_ (ec=0x56a9598c, reg_cfp=0xf77a6fd8, calling=, argc=0, argv=0xf7727030,
stack_bottom=0xf772702c) at /builddir/ruby/ruby/vm_insnhelper.c:3794
#7 0x567c7636 in vm_call_method_each_type (ec=0x56a9598c, cfp=0xf77a6fd8, calling=0xffffd9e8)
at /builddir/ruby/ruby/vm_insnhelper.c:4772
#8 0x567d2ecb in vm_sendish (ec=, reg_cfp=, cd=, block_handler=,
method_explorer=) at /builddir/ruby/ruby/vm_callinfo.h:415
#9 vm_exec_core (ec=0xb3b91041, ec@entry=0x56a9598c) at /builddir/ruby/ruby/insns.def:898
#10 0x567c6d68 in vm_exec_loop (ec=, state=, tag=, result=10) at vm.c:2622
#11 rb_vm_exec (ec=0x56a9598c) at vm.c:2598
#12 0x5661e0ef in rb_ec_exec_node (ec=ec@entry=0x56a9598c, n=n@entry=0xf5b1eaf4) at eval.c:281
#13 0x56621558 in ruby_run_node (n=0xf5b1eaf4) at eval.c:319
#14 0x5657b460 in rb_main (argc=5, argv=0xffffdc34) at ./main.c:43
#15 main (argc=, argv=) at ./main.c:68
I can trigger this with "./configure CFLAGS="-O2 -g" The -O2 is important. With -flto=auto it goes away, with -O0 or -O3 too.
I use gcc (GCC) 14.2.1 20250405 on Void Linux with glibc 2.41. On 64-bit x86_64 it works fine with same versions.
I have bisected the issue to https://github.com/ruby/ruby/commit/14d154076876
but I don't see how this can introduce the bug. I assume it's some undefined behavior that is potentially triggered.
(However reverting that patch works...)
I think the behavior is related to the embedded string being expanded into a proper string, but my internal-fu isn't good enough.
I hope this helps to debug the issue.
Updated by leahneukirchen (Leah Neukirchen) 3 days ago
It seems RESIZE_CAPA_TERM is miscompiled, so this may not be a Ruby issue but a compiler problem.
At the end of the if(STR_EMBED_P(str)) block, RSTRING_PTR(str) still points into the string object and not to the new heap-allocated string.
Printing the address of str after RESIZE_CAPA(str, slen + carry_len); seems to work around it, could be some aliasing issue.
Updated by alanwu (Alan Wu) 2 days ago
Printing the address of str after RESIZE_CAPA(str, slen + carry_len); seems to work around it, could be some aliasing issue.
Try adding optflags=-fno-strict-aliasing
to configure
?
By the way, unless you want to completely replace some default flags configure
provides, you should use the lowercase cflags, optflags, and debugflags options. They append instead of replace.
Updated by leahneukirchen (Leah Neukirchen) 2 days ago
I tried that, it didn't help. But I also couldn't reproduce it on Debian unstable GCC 14, so it may be an issue with my toolchain.
This patch works around it and shouldn't do much harm else:
--- a/string.c
+++ b/string.c
@@ -5354,7 +5354,8 @@
ENC_CODERANGE_SET(str, ENC_CODERANGE_UNKNOWN);
}
RESIZE_CAPA(str, slen + carry_len);
- sbeg = RSTRING_PTR(str);
+ volatile VALUE wtf = str;
+ sbeg = RSTRING_PTR(wtf);
s = sbeg + carry_pos;
memmove(s + carry_len, s, slen - carry_pos);
memmove(s, carry, carry_len);