Skip to content

Commit d6aff23

Browse files
chore: add Value depth level guard (#15778)
* chore: add Value depth level guard * chore: add tests * chore: use absl substitute in new test * chore: deduplicate depth exceeded error * chore: move internal functions to anonymous namespace * chore: improve error, use ++depth * Apply suggestion from @diegomarquezp * chore: lint * chore: don't increase level in container values * chore: lint * chore: non-mutating increment; use max depth constant * chore: use constant in error message
1 parent bf93649 commit d6aff23

File tree

3 files changed

+134
-62
lines changed

3 files changed

+134
-62
lines changed

google/cloud/bigtable/value.cc

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ namespace bigtable {
3030
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3131
namespace {
3232

33+
auto constexpr kMaxValueDepth = 10;
34+
3335
// Some Bigtable proto fields use Cord internally and string externally.
3436
template <typename T, typename std::enable_if<
3537
std::is_same<T, std::string>::value>::type* = nullptr>
@@ -339,18 +341,32 @@ std::ostream& operator<<(std::ostream& os, Value const& v) {
339341
return StreamHelper(os, v.value_, v.type_, StreamMode::kScalar);
340342
}
341343

344+
Status CheckDepthExceeded(int const depth) {
345+
if (depth > kMaxValueDepth) {
346+
return internal::InternalError(
347+
absl::Substitute("Nested value depth exceeds $0 levels",
348+
kMaxValueDepth),
349+
GCP_ERROR_INFO());
350+
}
351+
return Status{};
352+
}
353+
342354
// NOLINTNEXTLINE(misc-no-recursion)
343-
Status Value::TypeAndArrayValuesMatch(
344-
google::bigtable::v2::Type const& type,
345-
google::bigtable::v2::Value const& value) {
355+
Status TypeAndArrayValuesMatch(google::bigtable::v2::Type const& type,
356+
google::bigtable::v2::Value const& value,
357+
int const depth) {
358+
auto depth_exceeded = CheckDepthExceeded(depth);
359+
if (!depth_exceeded.ok()) {
360+
return depth_exceeded;
361+
}
346362
if (!value.has_array_value()) {
347363
return internal::InternalError(
348364
"Value kind must be ARRAY_VALUE for columns of type: MAP");
349365
}
350366
auto const& vals = value.array_value().values();
351367
for (auto const& val : vals) {
352368
auto const element_match_result =
353-
TypeAndValuesMatch(type.array_type().element_type(), val);
369+
Value::TypeAndValuesMatch(type.array_type().element_type(), val, depth);
354370
if (!element_match_result.ok()) {
355371
return element_match_result;
356372
}
@@ -359,8 +375,13 @@ Status Value::TypeAndArrayValuesMatch(
359375
}
360376

361377
// NOLINTNEXTLINE(misc-no-recursion)
362-
Status Value::TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
363-
google::bigtable::v2::Value const& value) {
378+
Status TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
379+
google::bigtable::v2::Value const& value,
380+
int const depth) {
381+
auto depth_exceeded = CheckDepthExceeded(depth);
382+
if (!depth_exceeded.ok()) {
383+
return depth_exceeded;
384+
}
364385
if (!value.has_array_value()) {
365386
return internal::InternalError(
366387
"Value kind must be ARRAY_VALUE for columns of type: MAP");
@@ -376,12 +397,13 @@ Status Value::TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
376397
auto map_key = val.array_value().values(0);
377398
auto map_value = val.array_value().values(1);
378399
// NOLINTNEXTLINE(misc-no-recursion)
379-
auto key_match_result = TypeAndValuesMatch(key_type, map_key);
400+
auto key_match_result = Value::TypeAndValuesMatch(key_type, map_key, depth);
380401
if (!key_match_result.ok()) {
381402
return key_match_result;
382403
}
383404
// NOLINTNEXTLINE(misc-no-recursion)
384-
auto value_match_result = TypeAndValuesMatch(value_type, map_value);
405+
auto value_match_result =
406+
Value::TypeAndValuesMatch(value_type, map_value, depth);
385407
if (!value_match_result.ok()) {
386408
return value_match_result;
387409
}
@@ -390,9 +412,13 @@ Status Value::TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
390412
}
391413

392414
// NOLINTNEXTLINE(misc-no-recursion)
393-
Status Value::TypeAndStructValuesMatch(
394-
google::bigtable::v2::Type const& type,
395-
google::bigtable::v2::Value const& value) {
415+
Status TypeAndStructValuesMatch(google::bigtable::v2::Type const& type,
416+
google::bigtable::v2::Value const& value,
417+
int const depth) {
418+
auto depth_exceeded = CheckDepthExceeded(depth);
419+
if (!depth_exceeded.ok()) {
420+
return depth_exceeded;
421+
}
396422
if (!value.has_array_value()) {
397423
return internal::InternalError(
398424
"Value kind must be ARRAY_VALUE for columns of type: STRUCT");
@@ -408,7 +434,7 @@ Status Value::TypeAndStructValuesMatch(
408434
for (int i = 0; i < fields.size(); ++i) {
409435
auto const& f1 = fields.Get(i);
410436
auto const& v = values[i];
411-
auto match_result = TypeAndValuesMatch(f1.type(), v);
437+
auto match_result = Value::TypeAndValuesMatch(f1.type(), v, depth);
412438
if (!match_result.ok()) {
413439
return match_result;
414440
}
@@ -423,7 +449,12 @@ Status Value::TypeAndStructValuesMatch(
423449
*/
424450
// NOLINTNEXTLINE(misc-no-recursion)
425451
Status Value::TypeAndValuesMatch(google::bigtable::v2::Type const& type,
426-
google::bigtable::v2::Value const& value) {
452+
google::bigtable::v2::Value const& value,
453+
int const depth) {
454+
auto depth_exceeded = CheckDepthExceeded(depth);
455+
if (!depth_exceeded.ok()) {
456+
return depth_exceeded;
457+
}
427458
using google::bigtable::v2::Type;
428459
auto make_mismatch_metadata_status = [&](std::string const& value_kind,
429460
std::string const& type_name) {
@@ -438,13 +469,13 @@ Status Value::TypeAndValuesMatch(google::bigtable::v2::Type const& type,
438469
Status result;
439470
switch (type.kind_case()) {
440471
case Type::kArrayType:
441-
result = TypeAndArrayValuesMatch(type, value);
472+
result = TypeAndArrayValuesMatch(type, value, depth + 1);
442473
break;
443474
case Type::kMapType:
444-
result = TypeAndMapValuesMatch(type, value);
475+
result = TypeAndMapValuesMatch(type, value, depth + 1);
445476
break;
446477
case Type::kStructType:
447-
result = TypeAndStructValuesMatch(type, value);
478+
result = TypeAndStructValuesMatch(type, value, depth + 1);
448479
break;
449480
case Type::kBoolType:
450481
if (!value.has_bool_value()) {

google/cloud/bigtable/value.h

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,10 @@ class Value {
316316
}
317317

318318
static Status TypeAndValuesMatch(google::bigtable::v2::Type const& type,
319-
google::bigtable::v2::Value const& value);
319+
google::bigtable::v2::Value const& value,
320+
int depth = 1);
320321

321322
private:
322-
static Status TypeAndArrayValuesMatch(
323-
google::bigtable::v2::Type const& type,
324-
google::bigtable::v2::Value const& value);
325-
static Status TypeAndMapValuesMatch(google::bigtable::v2::Type const& type,
326-
google::bigtable::v2::Value const& value);
327-
static Status TypeAndStructValuesMatch(
328-
google::bigtable::v2::Type const& type,
329-
google::bigtable::v2::Value const& value);
330-
331323
// Metafunction that returns true if `T` is an `absl::optional<U>`
332324
template <typename T>
333325
struct IsOptional : std::false_type {};

0 commit comments

Comments
 (0)