Skip to content

Commit a07e6f6

Browse files
sfc-gh-jkewjihuayu
authored andcommitted
Fix CouponList deserialization count validation bug
1 parent 7de007f commit a07e6f6

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

hll/include/CouponList-internal.hpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ CouponList<A>* CouponList<A>::newList(const void* bytes, size_t len, const A& al
9999
const bool emptyFlag = ((data[hll_constants::FLAGS_BYTE] & hll_constants::EMPTY_FLAG_MASK) ? true : false);
100100

101101
const uint32_t couponCount = data[hll_constants::LIST_COUNT_BYTE];
102+
// Reject LIST counts larger than the fixed LIST capacity.
103+
const uint32_t listCapacity = 1u << hll_constants::LG_INIT_LIST_SIZE;
104+
if (couponCount > listCapacity) {
105+
throw std::invalid_argument("Attempt to deserialize invalid CouponList with couponCount > capacity. Found couponCount: "
106+
+ std::to_string(couponCount)
107+
+ ", capacity: " + std::to_string(listCapacity));
108+
}
102109
const uint32_t couponsInArray = (compact ? couponCount : (1 << HllUtil<A>::computeLgArrInts(LIST, couponCount, lgK)));
103110
const size_t expectedLength = hll_constants::LIST_INT_ARR_START + (couponsInArray * sizeof(uint32_t));
104111
if (len < expectedLength) {
@@ -146,11 +153,19 @@ CouponList<A>* CouponList<A>::newList(std::istream& is, const A& allocator) {
146153
const bool oooFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::OUT_OF_ORDER_FLAG_MASK) ? true : false);
147154
const bool emptyFlag = ((listHeader[hll_constants::FLAGS_BYTE] & hll_constants::EMPTY_FLAG_MASK) ? true : false);
148155

156+
const uint32_t couponCount = listHeader[hll_constants::LIST_COUNT_BYTE];
157+
// Reject LIST counts larger than the fixed LIST capacity.
158+
const uint32_t listCapacity = 1u << hll_constants::LG_INIT_LIST_SIZE;
159+
if (couponCount > listCapacity) {
160+
throw std::invalid_argument("Attempt to deserialize invalid CouponList with couponCount > capacity. Found couponCount: "
161+
+ std::to_string(couponCount)
162+
+ ", capacity: " + std::to_string(listCapacity));
163+
}
164+
149165
ClAlloc cla(allocator);
150166
CouponList<A>* sketch = new (cla.allocate(1)) CouponList<A>(lgK, tgtHllType, mode, allocator);
151167
using coupon_list_ptr = std::unique_ptr<CouponList<A>, std::function<void(HllSketchImpl<A>*)>>;
152168
coupon_list_ptr ptr(sketch, sketch->get_deleter());
153-
const uint32_t couponCount = listHeader[hll_constants::LIST_COUNT_BYTE];
154169
sketch->couponCount_ = couponCount;
155170
sketch->putOutOfOrderFlag(oooFlag); // should always be false for LIST
156171

hll/test/CouponListTest.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <string>
2525
#include <exception>
2626
#include <stdexcept>
27+
#include <vector>
2728

2829
#include "hll.hpp"
2930
#include "CouponList.hpp"
@@ -180,4 +181,63 @@ TEST_CASE("coupon list: check corrupt stream data", "[coupon_list]") {
180181
REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
181182
}
182183

184+
TEST_CASE("coupon list: rejects malformed coupon count in bytes", "[coupon_list]") {
185+
// Reject declared LIST counts beyond the fixed LIST capacity.
186+
uint8_t lgK = 8;
187+
hll_sketch sk1(lgK);
188+
sk1.update(1);
189+
sk1.update(2);
190+
auto sketchBytes = sk1.serialize_compact();
191+
192+
const uint8_t malformedCount = 10;
193+
// Keep the input long enough to validate the declared LIST count.
194+
const size_t requiredLen = hll_constants::LIST_INT_ARR_START
195+
+ malformedCount * sizeof(uint32_t);
196+
if (sketchBytes.size() < requiredLen) {
197+
sketchBytes.resize(requiredLen, 0);
198+
}
199+
sketchBytes[hll_constants::LIST_COUNT_BYTE] = malformedCount;
200+
201+
REQUIRE_THROWS_AS(
202+
hll_sketch::deserialize(sketchBytes.data(), sketchBytes.size()),
203+
std::invalid_argument);
204+
REQUIRE_THROWS_AS(
205+
CouponList<std::allocator<uint8_t>>::newList(
206+
sketchBytes.data(), sketchBytes.size(), std::allocator<uint8_t>()),
207+
std::invalid_argument);
208+
}
209+
210+
TEST_CASE("coupon list: rejects malformed coupon count in stream", "[coupon_list]") {
211+
uint8_t lgK = 8;
212+
hll_sketch sk1(lgK);
213+
sk1.update(1);
214+
sk1.update(2);
215+
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
216+
sk1.serialize_compact(ss);
217+
218+
const uint8_t malformedCount = 10;
219+
const size_t requiredLen = hll_constants::LIST_INT_ARR_START
220+
+ malformedCount * sizeof(uint32_t);
221+
// Keep the stream long enough to validate the declared LIST count.
222+
ss.seekp(0, std::ios::end);
223+
const auto endPos = static_cast<size_t>(ss.tellp());
224+
if (endPos < requiredLen) {
225+
std::vector<char> pad(requiredLen - endPos, 0);
226+
ss.write(pad.data(), pad.size());
227+
}
228+
229+
ss.seekp(hll_constants::LIST_COUNT_BYTE);
230+
ss.put(static_cast<char>(malformedCount));
231+
232+
ss.clear();
233+
ss.seekg(0);
234+
REQUIRE_THROWS_AS(hll_sketch::deserialize(ss), std::invalid_argument);
235+
236+
ss.clear();
237+
ss.seekg(0);
238+
REQUIRE_THROWS_AS(
239+
CouponList<std::allocator<uint8_t>>::newList(ss, std::allocator<uint8_t>()),
240+
std::invalid_argument);
241+
}
242+
183243
} /* namespace datasketches */

0 commit comments

Comments
 (0)