UBSan:boost::program_options 与 std::string

naf*_*fur 5 c++ boost boost-program-options clang++ ubsan

我们目前正在调查我们程序中可能的未定义行为,该行为由 clang7 UBSan 与 boost 1.69.0 中的 boost::program_option 结合标记。我们已经创建了以下可以编译和运行的工作示例clang++ -std=c++17 -fsanitize=undefined -fno-omit-frame-pointer -lboost_program_options debug.cpp && UBSAN_OPTIONS=print_stacktrace=1 ./a.out

#include <iostream>
#include <boost/program_options.hpp>

namespace po = boost::program_options;

int main() {
    std::string test_string = "";
    po::options_description desc("test");
    desc.add_options()
        ("string", po::value<std::string>(&test_string))
    ;

    constexpr char *argv[] = {"test", "--string", "test"};
    constexpr int argc = sizeof(argv) / sizeof(char*);

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    std::cerr << "Before notify" << std::endl;
    po::notify(vm);

    std::cout << "string -> " << test_string << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

我们得到以下输出:

/usr/include/boost/any.hpp:249:17: runtime error: downcast of address 0x5638e42892e0 which does not point to an object of type 'any::holder<typename remove_cv<basic_string<char, char_traits<char>, allocator<char> > >::type>' (aka 'holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >')
0x5638e42892e0: note: object is of type 'boost::any::holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'
 00 00 00 00  00 55 f9 63 28 7f 00 00  f8 92 28 e4 38 56 00 00  04 00 00 00 00 00 00 00  74 65 73 74
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'boost::any::holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'
    #0 0x5638e1b06d6e in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >* boost::any_cast<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::any*) (/home/gereon/carl/src/tests/carl-settings/a.out+0x4cd6e)
    #1 0x5638e1b0672c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const* boost::any_cast<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::any const*) (/home/gereon/carl/src/tests/carl-settings/a.out+0x4c72c)
    #2 0x5638e1b03625 in boost::program_options::typed_value<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>::notify(boost::any const&) const (/home/gereon/carl/src/tests/carl-settings/a.out+0x49625)
    #3 0x7f2863f6a71e in boost::program_options::variables_map::notify() (/usr/lib/libboost_program_options.so.1.69.0+0x5771e)
    #4 0x5638e1afa2ae in main (/home/gereon/carl/src/tests/carl-settings/a.out+0x402ae)
    #5 0x7f2863a13222 in __libc_start_main (/usr/lib/libc.so.6+0x24222)
    #6 0x5638e1ad33ad in _start (/home/gereon/carl/src/tests/carl-settings/a.out+0x193ad)

/usr/include/boost/any.hpp:249:114: runtime error: member access within address 0x5638e42892e0 which does not point to an object of type 'any::holder<typename remove_cv<basic_string<char, char_traits<char>, allocator<char> > >::type>' (aka 'holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >')
0x5638e42892e0: note: object is of type 'boost::any::holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'
 00 00 00 00  00 55 f9 63 28 7f 00 00  f8 92 28 e4 38 56 00 00  04 00 00 00 00 00 00 00  74 65 73 74
              ^~~~~~~~~~~~~~~~~~~~~~~
              vptr for 'boost::any::holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'
    #0 0x5638e1b06e36 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >* boost::any_cast<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::any*) (/home/gereon/carl/src/tests/carl-settings/a.out+0x4ce36)
    #1 0x5638e1b0672c in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const* boost::any_cast<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >(boost::any const*) (/home/gereon/carl/src/tests/carl-settings/a.out+0x4c72c)
    #2 0x5638e1b03625 in boost::program_options::typed_value<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>::notify(boost::any const&) const (/home/gereon/carl/src/tests/carl-settings/a.out+0x49625)
    #3 0x7f2863f6a71e in boost::program_options::variables_map::notify() (/usr/lib/libboost_program_options.so.1.69.0+0x5771e)
    #4 0x5638e1afa2ae in main (/home/gereon/carl/src/tests/carl-settings/a.out+0x402ae)
    #5 0x7f2863a13222 in __libc_start_main (/usr/lib/libc.so.6+0x24222)
    #6 0x5638e1ad33ad in _start (/home/gereon/carl/src/tests/carl-settings/a.out+0x193ad)

string -> test
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,它运行良好(之后test_string具有正确的值)。我们已经调查了 boost::program_options 和 boost::any 并发现了以下内容:在program_options::typed_value::notify()(#2)中,我们得到了一个 boost::any 并通过调用 boost::any_cast() 将它转换为它应该是的类型。

考虑到 clang 的输出(downcast of address 0x5638e42892e0 which does not point to an object of type 'any::holder<typename remove_cv<basic_string<char, char_traits<char>, allocator<char> > >::type>' (aka 'holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >')object is of type 'boost::any::holder<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >'),我们只能得出结论,这个 boost::any 实例实际上拥有正确的成员......在这种情况下,向下转换应该是合法的。

然而,UBSan 表示不会出现误报。

有趣的是,我们无法在没有 program_options 的情况下重现此错误:

#include <boost/any.hpp>
#include <iostream>

void any_cast(const boost::any& a) {
    const std::string* t2 = boost::any_cast<std::string>(&a);
    std::cout << *t2 << std::endl;
}

int main() {
    boost::any a;
    a = std::string("test");
    any_cast(a);
}
Run Code Online (Sandbox Code Playgroud)

因此我们怀疑(预编译的)program_options 可能会以某种方式与 std::string 类型混淆。

顺便说一下,valgrind 不会报告两个程序的任何错误。

有任何想法吗?

我们已经在 Arch linux(boost 1.69.0-1,clang 7.0.1-1 使用 libstd++)和 CentOS 7.6(clang 7.0.0 使用 libc++ 和 boost 1.69.0 使用相同的clang 构建)上尝试过这个。