JDimproved / JDim

2ch browser for linux
https://jdimproved.github.io/JDim/
GNU General Public License v2.0
44 stars 11 forks source link

AddressSanitizerが有効だとトリップ生成時にクラッシュする #943

Closed ma8ma closed 2 years ago

ma8ma commented 2 years ago

AddressSanitizerが有効だとトリップ生成時にクラッシュする

edit(2022-03-26): コメント末尾に回避策を追記

バグの説明

コンパイラのオプション AddressSanitizer(ASan) を有効にしてビルドすると 書き込みビューのプレビュー表示でトリップを生成するときにクラッシュしました。

再現の方法

  1. AddressSanitizerを有効にしてjdimをビルドする
    meson asan -Db_sanitize=address
    ninja -C asan
  2. jdimを起動して適当なスレッドを開く
  3. 書き込みビューを開いて名前欄にトリップキー(#を入力して続いて任意の文字列)を入力する
  4. ツールバーのボタンまたはショートカットキーでプレビュー表示に切り替える
  5. jdimがクラッシュする
  6. テストプログラムを実行したときもクラッシュする

やりたかったこと・期待する結果

プレビュー表示にしてもクラッシュしない テストプログラムがクラッシュせず終了する

スクリーンショット

jdimがクラッシュしたときのログ ``` AddressSanitizer:DEADLYSIGNAL ================================================================= ==87359==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000000000000 bp 0x7ffe6439cb70 sp 0x7ffe6439c2f8 T0) ==87359==Hint: pc points to the zero page. ==87359==The signal is caused by a READ memory access. ==87359==Hint: address points to the zero page. #0 0x0 () AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV () ==87359==ABORTING ```
テストプログラムがクラッシュしたときのログ ``` [1/5] Generating buildinfo.h with a custom command (wrapped by meson to capture output) Running main() from /build/googletest-OI1WhS/googletest-1.10.0.20201025/googletest/src/gtest_main.cc [==========] Running 192 tests from 25 test suites. [----------] Global test environment set-up. [----------] 6 tests from CookieManager_GetCookieByHost [ RUN ] CookieManager_GetCookieByHost.no_data [ OK ] CookieManager_GetCookieByHost.no_data (0 ms) [ RUN ] CookieManager_GetCookieByHost.single_value [ OK ] CookieManager_GetCookieByHost.single_value (0 ms) [ RUN ] CookieManager_GetCookieByHost.multiple_values [ OK ] CookieManager_GetCookieByHost.multiple_values (0 ms) [ RUN ] CookieManager_GetCookieByHost.empty_values [ OK ] CookieManager_GetCookieByHost.empty_values (0 ms) [ RUN ] CookieManager_GetCookieByHost.parsing_domain [ OK ] CookieManager_GetCookieByHost.parsing_domain (0 ms) [ RUN ] CookieManager_GetCookieByHost.parsing_path [ OK ] CookieManager_GetCookieByHost.parsing_path (0 ms) [----------] 6 tests from CookieManager_GetCookieByHost (0 ms total) [----------] 1 test from CookieManager_DeleteCookieByHost [ RUN ] CookieManager_DeleteCookieByHost.delete_toplevel [ OK ] CookieManager_DeleteCookieByHost.delete_toplevel (0 ms) [----------] 1 test from CookieManager_DeleteCookieByHost (0 ms total) [----------] 4 tests from Iconv_ToAsciiFromUtf8 [ RUN ] Iconv_ToAsciiFromUtf8.empty [ OK ] Iconv_ToAsciiFromUtf8.empty (1 ms) [ RUN ] Iconv_ToAsciiFromUtf8.helloworld [ OK ] Iconv_ToAsciiFromUtf8.helloworld (0 ms) [ RUN ] Iconv_ToAsciiFromUtf8.hiragana [ OK ] Iconv_ToAsciiFromUtf8.hiragana (0 ms) [ RUN ] Iconv_ToAsciiFromUtf8.subdivision_flag [ OK ] Iconv_ToAsciiFromUtf8.subdivision_flag (0 ms) [----------] 4 tests from Iconv_ToAsciiFromUtf8 (1 ms total) [----------] 7 tests from Iconv_ToUtf8FromMs932 [ RUN ] Iconv_ToUtf8FromMs932.empty [ OK ] Iconv_ToUtf8FromMs932.empty (1 ms) [ RUN ] Iconv_ToUtf8FromMs932.helloworld [ OK ] Iconv_ToUtf8FromMs932.helloworld (0 ms) [ RUN ] Iconv_ToUtf8FromMs932.hiragana [ OK ] Iconv_ToUtf8FromMs932.hiragana (0 ms) [ RUN ] Iconv_ToUtf8FromMs932.hex_a0 [ OK ] Iconv_ToUtf8FromMs932.hex_a0 (0 ms) [ RUN ] Iconv_ToUtf8FromMs932.mojibake_fix_inequality_sign_pattern1 [ OK ] Iconv_ToUtf8FromMs932.mojibake_fix_inequality_sign_pattern1 (1 ms) [ RUN ] Iconv_ToUtf8FromMs932.mojibake_fix_inequality_sign_pattern2 [ OK ] Iconv_ToUtf8FromMs932.mojibake_fix_inequality_sign_pattern2 (0 ms) [ RUN ] Iconv_ToUtf8FromMs932.mapping_error [ OK ] Iconv_ToUtf8FromMs932.mapping_error (0 ms) [----------] 7 tests from Iconv_ToUtf8FromMs932 (2 ms total) [----------] 4 tests from Regex_NamedOrNumTest [ RUN ] Regex_NamedOrNumTest.invalid_both_arguments [ OK ] Regex_NamedOrNumTest.invalid_both_arguments (0 ms) [ RUN ] Regex_NamedOrNumTest.prioritize_named_capture [ OK ] Regex_NamedOrNumTest.prioritize_named_capture (0 ms) [ RUN ] Regex_NamedOrNumTest.unregistered_name [ OK ] Regex_NamedOrNumTest.unregistered_name (0 ms) [ RUN ] Regex_NamedOrNumTest.register_invalid_name [ OK ] Regex_NamedOrNumTest.register_invalid_name (0 ms) [----------] 4 tests from Regex_NamedOrNumTest (0 ms total) [----------] 10 tests from Utf8BytesTest [ RUN ] Utf8BytesTest.null_data [ OK ] Utf8BytesTest.null_data (0 ms) [ RUN ] Utf8BytesTest.ascii [ OK ] Utf8BytesTest.ascii (0 ms) [ RUN ] Utf8BytesTest.two_bytes [ OK ] Utf8BytesTest.two_bytes (0 ms) [ RUN ] Utf8BytesTest.three_bytes [ OK ] Utf8BytesTest.three_bytes (0 ms) [ RUN ] Utf8BytesTest.four_bytes [ OK ] Utf8BytesTest.four_bytes (0 ms) [ RUN ] Utf8BytesTest.obsolete_four_bytes [ OK ] Utf8BytesTest.obsolete_four_bytes (0 ms) [ RUN ] Utf8BytesTest.obsolete_five_bytes [ OK ] Utf8BytesTest.obsolete_five_bytes (0 ms) [ RUN ] Utf8BytesTest.obsolete_six_bytes [ OK ] Utf8BytesTest.obsolete_six_bytes (0 ms) [ RUN ] Utf8BytesTest.invalid_byte [ OK ] Utf8BytesTest.invalid_byte (0 ms) [ RUN ] Utf8BytesTest.invalid_seq [ OK ] Utf8BytesTest.invalid_seq (0 ms) [----------] 10 tests from Utf8BytesTest (1 ms total) [----------] 7 tests from MISC_DateToTimeTest [ RUN ] MISC_DateToTimeTest.empty_input [ OK ] MISC_DateToTimeTest.empty_input (0 ms) [ RUN ] MISC_DateToTimeTest.invalid_format [ OK ] MISC_DateToTimeTest.invalid_format (0 ms) [ RUN ] MISC_DateToTimeTest.unix_epoch [ OK ] MISC_DateToTimeTest.unix_epoch (0 ms) [ RUN ] MISC_DateToTimeTest.non_gmt_wont_be_parsed [ OK ] MISC_DateToTimeTest.non_gmt_wont_be_parsed (0 ms) [ RUN ] MISC_DateToTimeTest.iso8601_wont_be_parsed [ OK ] MISC_DateToTimeTest.iso8601_wont_be_parsed (0 ms) [ RUN ] MISC_DateToTimeTest.one_hundred_million [ OK ] MISC_DateToTimeTest.one_hundred_million (0 ms) [ RUN ] MISC_DateToTimeTest.wrong_day_will_be_parsed [ OK ] MISC_DateToTimeTest.wrong_day_will_be_parsed (0 ms) [----------] 7 tests from MISC_DateToTimeTest (0 ms total) [----------] 5 tests from MISC_TimetToStrTest [ RUN ] MISC_TimetToStrTest.time_normal [ OK ] MISC_TimetToStrTest.time_normal (0 ms) [ RUN ] MISC_TimetToStrTest.time_no_year [ OK ] MISC_TimetToStrTest.time_no_year (0 ms) [ RUN ] MISC_TimetToStrTest.time_week [ OK ] MISC_TimetToStrTest.time_week (0 ms) [ RUN ] MISC_TimetToStrTest.time_passed [ OK ] MISC_TimetToStrTest.time_passed (0 ms) [ RUN ] MISC_TimetToStrTest.time_second [ OK ] MISC_TimetToStrTest.time_second (0 ms) [----------] 5 tests from MISC_TimetToStrTest (0 ms total) [----------] 13 tests from GetTripTest [ RUN ] GetTripTest.trip8_sjis_empty [ OK ] GetTripTest.trip8_sjis_empty (0 ms) [ RUN ] GetTripTest.trip8_sjis_A AddressSanitizer:DEADLYSIGNAL ================================================================= ==87690==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000000000000 bp 0x7ffc4dacbe80 sp 0x7ffc4dacb608 T0) ==87690==Hint: pc points to the zero page. ==87690==The signal is caused by a READ memory access. ==87690==Hint: address points to the zero page. #0 0x0 () AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV () ==87690==ABORTING 1/1 gtest tests FAIL 0.07s (exit status 1) Summary of Failures: 1/1 gtest tests FAIL 0.07s (exit status 1) 1/1 gtest tests FAIL 0.07s (exit status 1) Ok: 0 Expected Fail: 0 Fail: 1 Unexpected Pass: 0 Skipped: 0 Timeout: 0 ```

動作環境

[バージョン] JDim 0.7.0-20220320(git:940e818995)
[ディストリ ] Ubuntu 21.10 (x86_64)
[パッケージ] バイナリ/ソース( <配布元> )
[ DE/WM ] KDE
[ gtkmm  ] 3.24.5
[ glibmm  ] 2.64.2
[ TLS lib ] GnuTLS 3.7.1
[ そ の 他 ] 

edit: gcc8, gcc9ではクラッシュしない gcc10, gcc11ではクラッシュした

追加の情報

AddressSanitizerを有効にするとcryptcrypt_r関数が差し替えられるようですが、 差し替えに問題があり関数が正常にリンクされずクラッシュするようです。

edit(2022-03-26): ASanを有効にするときクラッシュを回避する方法

mtasaka commented 2 years ago

問題は、build flag に -lcrypt をつけているにも拘わらず、Wl,--as-needed をつけて -fsanitizer=address -fsanitizer=undefined でcompileすると、多分 link時には libasan.so の中にある crypt が先にみつかり、libcrypt.soにリンクされず、しかし libasan.so の中の cryptは「何らかの初期化みたいなのが必要と思われ?」、「crypt関数の『実体』がnullptrをさしたままになっていて?」crashする、みたいな挙動になっているっぽいです。

ma8ma commented 2 years ago

一応リンカのオプションをいじれるか見てみます リンクをたどるとlibxcryptはasan有効のときテストをスキップしてるのでクラッシュの回避策を入れて触らないのが無難でしょうか https://github.com/besser82/libxcrypt/commit/3aa82ccd3a3fecea7e6a9d0f9c85c56e2e04bb78

ma8ma commented 2 years ago

gcc-11 は打ち消すオプションを追加すればasan有効でもプレビュー表示とテスト通りました edit: clang-13(バックエンドがlibstdc++)でもできました

diff --git a/src/meson.build b/src/meson.build
index a3f7e47243..d76692177e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -118,4 +118,5 @@ jdim_exe = executable(
   include_directories : jdim_incs,
   link_with : jdim_libs,
   install : true,
+  link_args : ['-Wl,--no-as-needed'],
 )
diff --git a/test/meson.build b/test/meson.build
index 2589b9c9ba..435b6b06e2 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -22,5 +22,6 @@ test_exe = executable(
   dependencies : deps,
   include_directories : jdim_incs,
   link_with : jdim_libs,
+  link_args : ['-Wl,--no-as-needed'],
 )
 test('gtest tests', test_exe)

生成されたbuild.ninjaを見ると-Wl,--as-neededの後ろに打ち消すオプション-Wl,--no-as-neededが入ります

 LINK_ARGS = -fsanitize=address -Wl,--as-needed -Wl,--no-undefined -Wl,--start-group src/article/libarticle.a src/bbslist/libbbslist.a src/board/libboard.a src/config/libconfig.a src/control/libcontrol.a src/dbimg/libdbimg.a src/dbtree/libdbtree.a src/history/libhistory.a src/icons/libicon.a src/image/libimage.a src/jdlib/libjdlib.a src/message/libmessage.a src/skeleton/libskeleton.a src/sound/libsound.a src/xml/libxml.a -Wl,--no-as-needed (省略) -Wl,--end-group
mtasaka commented 2 years ago

export LDFLAGS="$LDFLAGS -Wl,--push-state,--no-as-needed -lcrypt -Wl,--pop-state" だけでもきちんとlibcrypt.soにリンクされて、大丈夫のようです。意図としては、なるべく-Wl,--as-needed を使いたいんだけど、-lcryptだけは必ずリンクさせる、というものです。

ただ、全体に-Wl,--no-as-neededを付けるのが簡単ではありますかね。

ma8ma commented 2 years ago

追加の検証でgcc8とgcc9はクラッシュしないことを確認しました。 gcc10、gcc11で起こるようです。

問題のポイントを考えると

対応策の候補は

mtasaka commented 2 years ago
ma8ma commented 2 years ago

mesonがデフォルトで-Wl,--as-neededを追加していてそれが私の環境(ubuntu)でクラッシュの引き金になったようです。 ディストロの事情を追いきれていませんがdebianのgccパッケージがas-neededのパッチを入れてるようで./configureはデフォルトでas-neededを追加しないためasanを有効にしてもクラッシュしませんでした。

何はともあれ、AddressSanitizerを有効にするときの注意をREADMEかINSTALLに追加したいと思います。

ma8ma commented 2 years ago

READMEに注意書きを追加しましたのでissueを閉じます (#944) 検証とアドバイスありがとうございました :+1:

ma8ma commented 3 months ago

この問題は llvm 17 と gcc 14 で修正されるようです。

Ubuntu 23.10 の clang 17 を使ってテストしたところ segfault は発生しませんでした。

$ CC=clang-17 CXX=clang++-17 meson setup asan17 -Doptimization=1 -Db_sanitize=address,undefined -Dcpp_args="-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Werror=return-type -Werror=implicit-fallthrough -Werror=narrowing -Werror=uninitialized" -Dcpp_link_args="-Wl,-z,now,-z,relro -Wl,-Bsymbolic-functions"
$ time meson compile -C asan17
$ time meson test --gdb -C asan17
(snip)
[  PASSED  ] 614 tests.
[バージョン] JDim 0.11.0-20240330(git:3e9c8d5407)
[ディストリ ] Ubuntu 23.10 (x86_64)
[パッケージ] バイナリ/ソース( <配布元> )
[ DE/WM ] KDE
[ gtkmm  ] 3.24.8
[ glibmm  ] 2.66.6
[ TLS lib ] GnuTLS 3.8.1
[ そ の 他 ] 

同じディストロでgcc 13を使ってテストしたところ segfaultが発生しました。

$ meson setup asan-gcc13 -Doptimization=1 -Db_sanitize=address,undefined -Dcpp_args="-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Werror=return-type -Werror=implicit-fallthrough -Werror=narrowing -Werror=uninitialized" -Dcpp_link_args="-Wl,-z,now,-z,relro -Wl,-Bsymbolic-functions"
$ time meson compile -C asan-gcc13
$ time meson test --gdb -C asan-gcc13
(snip)
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ??? ()
#1  0x00007ffff7889b7e in __interceptor_crypt_r (key=0x7ffff2584670 "A", salt=0x7ffff2c10090 "H.", data=0x7ffff2c10140)
    at ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:10212
#2  0x000055555ac429a7 in create_trip_conventional (key="A") at ../src/jdlib/misctrip.cpp:235
#3  0x000055555ac438b2 in MISC::get_trip (utf8str="A", encoding=encoding@entry=Encoding::sjis) at ../src/jdlib/misctrip.cpp:272
#4  0x0000555559bbf241 in (anonymous namespace)::get_trip_sjis (u8key="A") at ../test/gtest_jdlib_misctrip.cpp:20
#5  (anonymous namespace)::GetTripTest_trip8_sjis_A_Test::TestBody (this=<optimized out>) at ../test/gtest_jdlib_misctrip.cpp:30
#6  0x000055555b1611af in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) ()
#7  0x000055555b14e6b6 in testing::Test::Run() ()
#8  0x000055555b14e875 in testing::TestInfo::Run() ()
#9  0x000055555b14e9a7 in testing::TestSuite::Run() ()
#10 0x000055555b156f24 in testing::internal::UnitTestImpl::RunAllTests() ()
#11 0x000055555b161797 in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) ()
#12 0x000055555b14eb28 in testing::UnitTest::Run() ()
#13 0x0000555559459bb4 in main ()

gcc 14 で segfault が発生しないか確かめてreadmeなどを更新したいと思います。

ma8ma commented 1 month ago

kubuntu 24.04 で gcc14 を試してみたところトリップ生成でクラッシュしなくなりました。 readmeなどを更新していきたいと思います。

OSとツールチェーン

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble

$ g++-14 --version
g++-14 (Ubuntu 14-20240412-0ubuntu1) 14.0.1 20240412 (experimental) [master r14-9935-g67e1433a94f]
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

meson setup

$ CC=gcc-14 CXX=g++-14 meson setup asan-gcc14 -Doptimization=1 -Db_sanitize=address,undefined -Dcpp_args="-Wp,-D_GLIBCXX_ASSERTIONS -fstack-protector-strong -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Werror=return-type -Werror=implicit-fallthrough -Werror=narrowing -Werror=uninitialized" -Dcpp_link_args="-Wl,-z,now,-z,relro -Wl,-Bsymbolic-functions"

jdimの動作環境

[バージョン] JDim 0.11.0-20240519(git:3753e4edb8)
[ディストリ ] Ubuntu 24.04 LTS (x86_64)
[パッケージ] バイナリ/ソース( <配布元> )
[ DE/WM ] KDE
[ gtkmm  ] 3.24.9
[ glibmm  ] 2.66.7
[ TLS lib ] GnuTLS 3.8.3
[ そ の 他 ]