【问题标题】:boost::archive::binary_iarchive constructor is directly crashing without throwing exception in linux onlyboost::archive::binary_iarchive 构造函数直接崩溃而不会仅在 linux 中引发异常
【发布时间】:2021-11-11 09:11:23
【问题描述】:

我们有类似下面的代码:


#include <sstream>
#include <iostream>
#include <fstream>

#include "boost/archive/binary_iarchive.hpp"
#include "boost/archive/binary_oarchive.hpp"

using namespace std;

class MetaData
{
public:
    int old_GroupID;
    int new_GroupID;
    double newpos;
    string newmessg;

    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version)
    {
        ar& old_GroupID;
        ar& new_GroupID;
        ar& newpos;
        ar& newmessg;
    }

};

void Serialization(const MetaData &object, ofstream &fileStream)
{
    try
    {
        boost::archive::binary_oarchive oarch(fileStream);
        oarch & object;
    }
    catch (boost::archive::archive_exception e)
    {

        cout << "boost::archive::archive_exception:exception while serialisation" << e.what() << endl;

    }
    catch (...)
    {
        cout << "unknow exception caught in Serialization" << endl;
    }

}

void DeSerialization(const MetaData &object, stringstream &stringStreamer)
{
    try
    {
        // This crashes instead of throwing
        boost::archive::binary_iarchive iarch(stringStreamer);
        iarch & object;
    }
    catch (boost::archive::archive_exception e)
    {
        cout << "boost::archive::archive_exception: exception" << e.what() << endl;
    }
    catch (...)
    {
        cout << "unknown exception caught" << endl;
    }
}

int main()
{

    string tempfilename = "/storage/serilazationfile.tmp";

    {

        ofstream OutputStream;
        OutputStream.open(tempfilename, ios::out | ios::binary); //assuming file is opened

        MetaData metadataobjectone;
        metadataobjectone.old_GroupID = 1;
        metadataobjectone.new_GroupID = 5;
        metadataobjectone.newpos = 600.156;
        metadataobjectone.newmessg = "A newmessage for object one";

        Serialization(metadataobjectone, ofstream);

        MetaData metadataobjecttwo;
        metadataobjecttwo.old_GroupID = 2;
        metadataobjecttwo.new_GroupID = 6;
        metadataobjecttwo.newpos = 800.192;
        metadataobjecttwo.newmessg = "A newmessage for object two";

        Serialization(metadataobjecttwo, ofstream); //this seralize object , in the file should be corrupted like adding spaces or add unwanted data. 

        OutputStream.flush();
        OutputStream.close();

    }

    stringstream stringStreamer;
    ifstream InputStream;
    InputStream.open(tempfilename, ios::in | ios::binary);
    stringStreamer << InputStream.rdbuf();
    stringStreamer.seekg(0, stringStreamer.end);
    size_t FileSize = stringStreamer.tellg();
    stringStreamer.seekg(0, stringStreamer.beg);

    while (true)
    {
        MetaData object;//MetaData is a userdefined structure
        DeSerialization(object, stringStreamer); //while deserializing 2nd oject(corrupted) exe will be crash in linux but it will not crash in windows.
        //doing something with deserialized data
        if (FileSize == stringStreamer.tellg())
        {
            break;
        }
    }


}

我们故意破坏serilazationfile.tmp(通过添加换行符或不需要的数据)以处理负面情况,例如文件偶然损坏或文件在实际环境中包含无效数据。

在 Windows 中,当反序列化损坏的文件时,会抛出一些异常并捕获“未知异常”,但在 Linux 中它会直接崩溃,即在 Linux 中它是无法捕获的。

那么这里的问题是什么,为什么在 Linux 中无法捕获这种情况?

使用 Boost 版本 1_58、x64bit ubuntu 18.05 和 x64bit Windows server 2016 进行测试。

