2011年9月18日日曜日

protocol bufferのサイズ制限について(protobuf-2.1.0)

C++のオブジェクトをシリアライズ化するのに便利なgoogle protocol buffer(PB)を使ってます。ある時、プログラムの実行記録に使っている時、以下のWarningがでてきました。


libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.


よくわかりませんが、PBの扱えるサイズがセキュリティ上の問題から64MBだよと言ってるらしいです。この時のログは40MBくらいでした。様子を見ると、どうも制限がかかるのは「入力」バッファのサイズのようです。(考えてみればあたりまえですが、デコードするとき一度メモリ上に読み込むはずで、そこにMAX制限がかかってます)
ソースコードを見てみましたが、ばっちりlimitが入ってました。

coded_stream.cc:

namespace {

static const int kDefaultTotalBytesLimit = 64 << 20;  // 64MB

static const int kDefaultTotalBytesWarningThreshold = 32 << 20;  // 32MB  ←警告を出すサイズまで定義されてます。


このサイズを変えればリミットと変えれるのかなと思って少し調べたら、入力ストリームに以下のメソッドがありましたが、コメントを見ると他のコードを混乱させるので、使うのはやめたほうがよさそうです。

void CodedInputStream::SetTotalBytesLimit(
    int total_bytes_limit, int warning_threshold){
  // Make sure the limit isn't already past, since this could confuse other
  // code.

ぐぐってたら、同じようなシリアライズ化のライブラリにMessagePackというのがありそちらのほうが高速だというのがありました。ちょっとそれとも比較してみると。

・制限のチェックをしているのはInputです。
→実装上、protbuf-2.1では入力データをmemcopyしてからデコードしているので64MBの制限がかかります。protbufと似たようなライブラリのMessagePackは入力の実装がmemcopyではなくポインタコピーで済ませているので入力(デコード)がprotbufより速い。しかし最新のprotbuf-2.4.1のInputの実装を調べてみたらmemcopyからポインタコピーになっていました。
2.1で制限として定義されていた変数名も2.4.1ではなくなっており、おそらく64MBの入力サイズの制限はないと思います。(これ間違いだった!!)

・しかし物理的な上限は発生します。
入力チェックのメソッドと思われるところで、入力ポインタ(current_point)のチェックをしているところがあり、真っ先にINT_MAXと比較しています。
当たり前ですが、ポインタはメモリアドレスを表わしていますから最大の値はINT_MAXになります。
またやっかいなことにこのINT_MAXは当該ライブラリが実行されるOSのアーキテェクチャに依存します。
つまり32bit OSの場合には物理的に4GBが最大になります。(ロギング等、実行は64bit linuxでやり、読み込みが32bit Windowsのプログラムなんかだったりするとうっかりこの制限にひっかるので注意!)

まあ、こんな大きなサイズのファイルをめったに作成しないでしょうが念のため


実験してみました。単純に同じデータをどんどん追加していく試験プログラムを作ります。(ファイル名:tttに追記していきます)


[ examples]$ ./add_person_cpp ttt
[ examples]$ ls -l ttt
-rw-r--r-- 1 hoge hoge 29561520  9月 16 15:50 ttt ←tttのサイズはまだ32MB未満です
[examples]$ ./list_people_cpp ttt|tail
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
[examples]$ ./add_person_cpp ttt
[examples]$ ls -l ttt
-rw-r--r-- 1 hoge hoge 33661520  9月 16 15:53 ttt ←tttのサイズが32MBを超えました!
[examples]$ ./list_people_cpp ttt|tail
libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
Person ID: 200
  Name: rinrin
  E-mail address: rinrin.hoge.com
  Mobile phone #: 2222
[examples]$

読み込むファイルが32MBを超えると、warningがでました。(ただ、まだ正しく読めてます)
またこれ以降、追記時に一度読まないといけないのでwarningがでるようになりました。

[examples]$ ./add_person_cpp ttt
libprotobuf WARNING google/protobuf/io/coded_stream.cc:462] Reading dangerously large protocol message.  If the message turns out to be larger than 67108864 bytes, parsing will be halted for security reasons.  To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.

ただ、まだ正しく読めます。


新しいprotobuf-2.4.1を試そうと考えたが、そもそもprotobufの実装が違うんだからlib内の関数のエントリー名を違えば、protocが生成するヘッダーだって異なってくるのが当たり前。(libだけすげかえれば・・・・なんて甘いことはだめだった Orz)
以下のように強引にヘッダーの場所やライブラリの場所を、2.4.1に指定して(システムにはすでに2.1.0がインストールされていますが、まだこの形態をVerUpしたくない)リビルドします。

[examples]$ c++ list_people.cc addressbook.pb.cc -I/opt2/home/maeda/protobuf-2.4.1/src -lpthread -L/opt2/home/hoge/protobuf-2.4.1/src/.libs -lprotobuf -o list_people_cpp

また実行前にLD_LIBRARY_PATHを追加して、2.4.1のdllを認識させます。

[examples]$ ldd ./list_people_cpp
        libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003a76000000)
        libprotobuf.so.7 => not found ←認識できていない!!
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003a7b800000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003a75800000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003a7b400000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003a75400000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003a73c00000)
[examples]$ echo $LD_LIBRARY_PATH
/opt/intel/Compiler/11.0/084/lib/intel64:/opt/intel/Compiler/11.0/084/ipp/em64t/sharedlib:/opt/inte\
l/Compiler/11.0/084/mkl/lib/em64t:/opt/intel/Compiler/11.0/084/tbb/em64t/cc4.1.0_libc2.4_kernel2.6.\
16.21/lib:/opt/intel/Compiler/11.0/084/lib/intel64:/opt/intel/Compiler/11.0/084/ipp/em64t/sharedlib\
:/opt/intel/Compiler/11.0/084/mkl/lib/em64t:/opt/intel/Compiler/11.0/084/tbb/em64t/cc4.1.0_libc2.4_\
kernel2.6.16.21/lib:/usr/local/lib:/usr/local/lib64:/opt2/qtcreator-2.0.1/lib:/usr/local/lib:/usr/l\
ocal/lib64:/opt2/qtcreator-2.0.1/lib
[examples]$ setenv LD_LIBRARY_PATH /opt2/home/hoge/protobuf-2.4.1/src/.libs:${LD_LIB\
RARY_PATH}
[examples]$ ldd ./list_people_cpp
        libpthread.so.0 => /lib64/libpthread.so.0 (0x0000003a76000000)
        libprotobuf.so.7 => /opt2/home/hoge/protobuf-2.4.1/src/.libs/libprotobuf.so.7 (0x00002ad95\
96e8000) ←dllを認識した!!
        libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003a7b800000)
        libm.so.6 => /lib64/libm.so.6 (0x0000003a75800000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003a7b400000)
        libc.so.6 => /lib64/libc.so.6 (0x0000003a75400000)
        /lib64/ld-linux-x86-64.so.2 (0x0000003a73c00000)
        libz.so.1 => /usr/lib64/libz.so.1 (0x0000003a76400000)
[examples]$

試してみたらダメ!!同じwarningが出ます。2.4.1の実装を調べたら、2.1.0から消えた64MBの閾値を格納する変数が別の位置にしっかり定義されていました。

    google::protobuf::io::CodedInputStream decoder(&input);  // has initializer but incomplete type
    decoder.SetTotalBytesLimit(100000000, 60000000);

とやって強引にlimitを変更しようとしましたが、最初のdecoderを宣言するところでコンパイルエラーがでます。(引数に入れているインスタンスがinitializerを必要としていると、言ってきました)
どうもコメントにあるように、このメソッドは使っちゃだめなようです。(googleのところも散々調べてみましたが、特殊な入力の時だけこのSetToalBytesLimit( )は働くようですが、それでもunusualだと書いてました)

PBの最初のところに、「これはRPCのデータを送るために作られた」、「ネットワークで大きなサイズのデータを送るのは問題がおきる」と書いてありましたので、PBをログ記録に使うのはそもそも用途が間違っているようです。

0 件のコメント:

コメントを投稿