|
24 | 24 | #include <fstream>
|
25 | 25 | #include <iostream>
|
26 | 26 | #include <memory>
|
| 27 | +#include <optional> |
27 | 28 | #include <ostream>
|
28 | 29 | #include <string>
|
29 | 30 | #include <utility>
|
@@ -1039,6 +1040,90 @@ bool HasReservedFieldNumber(const FieldDescriptor* field) {
|
1039 | 1040 | return false;
|
1040 | 1041 | }
|
1041 | 1042 |
|
| 1043 | +bool HasDebugRedact(const EnumDescriptor& enm) { |
| 1044 | + for (int i = 0; i < enm.value_count(); ++i) { |
| 1045 | + const EnumValueDescriptor* value = enm.value(i); |
| 1046 | + if (value->options().debug_redact()) { |
| 1047 | + return true; |
| 1048 | + } |
| 1049 | + } |
| 1050 | + return false; |
| 1051 | +} |
| 1052 | + |
| 1053 | +bool HasDebugRedact(const Descriptor& desc, |
| 1054 | + absl::flat_hash_map<const Descriptor*, bool>& visited); |
| 1055 | + |
| 1056 | +bool HasDebugRedact(const FieldDescriptor& field, |
| 1057 | + absl::flat_hash_map<const Descriptor*, bool>& visited) { |
| 1058 | + if (field.enum_type() != nullptr && HasDebugRedact(*field.enum_type())) { |
| 1059 | + return true; |
| 1060 | + } |
| 1061 | + if (field.message_type() != nullptr && |
| 1062 | + HasDebugRedact(*field.message_type(), visited)) { |
| 1063 | + return true; |
| 1064 | + } |
| 1065 | + return false; |
| 1066 | +} |
| 1067 | + |
| 1068 | +bool HasDebugRedact(const Descriptor& desc, |
| 1069 | + absl::flat_hash_map<const Descriptor*, bool>& visited) { |
| 1070 | + auto result = visited.emplace(&desc, false); |
| 1071 | + if (!result.second) { |
| 1072 | + return result.first->second; |
| 1073 | + } |
| 1074 | + for (int i = 0; i < desc.field_count(); ++i) { |
| 1075 | + if (HasDebugRedact(*desc.field(i), visited)) { |
| 1076 | + visited[&desc] = true; |
| 1077 | + return true; |
| 1078 | + } |
| 1079 | + } |
| 1080 | + return false; |
| 1081 | +} |
| 1082 | + |
| 1083 | +// Look for any enums with values marked debug_redact within this message |
| 1084 | +// schema. These can be used to mark fields that need to be redacted in debug |
| 1085 | +// string APIs, but are only discoverable via reflection that doesn't force |
| 1086 | +// linkage. |
| 1087 | +std::optional<std::string> FindDebugRedactMarker(const FileDescriptor& desc) { |
| 1088 | + std::optional<std::string> debug_redact_value; |
| 1089 | + absl::flat_hash_map<const Descriptor*, bool> visited; |
| 1090 | + google::protobuf::internal::VisitDescriptors( |
| 1091 | + desc, [&debug_redact_value, &visited](const FieldDescriptor& field) { |
| 1092 | + if (field.is_extension() && HasDebugRedact(field, visited)) { |
| 1093 | + debug_redact_value = field.full_name(); |
| 1094 | + } |
| 1095 | + }); |
| 1096 | + return debug_redact_value; |
| 1097 | +} |
| 1098 | + |
| 1099 | +bool ValidateOptionImports(const FileDescriptor& file, |
| 1100 | + const DescriptorPool& pool, |
| 1101 | + DescriptorPool::ErrorCollector* printer) { |
| 1102 | + for (int i = 0; i < file.option_dependency_count(); ++i) { |
| 1103 | + const FileDescriptor* dep = |
| 1104 | + pool.FindFileByName(file.option_dependency_name(i)); |
| 1105 | + if (dep == nullptr) { |
| 1106 | + // If we don't have the dependency we can't validate it, assume it's ok. |
| 1107 | + continue; |
| 1108 | + } |
| 1109 | + std::optional<std::string> debug_redact_value = FindDebugRedactMarker(*dep); |
| 1110 | + if (debug_redact_value.has_value()) { |
| 1111 | + printer->RecordError( |
| 1112 | + file.name(), "", nullptr, DescriptorPool::ErrorCollector::OPTION_NAME, |
| 1113 | + absl::StrCat( |
| 1114 | + "Optional dependency ", dep->name(), " contains a custom option ", |
| 1115 | + *debug_redact_value, |
| 1116 | + " marked debug_redact, which is used to mark fields that need to " |
| 1117 | + "be redacted in debug string APIs. Switch to a regular import or " |
| 1118 | + "remove the debug_redact annotation to avoid potentially leaking " |
| 1119 | + "sensitive data.")); |
| 1120 | + return false; |
| 1121 | + } |
| 1122 | + } |
| 1123 | + |
| 1124 | + return true; |
| 1125 | +} |
| 1126 | + |
1042 | 1127 | } // namespace
|
1043 | 1128 |
|
1044 | 1129 | namespace {
|
@@ -1294,6 +1379,11 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
|
1294 | 1379 | bool validation_error = false; // Defer exiting so we log more warnings.
|
1295 | 1380 |
|
1296 | 1381 | for (auto& file : parsed_files) {
|
| 1382 | + if (!ValidateOptionImports(*file, *descriptor_pool, |
| 1383 | + error_collector.get())) { |
| 1384 | + validation_error = true; |
| 1385 | + } |
| 1386 | + |
1297 | 1387 | google::protobuf::internal::VisitDescriptors(
|
1298 | 1388 | *file, [&](const FieldDescriptor& field) {
|
1299 | 1389 | if (HasReservedFieldNumber(&field)) {
|
|
0 commit comments