@@ -1069,6 +1069,10 @@ Status LoudsTrie::InitFromData(const Slice& data) {
10691069 if (max_depth_ > kMaxReasonableDepth ) {
10701070 return Status::Corruption (" Trie index: max_depth exceeds reasonable limit" );
10711071 }
1072+ if (static_cast <uint64_t >(cutoff_level_) >
1073+ static_cast <uint64_t >(max_depth_) + 1 ) {
1074+ return Status::Corruption (" Trie index: cutoff_level exceeds max_depth" );
1075+ }
10721076
10731077 memcpy (&dense_leaf_count_, p, 8 );
10741078 p += 8 ;
@@ -1080,6 +1084,17 @@ Status LoudsTrie::InitFromData(const Slice& data) {
10801084 p += 8 ;
10811085 remaining -= 8 ;
10821086
1087+ // d_labels_ stores exactly 256 bits per dense node, and Bitvector rejects
1088+ // serialized bitvectors whose logical size exceeds UINT32_MAX bits because
1089+ // its rank LUT entries are uint32_t. Check the matching dense-node bound
1090+ // explicitly before using this header field to bound validation work.
1091+ static constexpr uint64_t kMaxReasonableDenseNodes =
1092+ std::numeric_limits<uint32_t >::max () / 256 ;
1093+ if (dense_node_count_ > kMaxReasonableDenseNodes ) {
1094+ return Status::Corruption (
1095+ " Trie index: dense_node_count exceeds reasonable limit" );
1096+ }
1097+
10831098 // Read flags field.
10841099 uint32_t flags;
10851100 memcpy (&flags, p, 4 );
@@ -1120,26 +1135,72 @@ Status LoudsTrie::InitFromData(const Slice& data) {
11201135 // ordinal computation during traversal. Inconsistent values would cause
11211136 // incorrect rank arithmetic leading to wrong leaf indices -> wrong block
11221137 // handles.
1123- if (dense_node_count_ > 0 ) {
1124- // Each dense node uses 256 bits in d_labels_.
1125- if (d_labels_.NumBits () != dense_node_count_ * 256 ) {
1126- return Status::Corruption (
1127- " Trie index: d_labels size inconsistent with dense_node_count" );
1128- }
1129- // d_has_child_ has one bit per set bit in d_labels_ (one per label).
1130- if (d_has_child_.NumBits () != d_labels_.NumOnes ()) {
1131- return Status::Corruption (
1132- " Trie index: d_has_child size inconsistent with d_labels" );
1138+ if (dense_node_count_ > std::numeric_limits<uint64_t >::max () / 256 ||
1139+ d_labels_.NumBits () != dense_node_count_ * 256 ) {
1140+ return Status::Corruption (
1141+ " Trie index: d_labels size inconsistent with dense_node_count" );
1142+ }
1143+ // d_has_child_ has one bit per set bit in d_labels_ (one per label).
1144+ if (d_has_child_.NumBits () != d_labels_.NumOnes ()) {
1145+ return Status::Corruption (
1146+ " Trie index: d_has_child size inconsistent with d_labels" );
1147+ }
1148+ // d_is_prefix_key_ has one bit per dense node.
1149+ if (d_is_prefix_key_.NumBits () != dense_node_count_) {
1150+ return Status::Corruption (
1151+ " Trie index: d_is_prefix_key size inconsistent with "
1152+ " dense_node_count" );
1153+ }
1154+ uint64_t computed_dense_leaf_count =
1155+ d_is_prefix_key_.NumOnes () +
1156+ (d_labels_.NumOnes () - d_has_child_.NumOnes ());
1157+ if (dense_leaf_count_ != computed_dense_leaf_count) {
1158+ return Status::Corruption (
1159+ " Trie index: dense_leaf_count inconsistent with dense topology" );
1160+ }
1161+ if (dense_leaf_count_ > num_keys_) {
1162+ return Status::Corruption (" Trie index: dense_leaf_count exceeds num_keys" );
1163+ }
1164+ if (cutoff_level_ > 0 && dense_node_count_ == 0 ) {
1165+ return Status::Corruption (
1166+ " Trie index: cutoff_level inconsistent with dense_node_count" );
1167+ }
1168+
1169+ uint64_t expected_dense_child_count = 0 ;
1170+ if (cutoff_level_ == 0 ) {
1171+ expected_dense_child_count = num_keys_ == 0 ? 0 : 1 ;
1172+ } else {
1173+ uint64_t level_start_node = 0 ;
1174+ uint64_t level_node_count = dense_node_count_ == 0 ? 0 : 1 ;
1175+ uint64_t dense_nodes_seen = 0 ;
1176+ for (uint32_t level = 0 ; level < cutoff_level_; level++) {
1177+ if (level_start_node + level_node_count > dense_node_count_) {
1178+ return Status::Corruption (
1179+ " Trie index: dense_node_count inconsistent with dense topology" );
1180+ }
1181+ uint64_t internal_children = 0 ;
1182+ for (uint64_t i = 0 ; i < level_node_count; i++) {
1183+ uint64_t node_num = level_start_node + i;
1184+ uint64_t label_begin = d_labels_.Rank1 (node_num * 256 );
1185+ uint64_t label_end = d_labels_.Rank1 ((node_num + 1 ) * 256 );
1186+ internal_children +=
1187+ d_has_child_.Rank1 (label_end) - d_has_child_.Rank1 (label_begin);
1188+ }
1189+ dense_nodes_seen += level_node_count;
1190+ if (level + 1 == cutoff_level_) {
1191+ expected_dense_child_count = internal_children;
1192+ }
1193+ level_start_node += level_node_count;
1194+ level_node_count = internal_children;
11331195 }
1134- // d_is_prefix_key_ has one bit per dense node.
1135- if (d_is_prefix_key_.NumBits () != dense_node_count_) {
1196+ if (dense_nodes_seen != dense_node_count_) {
11361197 return Status::Corruption (
1137- " Trie index: d_is_prefix_key size inconsistent with "
1138- " dense_node_count" );
1198+ " Trie index: dense_node_count inconsistent with dense topology" );
11391199 }
11401200 }
1141- if (dense_leaf_count_ > num_keys_) {
1142- return Status::Corruption (" Trie index: dense_leaf_count exceeds num_keys" );
1201+ if (dense_child_count_ != expected_dense_child_count) {
1202+ return Status::Corruption (
1203+ " Trie index: dense_child_count inconsistent with dense topology" );
11431204 }
11441205
11451206 // Sparse section.
@@ -1190,15 +1251,30 @@ Status LoudsTrie::InitFromData(const Slice& data) {
11901251 // label has exactly one bit in s_has_child_ and one bit in s_louds_.
11911252 // A mismatch means the serialized data is inconsistent, which would cause
11921253 // GetBit/Rank1 OOB reads during traversal.
1193- if (s_labels_size_ > 0 ) {
1194- if (s_has_child_.NumBits () != s_labels_size_) {
1195- return Status::Corruption (
1196- " Trie index: s_has_child size inconsistent with s_labels_size" );
1197- }
1198- if (s_louds_.NumBits () != s_labels_size_) {
1199- return Status::Corruption (
1200- " Trie index: s_louds size inconsistent with s_labels_size" );
1201- }
1254+ if (s_has_child_.NumBits () != s_labels_size_) {
1255+ return Status::Corruption (
1256+ " Trie index: s_has_child size inconsistent with s_labels_size" );
1257+ }
1258+ if (s_louds_.NumBits () != s_labels_size_) {
1259+ return Status::Corruption (
1260+ " Trie index: s_louds size inconsistent with s_labels_size" );
1261+ }
1262+ if (s_labels_size_ > 0 && !s_louds_.GetBit (0 )) {
1263+ return Status::Corruption (" Trie index: sparse LOUDS root is missing" );
1264+ }
1265+ if (s_is_prefix_key_.NumBits () != s_louds_.NumOnes ()) {
1266+ return Status::Corruption (
1267+ " Trie index: s_is_prefix_key size inconsistent with sparse nodes" );
1268+ }
1269+ if (dense_child_count_ > s_louds_.NumOnes ()) {
1270+ return Status::Corruption (
1271+ " Trie index: dense_child_count exceeds sparse node count" );
1272+ }
1273+ uint64_t computed_sparse_leaf_count =
1274+ s_is_prefix_key_.NumOnes () + (s_labels_size_ - s_has_child_.NumOnes ());
1275+ if (dense_leaf_count_ + computed_sparse_leaf_count != num_keys_) {
1276+ return Status::Corruption (
1277+ " Trie index: num_keys inconsistent with trie topology" );
12021278 }
12031279
12041280 // Child position lookup tables for Select-free sparse traversal.
@@ -1219,6 +1295,18 @@ Status LoudsTrie::InitFromData(const Slice& data) {
12191295 return Status::Corruption (
12201296 " Trie index: num_internal exceeds reasonable limit" );
12211297 }
1298+ if (num_internal != s_has_child_.NumOnes ()) {
1299+ return Status::Corruption (
1300+ " Trie index: child position table count inconsistent with sparse "
1301+ " topology" );
1302+ }
1303+ if (dense_child_count_ >
1304+ std::numeric_limits<uint64_t >::max () - num_internal ||
1305+ s_louds_.NumOnes () != dense_child_count_ + num_internal) {
1306+ return Status::Corruption (
1307+ " Trie index: sparse node count inconsistent with child position "
1308+ " table" );
1309+ }
12221310
12231311 if (num_internal > 0 ) {
12241312 // Read s_child_start_pos_
@@ -1251,10 +1339,26 @@ Status LoudsTrie::InitFromData(const Slice& data) {
12511339 for (uint64_t k = 0 ; k < num_internal; k++) {
12521340 if (s_child_start_pos_[k] >= s_labels_size_ ||
12531341 s_child_end_pos_[k] > s_labels_size_ ||
1254- s_child_end_pos_[k] < s_child_start_pos_[k]) {
1342+ s_child_end_pos_[k] <= s_child_start_pos_[k]) {
12551343 return Status::Corruption (
12561344 " Trie index: child position out of range for sparse labels" );
12571345 }
1346+ if (!s_louds_.GetBit (s_child_start_pos_[k])) {
1347+ return Status::Corruption (
1348+ " Trie index: child position does not start a sparse node" );
1349+ }
1350+ uint64_t child_node = dense_child_count_ + k;
1351+ uint64_t expected_start = s_louds_.FindNthOneBit (child_node);
1352+ uint64_t expected_end = s_louds_.NextSetBit (expected_start + 1 );
1353+ if (expected_end > s_labels_size_) {
1354+ expected_end = s_labels_size_;
1355+ }
1356+ if (s_child_start_pos_[k] != expected_start ||
1357+ s_child_end_pos_[k] != expected_end) {
1358+ return Status::Corruption (
1359+ " Trie index: child position table inconsistent with sparse "
1360+ " topology" );
1361+ }
12581362 }
12591363 }
12601364 }
@@ -1269,6 +1373,10 @@ Status LoudsTrie::InitFromData(const Slice& data) {
12691373 memcpy (&num_chains, p, 8 );
12701374 p += 8 ;
12711375 remaining -= 8 ;
1376+ if (num_chains > num_internal) {
1377+ return Status::Corruption (
1378+ " Trie index: chain count exceeds sparse internal node count" );
1379+ }
12721380
12731381 if (num_chains > 0 ) {
12741382 // Read chain bitmap.
@@ -1278,6 +1386,11 @@ Status LoudsTrie::InitFromData(const Slice& data) {
12781386 }
12791387 p += consumed;
12801388 remaining -= consumed;
1389+ if (s_chain_bitmap_.NumBits () != num_internal ||
1390+ s_chain_bitmap_.NumOnes () != num_chains) {
1391+ return Status::Corruption (
1392+ " Trie index: chain bitmap inconsistent with chain count" );
1393+ }
12811394
12821395 // Read compact chain_offsets (uint32_t per chain).
12831396 size_t offsets_bytes = num_chains * sizeof (uint32_t );
0 commit comments