Prevent TLVWrite from possibly performing buffer overrun if trying to reserve (#30714)
* Prevent TLVWrite from possibly performing buffer overrun when calling reserve
* Quick cleanup
* Address PR Comments
* Address CI comments
* Address CI comments
* Small fix
* Address PR Comment
* Missing file save
* Fix CI
diff --git a/src/lib/core/TLVBackingStore.h b/src/lib/core/TLVBackingStore.h
index 6d3c989..e7712a0 100644
--- a/src/lib/core/TLVBackingStore.h
+++ b/src/lib/core/TLVBackingStore.h
@@ -155,6 +155,16 @@
*
*/
virtual CHIP_ERROR FinalizeBuffer(TLVWriter & writer, uint8_t * bufStart, uint32_t bufLen) = 0;
+
+ /**
+ * Returns whether call to GetNewBuffer will always fail.
+ *
+ * There are some implementations of TLVBackingStore that provide some level of utility, such as access to pool
+ * of particular kind of buffer and/or reserving space for headers. Some implementation allow the caller to
+ * specify that they only intend to use a single buffer. It is useful for TLVWriter to know if this is the case.
+ *
+ */
+ virtual bool GetNewBufferWillAlwaysFail() { return false; }
};
} // namespace TLV
diff --git a/src/lib/core/TLVWriter.cpp b/src/lib/core/TLVWriter.cpp
index 1ac6433..b20cc70 100644
--- a/src/lib/core/TLVWriter.cpp
+++ b/src/lib/core/TLVWriter.cpp
@@ -128,6 +128,20 @@
return err;
}
+CHIP_ERROR TLVWriter::ReserveBuffer(uint32_t aBufferSize)
+{
+ VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrReturnError(mRemainingLen >= aBufferSize, CHIP_ERROR_NO_MEMORY);
+
+ if (mBackingStore)
+ {
+ VerifyOrReturnError(mBackingStore->GetNewBufferWillAlwaysFail(), CHIP_ERROR_INCORRECT_STATE);
+ }
+ mReservedSize += aBufferSize;
+ mRemainingLen -= aBufferSize;
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR TLVWriter::PutBoolean(Tag tag, bool v)
{
return WriteElementHead((v) ? TLVElementType::BooleanTrue : TLVElementType::BooleanFalse, tag, 0);
diff --git a/src/lib/core/TLVWriter.h b/src/lib/core/TLVWriter.h
index eb38d5a..39e23a8 100644
--- a/src/lib/core/TLVWriter.h
+++ b/src/lib/core/TLVWriter.h
@@ -144,15 +144,12 @@
* @retval #CHIP_NO_ERROR Successfully reserved required buffer size.
* @retval #CHIP_ERROR_INCORRECT_STATE If the TLVWriter was not initialized.
* @retval #CHIP_ERROR_NO_MEMORY The reserved buffer size cannot fits into the remaining buffer size.
+ * @retval #CHIP_ERROR_INCORRECT_STATE
+ * Uses TLVBackingStore and is in a state where it might allocate
+ * additional non-contigious memory, thus making it difficult/impossible
+ * to properly reserve space.
*/
- CHIP_ERROR ReserveBuffer(uint32_t aBufferSize)
- {
- VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
- VerifyOrReturnError(mRemainingLen >= aBufferSize, CHIP_ERROR_NO_MEMORY);
- mReservedSize += aBufferSize;
- mRemainingLen -= aBufferSize;
- return CHIP_NO_ERROR;
- }
+ CHIP_ERROR ReserveBuffer(uint32_t aBufferSize);
/**
* Release previously reserved buffer.
diff --git a/src/system/TLVPacketBufferBackingStore.h b/src/system/TLVPacketBufferBackingStore.h
index 81afe97..c39b871 100644
--- a/src/system/TLVPacketBufferBackingStore.h
+++ b/src/system/TLVPacketBufferBackingStore.h
@@ -80,6 +80,12 @@
CHIP_ERROR OnInit(chip::TLV::TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
CHIP_ERROR GetNewBuffer(chip::TLV::TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
CHIP_ERROR FinalizeBuffer(chip::TLV::TLVWriter & writer, uint8_t * bufStart, uint32_t bufLen) override;
+ virtual bool GetNewBufferWillAlwaysFail() override
+ {
+ // For non-chained buffers, caller is given one chunk of contiguous memory. All calls to
+ // GetNewBuffer will fail with CHIP_ERROR_NO_MEMORY.
+ return !mUseChainedBuffers;
+ }
protected:
chip::System::PacketBufferHandle mHeadBuffer;
diff --git a/src/system/tests/TestTLVPacketBufferBackingStore.cpp b/src/system/tests/TestTLVPacketBufferBackingStore.cpp
index fccced4..e46906d 100644
--- a/src/system/tests/TestTLVPacketBufferBackingStore.cpp
+++ b/src/system/tests/TestTLVPacketBufferBackingStore.cpp
@@ -35,6 +35,16 @@
namespace {
+void WriteUntilRemainingLessThan(nlTestSuite * inSuite, PacketBufferTLVWriter & writer, const uint32_t remainingSize)
+{
+ uint32_t lengthRemaining = writer.GetRemainingFreeLength();
+ while (lengthRemaining >= remainingSize)
+ {
+ NL_TEST_ASSERT(inSuite, writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7)) == CHIP_NO_ERROR);
+ lengthRemaining = writer.GetRemainingFreeLength();
+ }
+}
+
class TLVPacketBufferBackingStoreTest
{
public:
@@ -43,6 +53,9 @@
static void BasicEncodeDecode(nlTestSuite * inSuite, void * inContext);
static void MultiBufferEncode(nlTestSuite * inSuite, void * inContext);
+ static void NonChainedBufferCanReserve(nlTestSuite * inSuite, void * inContext);
+ static void TestWriterReserveUnreserveDoesNotOverflow(nlTestSuite * inSuite, void * inContext);
+ static void TestWriterReserve(nlTestSuite * inSuite, void * inContext);
};
int TLVPacketBufferBackingStoreTest::TestSetup(void * inContext)
@@ -238,14 +251,111 @@
NL_TEST_ASSERT(inSuite, error == CHIP_END_OF_TLV);
}
+void TLVPacketBufferBackingStoreTest::NonChainedBufferCanReserve(nlTestSuite * inSuite, void * inContext)
+{
+ // Start with a too-small buffer.
+ uint32_t smallSize = 5;
+ uint32_t smallerSizeToReserver = smallSize - 1;
+
+ auto buffer = PacketBufferHandle::New(smallSize, /* aReservedSize = */ 0);
+
+ PacketBufferTLVWriter writer;
+ writer.Init(std::move(buffer), /* useChainedBuffers = */ false);
+
+ CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+}
+
+// This test previously was created to show that there was an overflow bug, now this test mainly
+// just checks that you cannot reserve this type of TLVBackingStorage buffer.
+void TLVPacketBufferBackingStoreTest::TestWriterReserveUnreserveDoesNotOverflow(nlTestSuite * inSuite, void * inContext)
+{
+ // Start with a too-small buffer.
+ uint32_t smallSize = 100;
+ uint32_t smallerSizeToReserver = smallSize - 1;
+
+ auto buffer = PacketBufferHandle::New(smallSize, 0);
+
+ PacketBufferTLVWriter writer;
+ writer.Init(std::move(buffer), /* useChainedBuffers = */ true);
+
+ CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
+ if (error == CHIP_NO_ERROR)
+ {
+ uint32_t lengthRemaining = writer.GetRemainingFreeLength();
+ NL_TEST_ASSERT(inSuite, lengthRemaining == 1);
+ // Lets try to overflow by getting next buffer in the chain,
+ // unreserving then writing until the end of the current buffer.
+ error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+
+ lengthRemaining = writer.GetRemainingFreeLength();
+ NL_TEST_ASSERT(inSuite, lengthRemaining > smallerSizeToReserver);
+
+ WriteUntilRemainingLessThan(inSuite, writer, 2);
+
+ lengthRemaining = writer.GetRemainingFreeLength();
+ NL_TEST_ASSERT(inSuite, lengthRemaining != 0);
+ NL_TEST_ASSERT(inSuite, lengthRemaining < smallerSizeToReserver);
+
+ error = writer.UnreserveBuffer(smallerSizeToReserver);
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+
+ lengthRemaining = writer.GetRemainingFreeLength();
+ NL_TEST_ASSERT(inSuite, lengthRemaining > smallerSizeToReserver);
+
+ // This is where we get overflow.
+ WriteUntilRemainingLessThan(inSuite, writer, 2);
+
+ // If we get here then the overflow condition we were expecting did not happen. If that is the case,
+ // either we have fixed reservation for chained buffers, or expected failure didn't hit on this
+ // platform.
+ //
+ // If there is a fix please add reservation for chained buffers, please make sure you account for
+ // what happens if TLVWriter::WriteData fails to get a new buffer but we are not at max size, do
+ // you actually have space for what was supposed to be reserved.
+ NL_TEST_ASSERT(inSuite, false);
+ }
+
+ // We no longer allow non-contigous buffers to be reserved.
+ NL_TEST_ASSERT(inSuite, error == CHIP_ERROR_INCORRECT_STATE);
+}
+
+void TLVPacketBufferBackingStoreTest::TestWriterReserve(nlTestSuite * inSuite, void * inContext)
+{
+ // Start with a too-small buffer.
+ uint32_t smallSize = 5;
+ uint32_t smallerSizeToReserver = smallSize - 1;
+
+ auto buffer = PacketBufferHandle::New(smallSize, 0);
+
+ PacketBufferTLVWriter writer;
+ writer.Init(std::move(buffer), /* useChainedBuffers = */ false);
+
+ CHIP_ERROR error = writer.ReserveBuffer(smallerSizeToReserver);
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+
+ error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
+ NL_TEST_ASSERT(inSuite, error == CHIP_ERROR_NO_MEMORY);
+
+ error = writer.UnreserveBuffer(smallerSizeToReserver);
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+
+ error = writer.Put(TLV::AnonymousTag(), static_cast<uint8_t>(7));
+ NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
+}
+
/**
* Test Suite. It lists all the test functions.
*/
// clang-format off
const nlTest sTests[] =
{
- NL_TEST_DEF("BasicEncodeDecode", TLVPacketBufferBackingStoreTest::BasicEncodeDecode),
- NL_TEST_DEF("MultiBufferEncode", TLVPacketBufferBackingStoreTest::MultiBufferEncode),
+ NL_TEST_DEF("BasicEncodeDecode", TLVPacketBufferBackingStoreTest::BasicEncodeDecode),
+ NL_TEST_DEF("MultiBufferEncode", TLVPacketBufferBackingStoreTest::MultiBufferEncode),
+ NL_TEST_DEF("NonChainedBufferCanReserve", TLVPacketBufferBackingStoreTest::NonChainedBufferCanReserve),
+ NL_TEST_DEF("TestWriterReserveUnreserveDoesNotOverflow", TLVPacketBufferBackingStoreTest::TestWriterReserveUnreserveDoesNotOverflow),
+ NL_TEST_DEF("TestWriterReserve", TLVPacketBufferBackingStoreTest::TestWriterReserve),
NL_TEST_SENTINEL()
};