bej_decoder: Add a limit on number of operations
This is to prevent the decoder from taking on a huge number of
operations from a single input, that could cause the main while loop for
bejDecode to run for a long time.
Tested: Unit test passes
Change-Id: Idb68b706d3634d1874d1814f17420076672d21ee
Signed-off-by: Brandon Kim <brandonkim@google.com>
diff --git a/src/bej_decoder_core.c b/src/bej_decoder_core.c
index a9bb02c..b500fcf 100644
--- a/src/bej_decoder_core.c
+++ b/src/bej_decoder_core.c
@@ -719,8 +719,16 @@
.stackDataPtr = stackDataPtr,
};
+ uint64_t maxOperations = 1000000;
+ uint64_t operationCount = 0;
+
while (params.state.encodedStreamOffset < streamLen)
{
+ if (++operationCount > maxOperations)
+ {
+ fprintf(stderr, "BEJ decoding exceeded max operations\n");
+ return bejErrorNotSuppoted;
+ }
// Go to the next encoded segment in the encoded stream.
params.state.encodedSubStream =
enStream + params.state.encodedStreamOffset;
diff --git a/test/bej_decoder_test.cpp b/test/bej_decoder_test.cpp
index 55b4358..b229043 100644
--- a/test/bej_decoder_test.cpp
+++ b/test/bej_decoder_test.cpp
@@ -1,8 +1,10 @@
#include "bej_common_test.hpp"
#include "bej_decoder_json.hpp"
+#include "bej_encoder_json.hpp"
#include <memory>
#include <string_view>
+#include <vector>
#include <gmock/gmock-matchers.h>
#include <gmock/gmock.h>
@@ -80,6 +82,65 @@
EXPECT_TRUE(jsonDecoded.dump() == inputsOrErr->expectedJson.dump());
}
+TEST(BejDecoderSecurityTest, MaxOperationsLimit)
+{
+ auto inputsOrErr = loadInputs(dummySimpleTestFiles);
+ ASSERT_TRUE(inputsOrErr);
+
+ BejDictionaries dictionaries = {
+ .schemaDictionary = inputsOrErr->schemaDictionary,
+ .annotationDictionary = inputsOrErr->annotationDictionary,
+ .errorDictionary = inputsOrErr->errorDictionary,
+ };
+
+ // Each array element below consists of a set and two properties, resulting
+ // in 3 operations. 400,000 elements will result in 1,200,000 operations,
+ // which should exceed the limit of 1,000,000.
+ constexpr int numElements = 400000;
+
+ auto root = std::make_unique<RedfishPropertyParent>();
+ bejTreeInitSet(root.get(), "DummySimple");
+
+ auto childArray = std::make_unique<RedfishPropertyParent>();
+ bejTreeInitArray(childArray.get(), "ChildArrayProperty");
+ bejTreeLinkChildToParent(root.get(), childArray.get());
+
+ std::vector<std::unique_ptr<RedfishPropertyParent>> sets;
+ std::vector<std::unique_ptr<RedfishPropertyLeafBool>> bools;
+ std::vector<std::unique_ptr<RedfishPropertyLeafEnum>> enums;
+ sets.reserve(numElements);
+ bools.reserve(numElements);
+ enums.reserve(numElements);
+
+ for (int i = 0; i < numElements; ++i)
+ {
+ auto chArraySet = std::make_unique<RedfishPropertyParent>();
+ bejTreeInitSet(chArraySet.get(), nullptr);
+
+ auto chArraySetBool = std::make_unique<RedfishPropertyLeafBool>();
+ bejTreeAddBool(chArraySet.get(), chArraySetBool.get(), "AnotherBoolean",
+ true);
+
+ auto chArraySetLs = std::make_unique<RedfishPropertyLeafEnum>();
+ bejTreeAddEnum(chArraySet.get(), chArraySetLs.get(), "LinkStatus",
+ "NoLink");
+
+ bejTreeLinkChildToParent(childArray.get(), chArraySet.get());
+
+ sets.push_back(std::move(chArraySet));
+ bools.push_back(std::move(chArraySetBool));
+ enums.push_back(std::move(chArraySetLs));
+ }
+
+ libbej::BejEncoderJson encoder;
+ encoder.encode(&dictionaries, bejMajorSchemaClass, root.get());
+ std::vector<uint8_t> outputBuffer = encoder.getOutput();
+
+ BejDecoderJson decoder;
+ EXPECT_THAT(decoder.decode(dictionaries, std::span(outputBuffer)),
+ bejErrorNotSuppoted);
+}
+
/**
* TODO: Add more test cases.
* - Test Enums inside array elemets