如何通过boost::iostream防止zip炸弹

3

我写了一段像这样的代码:

std::vector<char> unzip(std::vector<char> const& compressed)
{
   std::vector<char> decompressed;

   boost::iostreams::filtering_ostream os;

   os.push(boost::iostreams::gzip_decompressor());
   os.push(boost::iostreams::back_inserter(decompressed));

   boost::iostreams::write(os, &compressed[0], compressed.size());
   os.reset();
   return decompressed;
}

如果compressed是一个zip炸弹,会发生什么?我认为内存会耗尽并且进程会崩溃。
那么如何避免这种情况呢?我该如何在解压缩之前检查原始数据的大小?

我会在std::thread中运行此程序,检查共享解压缩的大小,如果超过限制,则终止线程。参见https://dev59.com/vmct5IYBdhLWcg3wSbfY。 - schorsch_76
编写一个自定义的 back_inserter,其中您可以添加终止标志,以便在添加了try/catch处理程序后使用该标志抛出异常。 - schorsch_76
@schorsch_76 我认为你是正确的。 - alpha
1
不。线程的想法很糟糕。它会因无谓的复杂性和低效率而带来很多问题。(如果你要做那个,你可以启动一个具有适当资源限制的单独进程。这样一来,它就具有了进程空间隔离的好处,因此可以防御解压缩编解码器中可能存在的大量其他漏洞) - sehe
3
@schorsch_76,你实际上无法终止一个线程。 - n. m.
@sehe 我是指自定义的 back_inserter,而不是线程。当然,线程的想法很糟糕。 - alpha
2个回答

2

像往常一样,您需要在解压缩时注意。

您可以使用固定/有限容量的缓冲区(例如使用 boost::iostreams::array_sink),或者可以在复制操作中包装一个最大大小的保护程序。

此外,在您的示例中,输入是内存缓冲区,因此更合理的是使用设备而不是流进行输入。 因此,这里是一个简单的方法:

std::vector<char> unzip(size_t limit, std::vector<char> const& compressed) {
   std::vector<char> decompressed;

   boost::iostreams::filtering_istream is;

   is.push(boost::iostreams::gzip_decompressor());
   is.push(boost::iostreams::array_source(compressed.data(), compressed.size()));

   while (is && (decompressed.size() < limit)) {
       char buf[512];
       is.read(buf, sizeof(buf));
       decompressed.insert(decompressed.end(), buf, buf + is.gcount());
   }
   return decompressed;
}

当创建一个简单的60字节的小型炸弹,它会扩展成20千字节的NUL字符时:

int main() {
    std::vector<char> const bomb = { 
          char(0x1f), char(0x8b), char(0x08), char(0x08), char(0xd1), char(0x6d), char(0x0e), char(0x5b), char(0x00), char(0x03), char(0x62), char(0x6f),
          char(0x6d), char(0x62), char(0x00), char(0xed), char(0xc1), char(0x31), char(0x01), char(0x00), char(0x00), char(0x00), char(0xc2), char(0xa0),
          char(0xf5), char(0x4f), char(0x6d), char(0x0a), char(0x3f), char(0xa0), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00),
          char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00), char(0x00),
          char(0x00), char(0x80), char(0xb7), char(0x01), char(0x60), char(0x83), char(0xbc), char(0xe6), char(0x00), char(0x50), char(0x00), char(0x00)
        };

    auto max10k  = unzip(10*1024, bomb);
    auto max100k = unzip(100*1024, bomb);

    std::cout << "max10k:  " << max10k.size()  << " bytes\n";
    std::cout << "max100k: " << max100k.size() << " bytes\n";
}

打印在Coliru上实时运行

max10k:  10240 bytes
max100k: 20480 bytes

抛出异常

当然,如果超出限制,您可以选择抛出异常:

std::vector<char> unzip(size_t limit, std::vector<char> const& compressed) {
   std::vector<char> decompressed;

   boost::iostreams::filtering_istream is;

   is.push(boost::iostreams::gzip_decompressor());
   is.push(boost::iostreams::array_source(compressed.data(), compressed.size()));

   while (is) {
       char buf[512];
       is.read(buf, sizeof(buf)); // can't detect EOF before attempting read on some streams

       if (decompressed.size() + is.gcount() >= limit)
           throw std::runtime_error("unzip limit exceeded");

       decompressed.insert(decompressed.end(), buf, buf + is.gcount());
   }
   return decompressed;
}

1

schorsch_76刚刚说我可以编写一个自定义的back_inserter,所以我就写了一个,它可以正常工作:

namespace boost {
namespace iostreams {
template<typename Container>
class limit_back_insert_device {
public:
    typedef typename Container::value_type  char_type;
    typedef sink_tag                        category;
    limit_back_insert_device(Container& cnt, size_t max_size)
        : container(&cnt)
        , max_size(max_size) {
        check(0);
    }
    std::streamsize write(const char_type* s, std::streamsize n) {
        check(n);
        container->insert(container->end(), s, s + n);
        return n;
    }
private:
    void check(size_t n) {
        if (std::numeric_limits<size_t>::max() - n < container->size()) {
            throw std::runtime_error("size_t overflow");
        }

        if ((container->size() + n) > max_size) {
            throw std::runtime_error("container->size() > max_size");
        }
    }
protected:
    Container * container;
    size_t const max_size;
};

template<typename Container>
limit_back_insert_device<Container> limit_back_inserter(Container& cnt,
    size_t max_size) {
    return limit_back_insert_device<Container>(cnt, max_size);
}
} 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接