Skip to content

Commit c10bc43

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

2 files changed

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

0 commit comments

Comments
 (0)