Project

General

Profile

Actions

Bug #11486

closed

euc-jpな文字列に対してgsubするとSEGVします

Added by hiraku (Hiraku Kuroda) over 8 years ago. Updated over 8 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 2.3.0dev (2015-08-25 trunk 51677) [x86_64-linux]
[ruby-dev:<unknown>]

Description

euc-jpで記述されたinvalidなhtmlを置換処理でvalidにするスクリプトを作っていたのですが、文字列を String#gsub する部分でSEGVが発生しました。
最初に見つけたのは v2.2.3 でしたが、現時点のtrunkの r51677 と v2.2.0 でも起こりました。

以下は再現させるスクリプトと r51677 での出力です。スクリプトはutf-8で書いています。
他に必要な情報がありましたらお知らせください。

kuroda@charlie:~$ expand -3 segv.rb 
#!/usr/bin/env ruby

src = ""
(19..298).each do |n|
   src += ("あ"*n + "\r"*n).encode("euc-jp")
end
src.gsub(/xxx/i, "")
puts "OK"
kuroda@charlie:~$ /opt/ruby/r51677/bin/ruby -v segv.rb 
ruby 2.3.0dev (2015-08-25 trunk 51677) [x86_64-linux]
segv.rb:7: [BUG] Segmentation fault at 0x007fc2542e7ee0
ruby 2.3.0dev (2015-08-25 trunk 51677) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0003 p:---- s:0010 e:000009 CFUNC  :gsub
c:0002 p:0023 s:0005 E:001a98 EVAL   segv.rb:7 [FINISH]
c:0001 p:0000 s:0002 E:001b80 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
segv.rb:7:in `<main>'
segv.rb:7:in `gsub'

-- Machine register context ------------------------------------------------
 RIP: 0x00007fc2521c8553 RBP: 0x00007fc25491e7a0 RSP: 0x00007ffe0c58e4d8
 RAX: 0x00000000fffffe70 RBX: 0x00007fc2542e8072 RCX: 0x00007ffe0c58e5b0
 RDX: 0x00007fc2542e8073 RDI: 0x00007fc2542e7ee0 RSI: 0x00007ffe0c58e590
  R8: 0x00007fc25491e7a0  R9: 0x00007fc2521c8d40 R10: 0x00000000000000a2
 R11: 0x00007fc2521c8d40 R12: 0x00007fc2542e8070 R13: 0x00007fc254c35e40
 R14: 0x00007fc2542e8073 R15: 0x00007ffe0c58e5b0 EFL: 0x0000000000010206

-- C level backtrace information -------------------------------------------
/opt/ruby/r51677/bin/ruby(rb_vm_bugreport+0x4ea) [0x7fc25417180a] vm_dump.c:695
/opt/ruby/r51677/bin/ruby(rb_bug_context+0xcb) [0x7fc25414d99b] error.c:422
/opt/ruby/r51677/bin/ruby(sigsegv+0x3e) [0x7fc25405133e] signal.c:886
/lib/x86_64-linux-gnu/libpthread.so.0 [0x7fc253b30d10]
/opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so(mbc_case_fold+0x3) [0x7fc2521c8553] ./enc/euc_jp.c:373
/opt/ruby/r51677/bin/ruby(forward_search_range+0xba9) [0x7fc254036bc9] regexec.c:3014
/opt/ruby/r51677/bin/ruby(onig_search_gpos+0x6f6) [0x7fc25403e126] regexec.c:4162
/opt/ruby/r51677/bin/ruby(onig_search+0x16) [0x7fc25403e6e6] regexec.c:3906
/opt/ruby/r51677/bin/ruby(rb_reg_search0+0xfc) [0x7fc25402283c] re.c:1485
/opt/ruby/r51677/bin/ruby(str_gsub+0x71) [0x7fc254071071] string.c:4443
/opt/ruby/r51677/bin/ruby(vm_call_cfunc+0xf9) [0x7fc2540c8f19] vm_insnhelper.c:1604
/opt/ruby/r51677/bin/ruby(vm_call_method+0xfe) [0x7fc2540d67fe] vm_insnhelper.c:1984
/opt/ruby/r51677/bin/ruby(vm_exec_core+0x15ee) [0x7fc2540cfb8e] insns.def:976
/opt/ruby/r51677/bin/ruby(vm_exec+0x7f) [0x7fc2540d476f] vm.c:1470
/opt/ruby/r51677/bin/ruby(ruby_exec_internal+0xbf) [0x7fc253f872ef] eval.c:244
/opt/ruby/r51677/bin/ruby(ruby_run_node+0x2f) [0x7fc253f8af7f] eval.c:309
/opt/ruby/r51677/bin/ruby(main+0x4b) [0x7fc253f86f0b] parse.y:8801

