「clang(コンパイラ)+lld(リンカ)でWindows開発をしたい」というのはC++でWindows開発しているなら誰もが1度ぐらいは考えるのではないでしょうか?
そのためには環境構築難度、可搬性、デバッグ難度、権利問題、いろいろあるとは思います。
今回はそのうちの1つ「VisualStudioに比べてセキュリティは低下しないの?」を調べてみました。
この記事の目次
調査範囲
VisualStudioのコンパイラ及びリンカ提供の機能のうちセキュリティ的に主要機能と思われるものを独断と偏見で選びだし調査しました。
またこれらについては過去に本ブログで個別記事を書いています。
結論
結論から先に書いておきます。
ある程度は問題ないですが、節々が怪しいので未実装の機能を使わないとしてもセンシティブなプロダクトでは危ないと感じました。
以下まとめ
- コンパイラ
- リンカ
- /DYNAMICBASE
- 問題なし
- /SAFESEH
- オプションの処理に不具合、おそらく動いている?
- /GUARD:CF
- おそらく問題はないがコンパイラ側が未実装なため不明
- /DYNAMICBASE
検証環境
公式の手順に従いVisualStudioでビルドしたclang8.0.0の32bit版を使用。
ただしVisualStudioは2019で、clangビルドでコンパイルエラーした箇所のみパッチを当てた物。
また、正しい動作が得られなかったものについてのみ検証時のorigin/masterのHEADである9fa56f7829aa5f5cca911c400bb43d854b46dc15をビルドしたものも使用した。
/GS(BufferSecurityCheck)
コンパイラオプション
- VisualStudio
- cl.exe /GS a.cpp
- clang
- clang++.exe -fuse-ld=lld -fstack-protector-strong -v a.cpp
検証方法
#include <cstdio>
int main() {
char buf[8];
::fgets(buf, 256, stdin);
return 0;
}
上記をオプションありとなしでコンパイルして大量の入力を行い、オプションなしはデバッガにてreturnまで到達してから異常終了すること、ありはreturnより前に検知され強制終了するかを検証した。
結果
両者共に正しく強制終了した、逆アセンブラ結果もカナリアの値も問題なし。
/guard:cf(ControlFlowGuard)
本項はコンパイラとリンカ両方合わせての検証となる。
コンパイラオプション
- VisualStudio
- cl.exe /guard:cf a.cpp
- clang
- clang++.exe -fuse-ld=lld -cfguard -Wl,-guard:cf -v a.cpp
-cfguardオプションはそのままだと無視されるバグが存在しており、パッチを当てて有効化して使用した。
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index cbaf5cb..773e69e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -3470,6 +3470,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
// FIXME: Implement custom jobs for internal actions.
CmdArgs.push_back("-cc1");
+ if (Args.hasArg(options::OPT_cfguard)) {
+ CmdArgs.push_back("-cfguard");
+ }
+
// Add the "effective" target triple.
CmdArgs.push_back("-triple");
CmdArgs.push_back(Args.MakeArgString(TripleStr));
パッチを当てない場合-vオプションでいったん-cc1ありの長いコマンド例を出した後、そこに-cfguardを加えて実行すると認識する。
検証方法
#include <cstdio>
class Hoge {
public:
virtual int hoge() {
return 128;
}
};
class Fuga : public Hoge {
public:
virtual int hoge() {
return 256;
}
};
Hoge * ptr = new Hoge();
Hoge * ptr2 = new Fuga();
int Haga() {
return 512;
}
typedef int (*HagaPtr)();
volatile HagaPtr ptr3 = Haga;
void Hege(Hoge *ptr, Hoge *ptr2, HagaPtr ptr3) {
::printf("%d:%d:%d\n", ptr->hoge(), ptr2->hoge(), ptr3());
}
int main() {
Hege(ptr, ptr2, ptr3);
return 0;
}
上記をオプションありとなしでコンパイルしてそれぞれの仮想関数テーブルや関数ポインターなどをデバッガで別関数へ書き換え実行、オプションなしは別関数が実行されること、ありは強制終了するかを検証した。
結果
clang+lldでは強制終了しなかった。
>clang/lib/Driver/ToolChains/Clang.cpp:5873
>Currently there’s no support emitting CFG instrumentation; the flag only emits the table of address-taken functions.
とある通りチェック処理は未実装の模様。
また、解析した範囲ではチェック関数用の関数アドレスリストの生成や有効化フラグ自体は問題なく発行されているように見えた。
/DYNAMICBASE
コンパイラオプション
- VisualStudio
- cl.exe a.cpp /link/DYNAMICBASE
- clang
- clang++.exe -fuse-ld=lld -Wl,-dynamicbase -v a.cpp
- clang++.exe -fuse-ld=lld -Wl,-dynamicbase -v a.cpp
検証方法
int main() {
return 0;
}
上記をオプションありとなしでコンパイルしてデバッガ越しに実行、a.exeがロードされるアドレスを確認しtouchした後再度同様の作業をする、オプションなしはアドレスが変化していないこと、オプションありは変化していることを検証した。
結果
問題なし、ロードされるアドレスも自身が知っているランダマイズの範疇であった。
/SAFESEH
コンパイラオプション
- VisualStudio
- cl.exe a.cpp /link/SafeSEH
- clang
- clang++.exe -fuse-ld=lld -Wl,-safeseh -v a.cpp
- clang++.exe -fuse-ld=lld -Wl,-safeseh -v a.cpp
検証方法
#include <cstdio>
int main() {
char buf[8];
::fgets(buf, 256, stdin);
return 0;
}
上記をコンパイルオプションありとなしでコンパイルしてデバッガ越しに実行、大量の入力によりseh連鎖を上書きした後デバッガにより例外を発生させた、オプションなしは異常終了すること、オプションありは強制終了することと検証した。
結果
clang+lldでは常に強制終了した。
どうやら-safeseh:noを指定してもsafeseh有効のバイナリを吐くようで(おそらくバグ)、無効にする方法が見つからなかった。
そのため、SafeSEHが正しく作用した結果強制終了しているのか、なんらかの別要員でたまたま強制終了したのかの区別がつかなかった。
一応解析した範囲ではSafeSEHのフラグが立っており正常に適用されているように見えるので動いている可能性が高いとは思える。
終わりに
数にして4つのみの検証となったが未実装1、不具合により検証不能1という結果になった。
実際のところこれが信頼に値するかはわからないが、自分の感性においてはまだ不安が残る数値だと考える。
とはいえchromeでの採用実績があるとのことなので使用を志す人は検討してみるのもいいだろう。
ちなみに自分が調べた範囲だとchromeは/guard:cfを有効でコンパイルしているようだが、当然チェック関数は未実装であり、ControlFlowGuardは機能していなかったことをここに記録しておく。