/protobuf-shareddtor-crash

Reproducing a certain bug in protobuf

Primary LanguageC++MIT LicenseMIT

Protobuf SharedDtor Crash

To reproduce protocolbuffers/protobuf#435.

Setup

onnx.pb.cc is generated by protoc 2.6.1 from onnx.proto (licensed by MIT License). I added printfs for debugging.

  1. Download source code of protobuf and build it
$ curl -LO https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz
$ tar xvf protobuf-2.6.1.tar.gz
$ cd protobuf-2.6.1
$ ./configure --prefix=/home/<user>/protobuf-2.6.1 CFLAGS=-fPIC CXXFLAGS=-fPIC
$ make
$ make install
  1. Clone the repository and copy protobuf to include/ and lib/ directory
$ git clone https://github.pfidev.jp/okamoto/protobuf-shareddtor.git
$ cd protobuf-shareddtor
$ cp -r ~/protobuf-2.6.1/include/google protobuf-shareddtor/include
$ cp -r ~/protobuf-2.6.1/lib/libprotobuf.* protobuf-shareddtor/lib
  1. Build and run the reproduction code
$ export LD_LIBRARY_PATH=/<somewhere>/protobuf-shareddtor-crash/lib
$ mkdir -p build
$ cd build/
$ cmake ..
$ ./myapp

Result

$ ./myapp 
### Starting: ModelProto::SharedCtor() (0xf810e0)
### Finishing: ModelProto::SharedCtor() (0xf810e0)
### Starting: ModelProto::SharedCtor() (0xf89320)
### Finishing: ModelProto::SharedCtor() (0xf89320)
# Invoking: foo::new_onnx_model()
## Starting: onnx::ModelProto::new_onnx_model()
### Starting: ModelProto::SharedCtor() (0x7ffea029c020)
### Finishing: ModelProto::SharedCtor() (0x7ffea029c020)
## GetEmptyStringAlreadyInited in libfoo.so is 0xf7d260
## Finishing: onnx::ModelProto::new_onnx_model() (returns 0x7ffea029c020)
# Finished: foo::new_onnx_model() (returned value is 0x7ffea029c020)
producer_name: 
### Starting: ModelProto::SharedDtor() (0x7ffea029c020)
### GetEmptyStringAlreadyInited() is 0xf85fb0
### Deleting: producer_name_ (= 0xf7d260)
### Deleting: producer_version_ (= 0xf7d260)
*** Error in `./myapp': double free or corruption (out): 0x0000000000f83ae0 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f7d751077e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f7d7511037a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f7d7511453c]
./myapp(_ZN4onnx10ModelProto10SharedDtorEv+0x183)[0x42d799]
./myapp(_ZN4onnx10ModelProtoD1Ev+0x24)[0x42d59c]
./myapp(main+0xde)[0x45134d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f7d750b0830]
./myapp(_start+0x29)[0x422449]
======= Memory map: ========
...

Discussion

The conditions in SharedDtor() are always true even if the actual value of producer_name_ and producer_version_ are GetEmptyStringAlreadyInited() because it is allocated by libfoo.so not by myapp. As pointed out in the issue, it points to different address between libfoo.so and myapp (See GetEmptyStringAlreadyInited() is ... in the result).

As it turned out, it tries to delete GetEmptyStringAlreadyInited() in libfoo.so (= 0xf7d260) twice and produces double free or corruption error.

void ModelProto::SharedCtor() {
  ::google::protobuf::internal::GetEmptyString();
  ...
  producer_name_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
  producer_version_ = const_cast< ::std::string*>(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
  ...
}

void ModelProto::SharedDtor() {
  if (producer_name_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
    delete producer_name_;
  }
  if (producer_version_ != &::google::protobuf::internal::GetEmptyStringAlreadyInited()) {
    delete producer_version_;
  }
  ...