我们得到了下面的调用栈:-

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007f36efef7921 in __GI_abort () at abort.c:79
#2  0x00007f36edd53b21 in Vmacore::PanicExit(char const*) () from /usr/lib/vmware/libvmacore.so
#3  0x00007f36edb1a75d in ?? () from /usr/lib/vmware/libvmacore.so
#4  0x00007f36f08f12ac in operator new(unsigned long) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007f36f0931c29 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007f36f0932afb in std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7  0x00007f36f0932bb4 in std::string::reserve(unsigned long) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#8  0x00007f36f0932f9c in std::string::append(unsigned long, char) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#9  0x0000000002d97876 in boost::archive::basic_binary_iprimitive<boost::archive::binary_iarchive, char, std::char_traits<char> >::load(std::string&) ()
#10 0x0000000002d97a35 in boost::archive::basic_binary_iarchive<boost::archive::binary_iarchive>::init() ()
#11 0x00000000017a4ea3 in boost::archive::binary_iarchive_impl<boost::archive::binary_iarchive, char, std::char_traits<char> >::init (this=0x7f365d139570, flags=0)
    at /usr/local/include/boost/archive/binary_iarchive_impl.hpp:68
#12 0x00000000017a4182 in boost::archive::binary_iarchive_impl<boost::archive::binary_iarchive, char, std::char_traits<char> >::binary_iarchive_impl (
    this=0x7f365d139570, is=..., flags=0) at /usr/local/include/boost/archive/binary_iarchive_impl.hpp:97
#13 0x00000000017a3c93 in boost::archive::binary_iarchive::binary_iarchive (this=0x7f365d139570, is=..., flags=0)
    at /usr/local/include/boost/archive/binary_iarchive.hpp:44
**#14 0x0000000002697c4a in CustmSerialization::Deserialization<MetaData*, std::basic_stringstream<char, std::char_traits<char>, std::allocator<char> > > (
    this=0x7f365d13a0dc, object=@0x7f365d1398b0: 0xd68, fileStream=...)

CustmSerialization::Deserialization 中,我们有与上面示例类似的反序列化逻辑,并且我们有catch(...),但没有捕获到异常。

【问题讨论】:

  • @DevSolar 我已经提供了必要的细节,你现在可以检查一下吗。
  • 这个例子还有很长的路要走。这个想法是任何人都可以复制您的示例,粘贴,编译,并准确查看您在问题中所说的内容。我添加了必要的包括main() 和一些适当的缩进。我还修复了 FileSize 缺少类型。代码仍然无法编译,因为 someobjecttype 没有声明,我完全不确定 iarch &amp; object; 是否是正确的语法,但是我自己也没有使用过 Boost.Serialization。只是想向您展示如何编写一个有可能被回答的问题。 ;-) 关闭投票撤回
  • 有一个简短的特定输入示例也会有所帮助。 -- 注意你可以catch( const std::exception &amp;e ) { std::cerr &lt;&lt; e.what(); },或者catch( ... ) { boost::current_exception_diagnostic_information(); }#include "boost/exception/diagnostic_information.hpp"

标签: c++ boost try-catch


【解决方案1】:

没有自包含的复制器,我们只能猜测。

首先,让我写一个没有问题的演示。然后我会猜测你的(隐藏的)实现调用UB的方式。

工作演示

让我们创建一个saveload 函数,在途中打印一些调试输出:

Live On Coliru

#include "boost/archive/binary_iarchive.hpp"
#include "boost/archive/binary_oarchive.hpp"
#include <fstream>
#include <iostream>
#include <sstream>

struct MetaData {
    int i;
    void serialize(auto& ar, unsigned) { ar& i; }
};

void save(std::vector<MetaData> data, std::string const& filename)
{
    std::ofstream ofs(filename, std::ios::binary);

    for (auto& object : data) {

        try {
            boost::archive::binary_oarchive oa(ofs);
            oa << object;
        } catch (std::exception const& e) {
            std::cerr << e.what() << std::endl;
        } catch (...) {
            std::cerr << "unknown exception caught" << std::endl;
        }
        std::cerr << "save tellp: " << ofs.tellp() << "\n";
    }
}

auto load(std::string const& filename)
{
    std::stringstream mem;
    {
        std::ifstream ifs(filename, std::ios::binary);
        mem << ifs.rdbuf();
    }

    mem.seekg(0, mem.end);
    auto const FileSize = mem.tellg();

    mem.seekg(0, mem.beg);

    std::vector<MetaData> data;
    do {
        std::cerr << "load tellg: " << mem.tellg() << ", FileSize:" << FileSize << "\n";
        auto& object = data.emplace_back();

        try {
            // This crashes instead of throwing
            boost::archive::binary_iarchive iarch(mem);
            iarch >> object;
        } catch (std::exception const& e) {
            std::cerr << "boost::archive::archive_exception: exception" << std::endl;
        } catch (...) {
            std::cerr << "unknown exception caught" << std::endl;
        }

        // doing something with deserialized data
    } while (mem.tellg() < FileSize);

    return data;
}

