セキュリティキャンプ事後課題 (前編)

seccamp2017 Advent Calendar 2017 - Adventar 18日目の記事です。

セキュリティキャンプ2017全国大会 集中コースZの実習内容です。
事後課題としてキャンプ終了後にもslackでぼちぼち進捗を生み出していました。
全体的な内容と事後課題の前半部分を紹介したいと思います。

概要

テーマとしてはSPDKを使ったマルウェア検知機能付きKVSを作る。 ということになっていて、 具体的には
- ファイルを送るとストレージに保存できる。
- SPDKを使ったのでDiskIOが速い
- yaraというライブラリを組み込んだのでマルウェア検知機能がついている。

という感じです。実際にベンチマークを取ったりして速さを比べたりしていました。 github.com

事前課題

やったことは以下のとおりです。
- 土台となるKey-Valueストアの作成
- KVSでtcpソケットを用いたファイルの転送の対応
- KVSへのyaraの組み込み
- SPDKのセットアップ

ブログにちょっとだけ詳しく書いてあります。 kotarou777775.hatenablog.com

キャンプ中

事前課題で書いたプログラムでは生成したスレッドを放置していてゾンビになっていたり、大きなファイルを送ると上手く受信出来なかったり排他制御をしていなかったりと問題が多かったので、
キャンプ中は
- スレッドのゾンビ化の阻止
- スレッドの排他制御
- 大きなサイズのファイルに対応
- ログ機能の実装
- ベンチマークの測定
をしました。

ちょっと説明。

スレッドのゾンビ化

サーバーが生成したスレッドがクライアントが切断しても生き残っていたのが悪いです。
ちゃんと後始末をしましょう。

スレッドの排他制御

もともと気になっていました。
Go言語にはSyncMapというライブラリ(まだ標準じゃない)が存在するので使いました。
めっちゃ便利です。

syncmap - GoDoc

ログ機能の実装

logrusというライブラリを使用。 いい感じにログを出力してくれて、JSON形式で出力も出来るらしいですが普通に出力するようにしています。
環境変数でログのON/OFFを設定して完了。
github.com

ベンチマークの測定

講師の方が用意して下さった機材で実験。
クライアントの並列数とか送信する総ファイル数を設定して実行する感じです。
ページのキャッシュあり/なしでHDD, SSDでそれぞれ実験しました。一番右のSPDKは想像で書いただけで測定をしていない事に注意して下さい。
f:id:kotarou777775:20170819222534p:plain

↑の事とか通信の仕様を変更してたら、完成に出来ませんでした。
そんな中の成果報告のスライドがこちら(リンク)す。
セキュリティキャンプ2017全国大会 集中コースZ5 // Speaker Deck

キャンプ中に出来なかったところとして、
- yaraで並列でスキャンすると落ちる(多分yaraが悪い)
- SPDKを使って無い。
があり、事後課題になりました。

事後課題

事後課題として残っていた事は、
- yaraで並列スキャンすると落ちる
- SPDKでベンチマークをとっていない。    でした。

講師の忠鉢さんにサーバーを用意して頂きリモートで実験をしていました。

yaraで並列スキャンすると落ちる

最終的にはgo言語のバージョンを1.7.4から1.8.3に変えたら再現しなくなって放置しています。 キャンプ中は1.7.4を使用しており、手元(1.9.1)では再現出来なかったので試しに1.8ならどうだろうということで試したところバグが再現しなくなりました。以下はバージョンを変えるまでに調べた内容です。これといった結論は出していないので読み飛ばして構いません。

エラー出力

runtime: writebarrierptr *0xc420c6b7a0 = 0x1
fatal error: bad pointer in write barrier

runtime stack:
runtime.throw(0x8c3c83, 0x1c)
        /usr/lib/go-1.7/src/runtime/panic.go:566 +0x95
runtime.writebarrierptr.func1()
        /usr/lib/go-1.7/src/runtime/mbarrier.go:151 +0xbd
runtime.systemstack(0xc42001b500)
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:298 +0x79
runtime.mstart()
        /usr/lib/go-1.7/src/runtime/proc.go:1079

goroutine 660 [running]:
runtime.systemstack_switch()
        /usr/lib/go-1.7/src/runtime/asm_amd64.s:252 fp=0xc420c6b6d0 sp=0xc420c6b6c8
runtime.writebarrierptr(0xc420c6b7a0, 0x1)
以下省略

どうやらここ

go/iface.go at release-branch.go1.7 · golang/go · GitHubwritebarrierptr((*uintptr)(r), uintptr(e.data))におかしなポインタが入って

go/mbarrier.go at release-branch.go1.7 · golang/go · GitHub で落ちてしまうらしいです。

エラー出力の最初の方を見ると

runtime: writebarrierptr *0xc420c6b7a0 = 0x1
fatal error: bad pointer in write barrier

と書いており、0xc420c6b7a0に0x1というアドレスを代入している(ポインタのポインタにポインタを入れている)ようなことをしていました。
0x1という値は、

go-yara/rules.go at master · hillu/go-yara · GitHubより、

func (r *Rules) ScanMem(buf []byte, flags ScanFlags, timeout time.Duration) (matches []MatchRule, err error) {
    var ptr *C.uint8_t
    if len(buf) > 0 {
        ptr = (*C.uint8_t)(unsafe.Pointer(&(buf[0])))
    }
    id := callbackData.Put(&matches)
    defer callbackData.Delete(id)
    err = newError(C.yr_rules_scan_mem(
        r.cptr,
        ptr,
        C.size_t(len(buf)),
        C.int(flags),
        C.YR_CALLBACK_FUNC(C.rules_callback),
        unsafe.Pointer(id),
        C.int(timeout/time.Second)))
    r.keepAlive()
    return
}

...
    id := callbackData.Put(&matches)
...
        unsafe.Pointer(id),
...

の変数idの値であると考えられました。(それ以外考えられなかった。)
idの値を出力してみると、0 ~ 5あたりを出力していました。 以上です、結論は何もありません。

ここまでの感想

  • Go言語の勉強になったと思う。むやみにgoroutineを使うとメモリを半端じゃなく消費しますね。
  • GoからC言語の関数呼び出し沼にハマってしまったのが辛かった。
  • spdkを触りたい。