Skip to content

Commit 75934fa

Browse files
authored
Merge pull request #503 from jihuayu/codex/couponlist-deserialize-bugfix
Fix CouponList deserialization count validation bug
2 parents 7de007f + 805eaf3 commit 75934fa

2 files changed

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

0 commit comments

Comments
 (0)