Skip to content

Commit

Permalink
HPCC-32765 Report the compressed offset when reporting an error about…
Browse files Browse the repository at this point in the history
… an invalid compressed file

Signed-off-by: Gavin Halliday <[email protected]>
  • Loading branch information
ghalliday committed Oct 4, 2024
1 parent 0d5a839 commit 5dfa8a1
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 29 deletions.
2 changes: 2 additions & 0 deletions system/jlib/jfcmp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class jlib_decl CFcmpExpander : public CExpanderBase
size32_t outlen;
size32_t bufalloc;
const size32_t *in = nullptr;
const void * original = nullptr;

public:
CFcmpExpander()
Expand All @@ -247,6 +248,7 @@ class jlib_decl CFcmpExpander : public CExpanderBase

virtual size32_t init(const void *blk)
{
original = blk;
const size32_t *expsz = (const size32_t *)blk;
outlen = *expsz;
in = (expsz+1);
Expand Down
12 changes: 11 additions & 1 deletion system/jlib/jlz4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ class jlib_decl CLZ4Expander : public CFcmpExpander
size32_t written;
if (szchunk+totalExpanded<outlen)
{
if (unlikely(szchunk == 0))
{
//Special case this corruption - otherwise it enters an infinite loop
VStringBuffer msg("Unexpected zero length block at block offset %u", (size32_t)((const byte *)in - (const byte *)original));
throwUnexpectedX(msg.str());
}

//All but the last block are compressed (see expand() function above).
//Slightly concerning there always has to be one trailing byte for this to work!
size32_t maxOut = target.capacity();
Expand All @@ -286,7 +293,10 @@ class jlib_decl CLZ4Expander : public CFcmpExpander

//Sanity check to catch corrupt lz4 data that always returns an error.
if (maxOut > outlen)
throwUnexpected();
{
VStringBuffer msg("Decompression expected max %u bytes, but now %u at block offset %u", outlen, maxOut, (size32_t)((const byte *)in - (const byte *)original));
throwUnexpectedX(msg.str());
}

maxOut += szchunk; // Likely to quickly approach the actual expanded size
target.clear();
Expand Down
65 changes: 38 additions & 27 deletions system/jlib/jlzw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2118,7 +2118,7 @@ class CCompressedFile : implements ICompressedFileIO, public CInterface
{
unsigned code = e->errorCode();
StringBuffer msg;
e->errorMessage(msg).appendf(" at position %llu of %llu", nextExpansionPos, trailer.indexPos);
e->errorMessage(msg).appendf(" at uncompressed position %llu block %u of %llu", nextExpansionPos, curblocknum, trailer.indexPos);
e->Release();
throw makeStringException(code, msg.str());
}
Expand Down Expand Up @@ -2157,37 +2157,48 @@ class CCompressedFile : implements ICompressedFileIO, public CInterface

void expand(const void *compbuf,MemoryBuffer &expbuf,size32_t expsize, offset_t compressedPos)
{
size32_t rs = trailer.recordSize;
if (rs) { // diff expand
const byte *src = (const byte *)compbuf;
byte *dst = (byte *)expbuf.reserve(expsize);
if (expsize) {
assertex(expsize>=rs);
memcpy(dst,src,rs);
dst += rs;
src += rs;
expsize -= rs;
while (expsize) {
try
{
size32_t rs = trailer.recordSize;
if (rs) { // diff expand
const byte *src = (const byte *)compbuf;
byte *dst = (byte *)expbuf.reserve(expsize);
if (expsize) {
assertex(expsize>=rs);
src += DiffExpand(src, dst, dst-rs, rs);
expsize -= rs;
memcpy(dst,src,rs);
dst += rs;
src += rs;
expsize -= rs;
while (expsize) {
assertex(expsize>=rs);
src += DiffExpand(src, dst, dst-rs, rs);
expsize -= rs;
dst += rs;
}
}
}
}
else { // lzw or fastlz or lz4
assertex(expander.get());
size32_t exp = expander->expandFirst(expbuf, compbuf);
if (exp == 0)
{
unsigned numZeros = countZeros(trailer.blockSize, (const byte *)compbuf);
if (numZeros >= 16)
throw makeStringExceptionV(-1, "Unexpected zero fill in compressed file at position %llu length %u", compressedPos, numZeros);
}
else { // lzw or fastlz or lz4
assertex(expander.get());
size32_t exp = expander->expandFirst(expbuf, compbuf);
if (exp == 0)
{
unsigned numZeros = countZeros(trailer.blockSize, (const byte *)compbuf);
if (numZeros >= 16)
throw makeStringExceptionV(-1, "Unexpected zero fill in compressed file at position %llu length %u", compressedPos, numZeros);
}

startBlockPos = curblockpos;
nextExpansionPos = startBlockPos + exp;
fullBlockSize = expsize;
startBlockPos = curblockpos;
nextExpansionPos = startBlockPos + exp;
fullBlockSize = expsize;
}
}
catch (IException * e)
{
unsigned code = e->errorCode();
StringBuffer msg;
e->errorMessage(msg).appendf(" at compressed position %llu of %llu", compressedPos, trailer.indexPos);
e->Release();
throw makeStringException(code, msg.str());
}
}

Expand Down
2 changes: 1 addition & 1 deletion system/jlib/jlzw.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ extern jlib_decl bool isCompressedFile(const char *filename);
extern jlib_decl bool isCompressedFile(IFile *file);
extern jlib_decl ICompressedFileIO *createCompressedFileReader(IFile *file,IExpander *expander=NULL, bool memorymapped=false, IFEflags extraFlags=IFEnone);
extern jlib_decl ICompressedFileIO *createCompressedFileReader(IFileIO *fileio,IExpander *expander=NULL);
extern jlib_decl ICompressedFileIO *createCompressedFileWriter(IFile *file,size32_t recordsize,bool append=false,bool setcrc=true,ICompressor *compressor=NULL, unsigned compMethod=COMPRESS_METHOD_LZ4, IFEflags extraFlags=IFEnone);
extern jlib_decl ICompressedFileIO *createCompressedFileWriter(IFileIO *fileio, bool append, size32_t recordsize,bool setcrc=true,ICompressor *compressor=NULL, unsigned compMethod=COMPRESS_METHOD_LZ4);
extern jlib_decl ICompressedFileIO *createCompressedFileWriter(IFile *file,size32_t recordsize,bool append=false,bool setcrc=true,ICompressor *compressor=NULL, unsigned compMethod=COMPRESS_METHOD_LZ4, IFEflags extraFlags=IFEnone);

#define COMPRESSEDFILECRC (~0U)

Expand Down
1 change: 1 addition & 0 deletions testing/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ set ( SRCS
txSummarytests.cpp
accessmaptests.cpp
fxpptests.cpp
filetests.cpp
${HPCC_SOURCE_DIR}/esp/bindings/SOAP/xpp/fxpp/FragmentedXmlPullParser.cpp
${HPCC_SOURCE_DIR}/esp/bindings/SOAP/xpp/fxpp/FragmentedXmlAssistant.cpp
${CMAKE_BINARY_DIR}/generated/ws_loggingservice_esp.cpp
Expand Down
185 changes: 185 additions & 0 deletions testing/unittests/filetests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*##############################################################################
HPCC SYSTEMS software Copyright (C) 2024 HPCC Systems®.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
############################################################################## */

/*
* File regression tests
*
*/

#ifdef _USE_CPPUNIT
#include <memory>
#include <chrono>
#include <algorithm>
#include "jsem.hpp"
#include "jfile.hpp"
#include "jdebug.hpp"
#include "jset.hpp"
#include "rmtfile.hpp"
#include "jlzw.hpp"
#include "jqueue.hpp"
#include "jregexp.hpp"
#include "jsecrets.hpp"
#include "jutil.hpp"
#include "junicode.hpp"

#include "opentelemetry/sdk/common/attribute_utils.h"
#include "opentelemetry/sdk/resource/resource.h"

#include "unittests.hpp"

#define CPPUNIT_ASSERT_EQUAL_STR(x, y) CPPUNIT_ASSERT_EQUAL(std::string(x ? x : ""),std::string(y ? y : ""))

class JlibFileTest : public CppUnit::TestFixture
{
public:
CPPUNIT_TEST_SUITE(JlibFileTest);
CPPUNIT_TEST(testCompressed);
CPPUNIT_TEST(cleanup);
CPPUNIT_TEST_SUITE_END();

static constexpr const char * testFilename = "unittests_compressfile";
void createCompressed()
{
Owned<IFile> file(createIFile(testFilename));
Owned<ICompressedFileIO> io(createCompressedFileWriter(file, 0, false, false, nullptr));

constexpr size_t cnt = 10000;
constexpr size_t size = 1000;
offset_t pos = 0;
for (unsigned i = 0; i < cnt; i++)
{
byte temp[size];

for (unsigned j = 0; j < size; j += 4)
{
temp[j] = (byte)j;
temp[j+1] = (byte)j+1;
temp[j+2] = (byte)j+2;
temp[j+3] = (byte)random();
}

io->write(pos, size, temp);
pos += size;
}
}
void readCompressed(bool errorExpected)
{
bool success = false;
try
{
Owned<IFile> file(createIFile(testFilename));
Owned<ICompressedFileIO> io(createCompressedFileReader(file));

constexpr size_t cnt = 10000;
constexpr size_t size = 1000;
offset_t pos = 0;
for (unsigned i = 0; i < cnt; i++)
{
byte temp[size];

io->read(pos, size, temp);

for (unsigned j = 0; j < size; j += 4)
{
CPPUNIT_ASSERT_EQUAL(temp[j], (byte)j);
CPPUNIT_ASSERT_EQUAL(temp[j+1], (byte)(j+1));
}

pos += size;
}

success = true;
}
catch (IException *e)
{
if (errorExpected)
{
DBGLOG(e, "Expected error reading compressed file:");
}
else
{
StringBuffer msg("Unexpected error reading compressed file:");
e->errorMessage(msg);
CPPUNIT_FAIL(msg.str());
}
e->Release();
}
if (success && errorExpected)
CPPUNIT_FAIL("Expected error reading compressed file");
}
void read(offset_t offset, size32_t size, void * data)
{
Owned<IFile> file(createIFile(testFilename));
Owned<IFileIO> io(file->open(IFOread));
io->read(offset, size, data);
}
void write(offset_t offset, size32_t size, void * data)
{
Owned<IFile> file(createIFile(testFilename));
Owned<IFileIO> io(file->open(IFOwrite));
io->write(offset, size, data);
}
void testCompressed()
{
//patch the first block with zeros
constexpr byte zeros[0x100000] = { 0 };

createCompressed();
readCompressed(false);

write(0, sizeof(zeros), (void *)zeros);
readCompressed(true);

createCompressed();
write(0x10000, sizeof(zeros), (void *)zeros);
readCompressed(true);

createCompressed();
write(0x9000, sizeof(zeros), (void *)zeros);
readCompressed(true);

//Test the second block being corrupted with zeros
size32_t firstBlockSize = 0;
createCompressed();
read(4, sizeof(firstBlockSize), &firstBlockSize);
write(8+firstBlockSize, sizeof(zeros), (void *)zeros);
readCompressed(true);

//Test the data after the second block being corrupted with zeros
createCompressed();
read(4, sizeof(firstBlockSize), &firstBlockSize);
write(8+4+firstBlockSize, sizeof(zeros), (void *)zeros);
readCompressed(true);

//Test the second block being corrupted to an invalid size
size32_t newSize = 1;
createCompressed();
read(4, sizeof(firstBlockSize), &firstBlockSize);
write(8+firstBlockSize, sizeof(newSize), &newSize);
readCompressed(true);
}
void cleanup()
{
Owned<IFile> file(createIFile(testFilename));
file->remove();
}
};

CPPUNIT_TEST_SUITE_REGISTRATION( JlibFileTest );
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( JlibFileTest, "JlibFileTest" );

#endif

0 comments on commit 5dfa8a1

Please sign in to comment.