Skip to content

Commit 2c3d447

Browse files
guard iterator against end in stencilcount (#2623)
* guard iterator against end in stencilcount * handle truncated stencilcount keys * test stencilcount input boundaries * make sized hash decoding bounds safe * limit bounds checks to stencilcount * guard CBOR object key hashing --------- Co-authored-by: Stephen Berry <stephenberry.developer@gmail.com>
1 parent 1ce36b4 commit 2c3d447

4 files changed

Lines changed: 115 additions & 10 deletions

File tree

include/glaze/cbor/read.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,9 @@ namespace glz
13951395
if constexpr (N > 0) {
13961396
static constexpr auto HashInfo = hash_info<T>;
13971397

1398-
const auto index = decode_hash_with_size<CBOR, T, HashInfo, HashInfo.type>::op(it, end, key_len);
1398+
const auto index = key_len < HashInfo.min_length || key_len > HashInfo.max_length
1399+
? N
1400+
: decode_hash_with_size<CBOR, T, HashInfo, HashInfo.type>::op(it, end, key_len);
13991401

14001402
if (index < N) [[likely]] {
14011403
const sv key{reinterpret_cast<const char*>(it), static_cast<size_t>(key_len)};

include/glaze/stencil/stencilcount.hpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace glz
2727
}
2828
if (not bool(ctx.error)) [[likely]] {
2929
auto skip_whitespace = [&] {
30-
while (whitespace_table[uint8_t(*it)]) {
30+
while (it < end && whitespace_table[uint8_t(*it)]) {
3131
++it;
3232
}
3333
};
@@ -40,12 +40,12 @@ namespace glz
4040
switch (*it) {
4141
case '{': {
4242
++it;
43-
if (*it == '{') {
43+
if (it != end && *it == '{') {
4444
++it;
4545
skip_whitespace();
4646

4747
uint64_t count{};
48-
while (*it == '+') {
48+
while (it != end && *it == '+') {
4949
++it;
5050
++count;
5151
}
@@ -76,9 +76,9 @@ namespace glz
7676
prev_count = count;
7777
}
7878

79-
if (*it == '}') {
79+
if (it != end && *it == '}') {
8080
++it;
81-
if (*it == '}') {
81+
if (it != end && *it == '}') {
8282
++it;
8383
break;
8484
}
@@ -93,15 +93,23 @@ namespace glz
9393
++it;
9494
}
9595

96+
if (it == end) {
97+
break;
98+
}
99+
96100
const sv key{start, size_t(it - start)};
97101

98102
skip_whitespace();
99103

100104
static constexpr auto N = reflect<T>::size;
101105
static constexpr auto HashInfo = hash_info<T>;
102106

103-
const auto index =
104-
decode_hash_with_size<STENCIL, T, HashInfo, HashInfo.type>::op(start, end, key.size());
107+
const auto index = [&] {
108+
if (key.size() < HashInfo.min_length || key.size() > HashInfo.max_length) {
109+
return N;
110+
}
111+
return decode_hash_with_size<STENCIL, T, HashInfo, HashInfo.type>::op(start, end, key.size());
112+
}();
105113

106114
if (index < N) [[likely]] {
107115
std::string temp{};
@@ -137,9 +145,9 @@ namespace glz
137145

138146
skip_whitespace();
139147

140-
if (*it == '}') {
148+
if (it != end && *it == '}') {
141149
++it;
142-
if (*it == '}') {
150+
if (it != end && *it == '}') {
143151
++it;
144152
break;
145153
}

tests/cbor_test/cbor_test.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ struct my_struct
2929
std::array<uint64_t, 3> arr = {1, 2, 3};
3030
};
3131

32+
struct cbor_mod4_object
33+
{
34+
int x{};
35+
int y{};
36+
int z{};
37+
};
38+
39+
struct cbor_front_hash_object
40+
{
41+
int aaaa{};
42+
int aaab{};
43+
int aaba{};
44+
int aabb{};
45+
int abaa{};
46+
};
47+
3248
template <>
3349
struct glz::meta<my_struct>
3450
{
@@ -2258,6 +2274,26 @@ void past_fuzzing_issues()
22582274

22592275
void error_tests()
22602276
{
2277+
const auto read_exact = []<class T, size_t N>(const std::array<uint8_t, N>& input) {
2278+
auto buffer = std::make_unique_for_overwrite<char[]>(N);
2279+
std::copy_n(reinterpret_cast<const char*>(input.data()), N, buffer.get());
2280+
T value{};
2281+
return glz::read_cbor(value, std::string_view{buffer.get(), N});
2282+
};
2283+
2284+
"empty object key stays within the buffer"_test = [=] {
2285+
static_assert(glz::hash_info<cbor_mod4_object>.type == glz::hash_type::mod4);
2286+
constexpr std::array<uint8_t, 3> input{0xa1, 0x60, 0x00};
2287+
expect(read_exact.template operator()<cbor_mod4_object>(input).ec == glz::error_code::unknown_key);
2288+
};
2289+
2290+
"short front hash key stays within the buffer"_test = [=] {
2291+
static_assert(glz::hash_info<cbor_front_hash_object>.type == glz::hash_type::front_hash);
2292+
static_assert(glz::hash_info<cbor_front_hash_object>.front_hash_bytes == 4);
2293+
constexpr std::array<uint8_t, 4> input{0xa1, 0x61, 'a', 0x00};
2294+
expect(read_exact.template operator()<cbor_front_hash_object>(input).ec == glz::error_code::unknown_key);
2295+
};
2296+
22612297
"truncated_input"_test = [] {
22622298
std::string buffer;
22632299
buffer.push_back(static_cast<char>(glz::cbor::initial_byte(glz::cbor::major::uint, 24)));

tests/stencil/stencil_test.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,22 @@ suite mustache_list_template_tests = [] {
663663

664664
#include "glaze/stencil/stencilcount.hpp"
665665

666+
struct stencilcount_point
667+
{
668+
int x{};
669+
int y{};
670+
int z{};
671+
};
672+
673+
struct stencilcount_front_hash
674+
{
675+
int aaaa{};
676+
int aaab{};
677+
int aaba{};
678+
int aabb{};
679+
int abaa{};
680+
};
681+
666682
suite stencilcount_tests = [] {
667683
"basic docstencil"_test = [] {
668684
std::string_view layout = R"(# About
@@ -705,6 +721,49 @@ suite stencilcount_tests = [] {
705721
3.1.2 English
706722
)") << result;
707723
};
724+
725+
"truncated tags stay within the buffer"_test = [] {
726+
static_assert(glz::hash_info<stencilcount_point>.type == glz::hash_type::mod4);
727+
728+
constexpr std::array cases{
729+
std::pair<std::string_view, std::string_view>{"{{x}}", "42"},
730+
std::pair<std::string_view, std::string_view>{"{{ x }}", "42"},
731+
std::pair<std::string_view, std::string_view>{"{{\tx\t}}", "42"},
732+
std::pair<std::string_view, std::string_view>{"{{+++}}", "0.0.1"},
733+
std::pair<std::string_view, std::string_view>{"before {{x}} after", "before 42 after"},
734+
};
735+
736+
const auto render_prefix = []<class T>(const std::string_view layout, const size_t size, T& value) {
737+
auto buffer = std::make_unique_for_overwrite<char[]>(size);
738+
std::copy_n(layout.data(), size, buffer.get());
739+
return glz::stencilcount(std::string_view{buffer.get(), size}, value);
740+
};
741+
742+
stencilcount_point point{.x = 42};
743+
for (const auto& [layout, expected] : cases) {
744+
for (size_t size = 1; size <= layout.size(); ++size) {
745+
auto result = render_prefix(layout, size, point);
746+
expect(result.has_value()) << layout << " prefix size " << size;
747+
if (size == layout.size()) {
748+
expect(result.value_or("") == expected) << layout;
749+
}
750+
}
751+
}
752+
753+
static_assert(glz::hash_info<stencilcount_front_hash>.type == glz::hash_type::front_hash);
754+
static_assert(glz::hash_info<stencilcount_front_hash>.front_hash_bytes == 4);
755+
756+
stencilcount_front_hash front_hash_value{.aaaa = 42};
757+
for (const std::string_view layout : {"{{a}", "{{a}}", "{{a }", "{{aaaa}}"}) {
758+
for (size_t size = 1; size <= layout.size(); ++size) {
759+
auto result = render_prefix(layout, size, front_hash_value);
760+
expect(result.has_value()) << layout << " prefix size " << size;
761+
if (size == layout.size() && layout == "{{aaaa}}") {
762+
expect(result.value_or("") == "42");
763+
}
764+
}
765+
}
766+
};
708767
};
709768

710769
int main() { return 0; }

0 commit comments

Comments
 (0)