-- Other runtime information -----------------------------------------------

* Loaded script: segv.rb

* Loaded features:

    0 enumerator.so
    1 thread.rb
    2 rational.so
    3 complex.so
    4 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/encdb.so
    5 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/transdb.so
    6 /opt/ruby/r51677/lib/ruby/2.3.0/unicode_normalize.rb
    7 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/rbconfig.rb
    8 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/compatibility.rb
    9 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/defaults.rb
   10 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/deprecate.rb
   11 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/errors.rb
   12 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/version.rb
   13 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/requirement.rb
   14 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/platform.rb
   15 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/basic_specification.rb
   16 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/stub_specification.rb
   17 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/util/list.rb
   18 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/stringio.so
   19 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/specification.rb
   20 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/exceptions.rb
   21 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/core_ext/kernel_gem.rb
   22 /opt/ruby/r51677/lib/ruby/2.3.0/monitor.rb
   23 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb
   24 /opt/ruby/r51677/lib/ruby/2.3.0/rubygems.rb
   25 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
   26 /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/japanese_euc.so

* Process memory map:

7fc250de5000-7fc250fae000 r--s 00000000 08:01 4063545                    /lib/x86_64-linux-gnu/libc-2.21.so
7fc250fae000-7fc251d58000 r--s 00000000 00:2a 214972                     /opt/ruby/r51677/bin/ruby
7fc251d58000-7fc251d6e000 r-xp 00000000 08:01 4063571                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc251d6e000-7fc251f6d000 ---p 00016000 08:01 4063571                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc251f6d000-7fc251f6e000 rw-p 00015000 08:01 4063571                    /lib/x86_64-linux-gnu/libgcc_s.so.1
7fc251f6e000-7fc251fc5000 r-xp 00000000 00:2a 215796                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/japanese_euc.so
7fc251fc5000-7fc2521c4000 ---p 00057000 00:2a 215796                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/japanese_euc.so
7fc2521c4000-7fc2521c6000 r--p 00056000 00:2a 215796                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/japanese_euc.so
7fc2521c6000-7fc2521c7000 rw-p 00058000 00:2a 215796                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/japanese_euc.so
7fc2521c7000-7fc2521ca000 r-xp 00000000 00:2a 215789                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
7fc2521ca000-7fc2523c9000 ---p 00003000 00:2a 215789                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
7fc2523c9000-7fc2523ca000 r--p 00002000 00:2a 215789                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
7fc2523ca000-7fc2523cb000 rw-p 00003000 00:2a 215789                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
7fc2523cb000-7fc2523d3000 r-xp 00000000 00:2a 215824                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/stringio.so
7fc2523d3000-7fc2525d2000 ---p 00008000 00:2a 215824                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/stringio.so
7fc2525d2000-7fc2525d3000 r--p 00007000 00:2a 215824                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/stringio.so
7fc2525d3000-7fc2525d4000 rw-p 00008000 00:2a 215824                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/stringio.so
7fc2525d4000-7fc2525d6000 r-xp 00000000 00:2a 215794                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/transdb.so
7fc2525d6000-7fc2527d6000 ---p 00002000 00:2a 215794                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/transdb.so
7fc2527d6000-7fc2527d7000 r--p 00002000 00:2a 215794                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/transdb.so
7fc2527d7000-7fc2527d8000 rw-p 00003000 00:2a 215794                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/trans/transdb.so
7fc2527d8000-7fc2527da000 r-xp 00000000 00:2a 215770                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/encdb.so
7fc2527da000-7fc2529d9000 ---p 00002000 00:2a 215770                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/encdb.so
7fc2529d9000-7fc2529da000 r--p 00001000 00:2a 215770                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/encdb.so
7fc2529da000-7fc2529db000 rw-p 00002000 00:2a 215770                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/encdb.so
7fc2529db000-7fc252d92000 r--p 00000000 08:01 1835894                    /usr/lib/locale/locale-archive
7fc252d92000-7fc252f52000 r-xp 00000000 08:01 4063545                    /lib/x86_64-linux-gnu/libc-2.21.so
7fc252f52000-7fc253152000 ---p 001c0000 08:01 4063545                    /lib/x86_64-linux-gnu/libc-2.21.so
7fc253152000-7fc253156000 r--p 001c0000 08:01 4063545                    /lib/x86_64-linux-gnu/libc-2.21.so
7fc253156000-7fc253158000 rw-p 001c4000 08:01 4063545                    /lib/x86_64-linux-gnu/libc-2.21.so
7fc253158000-7fc25315c000 rw-p 00000000 00:00 0 
7fc25315c000-7fc253263000 r-xp 00000000 08:01 4063519                    /lib/x86_64-linux-gnu/libm-2.21.so
7fc253263000-7fc253462000 ---p 00107000 08:01 4063519                    /lib/x86_64-linux-gnu/libm-2.21.so
7fc253462000-7fc253463000 r--p 00106000 08:01 4063519                    /lib/x86_64-linux-gnu/libm-2.21.so
7fc253463000-7fc253464000 rw-p 00107000 08:01 4063519                    /lib/x86_64-linux-gnu/libm-2.21.so
7fc253464000-7fc25346d000 r-xp 00000000 08:01 4063494                    /lib/x86_64-linux-gnu/libcrypt-2.21.so
7fc25346d000-7fc25366c000 ---p 00009000 08:01 4063494                    /lib/x86_64-linux-gnu/libcrypt-2.21.so
7fc25366c000-7fc25366d000 r--p 00008000 08:01 4063494                    /lib/x86_64-linux-gnu/libcrypt-2.21.so
7fc25366d000-7fc25366e000 rw-p 00009000 08:01 4063494                    /lib/x86_64-linux-gnu/libcrypt-2.21.so
7fc25366e000-7fc25369c000 rw-p 00000000 00:00 0 
7fc25369c000-7fc25369f000 r-xp 00000000 08:01 4063450                    /lib/x86_64-linux-gnu/libdl-2.21.so
7fc25369f000-7fc25389e000 ---p 00003000 08:01 4063450                    /lib/x86_64-linux-gnu/libdl-2.21.so
7fc25389e000-7fc25389f000 r--p 00002000 08:01 4063450                    /lib/x86_64-linux-gnu/libdl-2.21.so
7fc25389f000-7fc2538a0000 rw-p 00003000 08:01 4063450                    /lib/x86_64-linux-gnu/libdl-2.21.so
7fc2538a0000-7fc25391e000 r-xp 00000000 08:01 1835614                    /usr/lib/x86_64-linux-gnu/libgmp.so.10.2.0
7fc25391e000-7fc253b1e000 ---p 0007e000 08:01 1835614                    /usr/lib/x86_64-linux-gnu/libgmp.so.10.2.0
7fc253b1e000-7fc253b1f000 r--p 0007e000 08:01 1835614                    /usr/lib/x86_64-linux-gnu/libgmp.so.10.2.0
7fc253b1f000-7fc253b20000 rw-p 0007f000 08:01 1835614                    /usr/lib/x86_64-linux-gnu/libgmp.so.10.2.0
7fc253b20000-7fc253b38000 r-xp 00000000 08:01 4063491                    /lib/x86_64-linux-gnu/libpthread-2.21.so
7fc253b38000-7fc253d38000 ---p 00018000 08:01 4063491                    /lib/x86_64-linux-gnu/libpthread-2.21.so
7fc253d38000-7fc253d39000 r--p 00018000 08:01 4063491                    /lib/x86_64-linux-gnu/libpthread-2.21.so
7fc253d39000-7fc253d3a000 rw-p 00019000 08:01 4063491                    /lib/x86_64-linux-gnu/libpthread-2.21.so
7fc253d3a000-7fc253d3e000 rw-p 00000000 00:00 0 
7fc253d3e000-7fc253d62000 r-xp 00000000 08:01 4063489                    /lib/x86_64-linux-gnu/ld-2.21.so
7fc253eb0000-7fc253f61000 r--s 00000000 08:01 1848523                    /usr/lib/debug/lib/x86_64-linux-gnu/libpthread-2.21.so
7fc253f61000-7fc253f62000 r--p 00023000 08:01 4063489                    /lib/x86_64-linux-gnu/ld-2.21.so
7fc253f62000-7fc253f63000 rw-p 00024000 08:01 4063489                    /lib/x86_64-linux-gnu/ld-2.21.so
7fc253f63000-7fc253f64000 rw-p 00000000 00:00 0 
7fc253f64000-7fc25423a000 r-xp 00000000 00:2a 214972                     /opt/ruby/r51677/bin/ruby
7fc254295000-7fc2542c5000 r--s 00000000 00:2a 215789                     /opt/ruby/r51677/lib/ruby/2.3.0/x86_64-linux/enc/euc_jp.so
7fc2542c5000-7fc2542e8000 r--s 00000000 08:01 4063491                    /lib/x86_64-linux-gnu/libpthread-2.21.so
7fc2542e8000-7fc25440f000 rw-p 00000000 00:00 0 
7fc254410000-7fc254433000 rw-p 00000000 00:00 0 
7fc254433000-7fc254434000 ---p 00000000 00:00 0 
7fc254434000-7fc254439000 rw-p 00000000 00:00 0                          [stack:21767]
7fc254439000-7fc25443e000 r--p 002d5000 00:2a 214972                     /opt/ruby/r51677/bin/ruby
7fc25443e000-7fc25443f000 rw-p 002da000 00:2a 214972                     /opt/ruby/r51677/bin/ruby
7fc25443f000-7fc254450000 rw-p 00000000 00:00 0 
7fc25491a000-7fc2559c2000 rw-p 00000000 00:00 0                          [heap]
7ffe0bd93000-7ffe0c592000 rw-p 00000000 00:00 0 
7ffe0c5c4000-7ffe0c5c6000 r--p 00000000 00:00 0                          [vvar]
7ffe0c5c6000-7ffe0c5c8000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]