int main()
{
    save({{11}, {22}, {33}}, "test.bin");

    std::cout << "----\n";
    for (auto& obj : load("test.bin")) {
        std::cout << "object " << obj.i << "\n";
    }
}

打印,例如:

save tellp: 49
save tellp: 98
save tellp: 147
----
load tellg: 0, FileSize:147
load tellg: 49, FileSize:147
load tellg: 98, FileSize:147
object 11
object 22
object 33

进入水晶球

我可以想象您没有在需要时完成归档:

void save(std::vector<MetaData> data, std::string const& filename)
{
    std::ofstream ofs(filename, std::ios::binary);

    try {
        boost::archive::binary_oarchive oa(ofs);
        for (auto& object : data) {
            oa << object;
            std::cerr << "save tellp: " << ofs.tellp() << "\n";
        }
    } catch (std::exception const& e) {
        std::cerr << e.what() << std::endl;
    } catch (...) {
        std::cerr << "unknown exception caught" << std::endl;
    }
}

尝试使用相同的加载函数读取此结果:

load tellg: 0, FileSize:57
load tellg: 49, FileSize:57
=================================================================
==31200==ERROR: AddressSanitizer: allocator is out of memory trying to allocate 0x2100000017 bytes
    #0 0x7fbbe371b717 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb4717)
    #1 0x7fbbde4bc8b9 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0x13d8b9)

==31200==HINT: if you don't care about these errors you may set allocator_may_return_null=1
SUMMARY: AddressSanitizer: out-of-memory (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb4717) in operator new(unsigned long)
==31200==ABORTING

这与您的堆栈跟踪非常相似。

你可以像上面那样修复它,或者匹配阅读功能(这就是档案的主要用途):

Live On Coliru

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <fstream>
#include <iostream>

struct MetaData {
    int i;
    void serialize(auto& ar, unsigned) { ar& i; }
};

void save(std::vector<MetaData> const& data, std::string const& filename) {
    std::ofstream ofs(filename, std::ios::binary);
    boost::archive::binary_oarchive oa(ofs);
    oa << data;
}

auto load(std::string const& filename) {
    std::vector<MetaData> data;

    std::ifstream ifs(filename, std::ios::binary);
    boost::archive::binary_iarchive iarch(ifs);
    iarch >> data;

    return data;
}

int main() {
    save({{11}, {22}, {33}}, "test.bin");

    for (auto& obj : load("test.bin")) {
        std::cout << "object " << obj.i << "\n";
    }
}

打印

object 11
object 22
object 33

注意这个版本是代码的一小部分。

【讨论】:

  • 我已编辑问题,请立即检查一次。在我提到的第二个序列化对象应该损坏的问题中,当在 linux 中反序列化第二个对象时,它将崩溃,即无法捕获,在 Windows 中可以正常工作,即错误将被捕获到 catch 块中。
  • 恐怕这样的模糊测试很容易发现 UB。毫不奇怪,如果您将垃圾放入其中,则不会指定会发生什么。您可能应该制作自己的具有校验和的存档格式,并使用反序列化限制。如果你真的想让它安全,你需要加密方法(例如 HMAC)。所有这些都不是库的功能。
  • 实际上,我们放置垃圾是为了重现写入磁盘中的序列化数据已损坏的情况,后来当我们尝试在 linux 中反序列化该数据时它会崩溃,而在 windows 中却没有。我想知道,为什么它只在 linux 中崩溃,即使我们已经处理了带有 catch(...) 块的代码。
  • 只是触发了UB。这可能被认为是一个错误(尽管弹性/安全性没有明确列出一个功能)。您总是可以追踪哪里出了问题(显然它只是用错误解释的大小进行失控分配,没有什么神奇的)并提出强化补丁。但就像我说的,唯一真正的解决方法是拥有一个自定义存档实现,添加例如HMAC 到流,因此您可以可靠地拒绝损坏的流。
猜你喜欢
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-15
  • 2011-09-20
  • 2012-03-22
  • 1970-01-01
相关资源
最近更新 更多