Reassem: Reject blocks overflowing 64bit upper

The reassembler logic isn't wrap around safe, so just truncate or
reject such blocks. For files specifically, a byte offset in the
2**64 bytes represents 16EiB which is the maximum size supported
by BTRFS or NTFS (and probably nothing we'd ever see in practice).
This commit is contained in:
Arne Welzel 2023-03-27 12:50:52 +02:00
parent 9f8eb682b1
commit ea80f21e1d
3 changed files with 71 additions and 1 deletions

View file

@ -5,8 +5,10 @@
#include "zeek/zeek-config.h" #include "zeek/zeek-config.h"
#include <algorithm> #include <algorithm>
#include <limits>
#include "zeek/Desc.h" #include "zeek/Desc.h"
#include "zeek/Reporter.h"
using std::min; using std::min;
@ -322,6 +324,17 @@ void Reassembler::CheckOverlap(const DataBlockList& list, uint64_t seq, uint64_t
void Reassembler::NewBlock(double t, uint64_t seq, uint64_t len, const u_char* data) void Reassembler::NewBlock(double t, uint64_t seq, uint64_t len, const u_char* data)
{ {
// Check for overflows - this should be handled by the caller
// and possibly reported as a weird or violation if applicable.
if ( std::numeric_limits<uint64_t>::max() - seq < len )
{
zeek::reporter->InternalWarning("Reassembler::NewBlock() truncating block at seq %" PRIx64
" from length %" PRIu64 " to %" PRIu64,
seq, len, std::numeric_limits<uint64_t>::max() - seq);
len = std::numeric_limits<uint64_t>::max() - seq;
}
if ( len == 0 ) if ( len == 0 )
return; return;

View file

@ -371,6 +371,7 @@ void TCP_Reassembler::BlockInserted(DataBlockMap::const_iterator it)
{ {
const auto& start_block = it->second; const auto& start_block = it->second;
assert(start_block.seq < start_block.upper);
if ( start_block.seq > last_reassem_seq || start_block.upper <= last_reassem_seq ) if ( start_block.seq > last_reassem_seq || start_block.upper <= last_reassem_seq )
return; return;

View file

@ -2,6 +2,7 @@
#include "zeek/file_analysis/FileReassembler.h" #include "zeek/file_analysis/FileReassembler.h"
#include "zeek/3rdparty/doctest.h"
#include "zeek/file_analysis/File.h" #include "zeek/file_analysis/File.h"
namespace zeek::file_analysis namespace zeek::file_analysis
@ -46,7 +47,7 @@ uint64_t FileReassembler::FlushTo(uint64_t sequence)
void FileReassembler::BlockInserted(DataBlockMap::const_iterator it) void FileReassembler::BlockInserted(DataBlockMap::const_iterator it)
{ {
const auto& start_block = it->second; const auto& start_block = it->second;
assert(start_block.seq < start_block.upper);
if ( start_block.seq > last_reassem_seq || start_block.upper <= last_reassem_seq ) if ( start_block.seq > last_reassem_seq || start_block.upper <= last_reassem_seq )
return; return;
@ -113,3 +114,58 @@ void FileReassembler::Overlap(const u_char* b1, const u_char* b2, uint64_t n)
// Not doing anything here yet. // Not doing anything here yet.
} }
} // end file_analysis } // end file_analysis
// Test reassembler logic through FileReassembler.
TEST_CASE("file reassembler")
{
// Can not construct due to protected constructor.
class TestFile : public zeek::file_analysis::File
{
public:
TestFile(const std::string& file_id, const std::string& source_name)
: zeek::file_analysis::File(file_id, source_name)
{
}
};
auto f = std::make_unique<TestFile>("test_file_id", "test_source_name");
auto r = std::make_unique<zeek::file_analysis::FileReassembler>(f.get(), 0);
const u_char* data = (u_char*)("0123456789ABCDEF");
SUBCASE("block overlap and 64bit overflow")
{
r->NewBlock(0.0, 0xfffffffffffffff7, 3, data);
r->NewBlock(0.0, 0xfffffffffffffff7, 15, data);
r->NewBlock(0.0, 0xfffffffffffffff3, 15, data);
// 0xfffffffffffffff3 through 0xffffffffffffffff
CHECK_EQ(r->TotalSize(), 12);
// This previously hung with an endless loop.
r->Flush();
CHECK_FALSE(r->HasBlocks());
CHECK_EQ(r->TotalSize(), 0);
}
SUBCASE("reject NewBlock() at 64 bit limit")
{
r->NewBlock(0.0, 0xffffffffffffffff, 4, data);
CHECK_FALSE(r->HasBlocks());
CHECK_EQ(r->TotalSize(), 0);
}
SUBCASE("truncate NewBlock() to upper 64 bit limit")
{
r->NewBlock(0.0, 0xfffffffffffffffa, 8, data);
CHECK(r->HasBlocks());
CHECK_EQ(r->TotalSize(), 5);
}
SUBCASE("no truncation")
{
r->NewBlock(0.0, 0xfffffffffffffff7, 8, data);
CHECK(r->HasBlocks());
CHECK_EQ(r->TotalSize(), 8);
}
}