[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

中止 (コアダンプ)
Actions #1

Updated by hiraku (Hiraku Kuroda) over 8 years ago

  • Subject changed from euc-jpな文字列に対してgsub!するとSEGVします to euc-jpな文字列に対してgsubするとSEGVします

すみません、タイトル修正しました。
String#gsub! でも発生するのですが、提出したサンプルは String#gsub を使っています。

Actions #2

Updated by hiraku (Hiraku Kuroda) over 8 years ago

2点補足します

  • 最初に現象を発見したときは euc-jp なファイルを読み込んで String#gsub! したら発生したのですが、サンプルスクリプトではループの中の encode("euc-jp") を外してループの後でまとめて src.encode!("euc-jp") とすると、SEGVが起こりません
  • src.gsub 中の正規表現は i オプションを外すとSEGVしなくなります

参考にしていただければ幸いです。

Actions #3

Updated by wanabe (_ wanabe) over 8 years ago

code_to_mbc() が ONIGERR_INVALID_CODE_POINT_VALUE を返し、
mbc_case_fold() がそれをエラーコードと判断せずそのままポインタに加算してしまっているようでした。

そもそも code_to_mbc() がエラーになるのが不思議な気はしますが、この原因はよくわかりませんでした。

Actions #4

Updated by nagachika (Tomoyuki Chikanaga) over 8 years ago

  • Backport changed from 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN to 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: REQUIRED
Actions #5

Updated by wanabe (_ wanabe) over 8 years ago

code_to_mbc() がエラーを返す原因について調べてみました。

String#gsub の内部で呼び出される forward_search_range() は、内部で reg->optimize の値によって呼び出す関数を決定しますが、このときに applied to a multibyte string というコメントのついている bm_search_notrev_ic() ではなく、bm_search_ic() が呼ばれているのが原因なのではないかと推測しています。
(たとえば bm_search_ic() 内の s += reg->map[s[1]] のような記述があり、s[1] というのは s の先頭がマルチバイト文字だったときに破綻しそうです)

これは、前述の reg->optimize が set_optimize_exact_info() で決定されるときに、正規表現のエンコーディングやパターンだけを見ているために起きています。
String#gsub を含む多くの場合は rb_reg_prepare_re() を経由するため、文字列と正規表現双方が ASCII 文字のみで構成されている場合には reg->enc は us_ascii になっていることが期待でき、また逆に us_ascii でない場合にはどちらかにマルチバイト文字が含まれているものと考えてよいように思います。

というところから考えて、以下のようなパッチを書いてみたところ、一応現象は収まったように見えます。
ただ本来の「文字列と正規表現両方にマルチバイト文字が含まれない」という条件の表現としてはわかりにくい気がしています。そもそも本当に bm_search_ic() でなく bm_search_notrev_ic() を使うことが根本的な解決なのかどうかもよくわかりませんでした。
また、メモリ配置の状況や環境によってはパッチ前の状態でも全く SEGV にならないため、テストをどう書くかも問題になりそうです。(OSX では全く再現しませんでした)

diff --git a/regcomp.c b/regcomp.c
index c1698ea..a39cfe8 100644
--- a/regcomp.c
+++ b/regcomp.c
@@ -5291,7 +5291,7 @@ set_optimize_exact_info(regex_t* reg, OptExactInfo* e)
   xmemcpy(reg->exact, e->s, e->len);
   reg->exact_end = reg->exact + e->len;
 
-  allow_reverse =
+  allow_reverse = reg->enc->max_enc_len == 1 &&
        ONIGENC_IS_ALLOWED_REVERSE_MATCH(reg->enc, reg->exact, reg->exact_end);
 
   if (e->ignore_case > 0) {

また別問題として enc/euc_jp.c の mbc_case_fold() では、code_to_mbc() がエラーを返した場合の処理を足しておいたほうが安全だと思います。

Actions #6

Updated by naruse (Yui NARUSE) over 8 years ago

  • Status changed from Open to Closed

Applied in changeset r52016.


  • enc/euc_jp.c (mbc_case_fold): check given string is valid or not,
    and if invalid, return 1. [Bug #11486]
Actions #7

Updated by naruse (Yui NARUSE) over 8 years ago

r52016 で修正しました、報告ありがとうございます。

おそらくbm_search_icを使っているのは意図的なもので、その条件はマルチバイトに対応しているかではなく、
誤マッチが発生するか否かです。
EUC-JPで誤マッチが発生するのは、対象文字列中の2バイト文字の2バイト目から、検索文字列の1バイト目がマッチするケースですが、
この場合はis_allowed_reverse_match()内の(c <= 0x7e || c == 0x8e || c == 0x8f)が偽になるケースなので除外されています。

今回の問題は、こうしたチェックを行うことで、内部の途中の処理で一時的に不正なバイト列が現れるの上等で高速化している部分において、
そのエラー処理が不十分だったことによるものでした。
ですから「 enc/euc_jp.c の mbc_case_fold() では、code_to_mbc() がエラーを返した場合の処理を足」すのが本質的な修正ですね。

Updated by nagachika (Tomoyuki Chikanaga) over 8 years ago

  • Backport changed from 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: REQUIRED to 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: DONE

r52016, r52017, r52019, r52020 and r52021 were backported into ruby_2_2 branch at r52652.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0