Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 1 | // Copyright 2023 The ChromiumOS Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Abhishek Bhardwaj | 64a204b | 2023-05-08 22:23:41 | [diff] [blame] | 5 | #include <cstdint> |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 6 | #include <functional> |
Abhishek Bhardwaj | 64a204b | 2023-05-08 22:23:41 | [diff] [blame] | 7 | #include <optional> |
| 8 | #include <string> |
| 9 | |
Abhishek Bhardwaj | 4618643e | 2023-05-11 15:25:30 | [diff] [blame] | 10 | #include <base/base64.h> |
Wei-Luan Wang | 2ba94a8 | 2025-08-22 02:13:45 | [diff] [blame] | 11 | #include <base/byte_count.h> |
Gwendal Grignou | fd1439b | 2024-01-10 23:51:41 | [diff] [blame] | 12 | #include <base/check.h> |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 13 | #include <base/files/file_path.h> |
| 14 | #include <base/files/file_util.h> |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 15 | #include <base/logging.h> |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 16 | #include <base/system/sys_info.h> |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 17 | #include <base/values.h> |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 18 | #include <brillo/file_utils.h> |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 19 | #include <brillo/process/process.h> |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 20 | #include <rootdev/rootdev.h> |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 21 | #include <vpd/vpd.h> |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 22 | |
Abhishek Bhardwaj | 64a204b | 2023-05-08 22:23:41 | [diff] [blame] | 23 | #include "libsegmentation/device_info.pb.h" |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 24 | #include "libsegmentation/feature_management_hwid.h" |
Abhishek Bhardwaj | 64a204b | 2023-05-08 22:23:41 | [diff] [blame] | 25 | #include "libsegmentation/feature_management_impl.h" |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 26 | #include "libsegmentation/feature_management_interface.h" |
| 27 | #include "libsegmentation/feature_management_util.h" |
| 28 | |
| 29 | namespace segmentation { |
| 30 | |
| 31 | namespace { |
| 32 | |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 33 | // The path for the "gsctool" binary. |
Gwendal Grignou | 55c65d0 | 2023-06-23 16:57:04 | [diff] [blame] | 34 | const char kGscToolBinaryPath[] = "/usr/sbin/gsctool"; |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 35 | |
| 36 | // The output of |kGscToolBinaryPath| will contain a "chassis_x_branded:" line. |
Gwendal Grignou | 55c65d0 | 2023-06-23 16:57:04 | [diff] [blame] | 37 | const char kChassisXBrandedKey[] = "chassis_x_branded:"; |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 38 | |
| 39 | // The output of |kGscToolBinaryPath| will contain a "hw_compliance_version:" |
| 40 | // line. |
Gwendal Grignou | 55c65d0 | 2023-06-23 16:57:04 | [diff] [blame] | 41 | const char kHwXComplianceVersion[] = "hw_x_compliance_version:"; |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 42 | |
| 43 | // The output from the "gsctool" binary. Some or all of these fields may not be |
| 44 | // present in the output. |
| 45 | struct GscToolOutput { |
| 46 | bool chassis_x_branded; |
| 47 | int32_t hw_compliance_version; |
| 48 | }; |
| 49 | |
| 50 | // Parses output from running |kGscToolBinaryPath| into GscToolOutput. |
| 51 | std::optional<GscToolOutput> ParseGscToolOutput( |
| 52 | const std::string& gsc_tool_output) { |
| 53 | GscToolOutput output; |
| 54 | std::istringstream iss(gsc_tool_output); |
| 55 | std::string line; |
| 56 | |
| 57 | // Flags to indicate we've found the required fields. |
| 58 | bool found_chassis = false; |
| 59 | bool found_compliance_version = false; |
| 60 | |
| 61 | // Keep going till there are lines in the output or we've found both the |
| 62 | // fields. |
| 63 | while (std::getline(iss, line) && |
| 64 | (!found_chassis || !found_compliance_version)) { |
| 65 | std::istringstream line_stream(line); |
| 66 | std::string key; |
| 67 | line_stream >> key; |
| 68 | |
| 69 | if (key == kChassisXBrandedKey) { |
| 70 | bool value; |
| 71 | line_stream >> std::boolalpha >> value; |
| 72 | output.chassis_x_branded = value; |
| 73 | found_chassis = true; |
| 74 | } else if (key == kHwXComplianceVersion) { |
| 75 | int32_t value; |
| 76 | line_stream >> value; |
| 77 | output.hw_compliance_version = value; |
| 78 | found_compliance_version = true; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | if (found_chassis && found_compliance_version) { |
| 83 | return output; |
| 84 | } |
| 85 | return std::nullopt; |
| 86 | } |
| 87 | |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 88 | // Returns the device information parsed from the output of the GSC tool binary |
| 89 | // on the device. |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 90 | std::optional<GscToolOutput> GetDeviceInfoFromGSC() { |
Gwendal Grignou | fd1439b | 2024-01-10 23:51:41 | [diff] [blame] | 91 | CHECK(base::PathExists(base::FilePath(kGscToolBinaryPath))); |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 92 | |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 93 | base::FilePath output_path; |
| 94 | if (!base::CreateTemporaryFile(&output_path)) { |
| 95 | LOG(ERROR) << "Failed to open output file"; |
| 96 | return std::nullopt; |
| 97 | } |
| 98 | |
| 99 | brillo::ProcessImpl process; |
| 100 | process.AddArg(kGscToolBinaryPath); |
| 101 | std::vector<std::string> args = {"--factory_config", "--any"}; |
| 102 | for (const auto& arg : args) { |
| 103 | process.AddArg(arg); |
| 104 | } |
Jaewon Jung | 48317ff | 2023-08-19 01:12:18 | [diff] [blame] | 105 | process.RedirectOutput(output_path); |
Abhishek Bhardwaj | d30802a | 2023-05-09 01:28:14 | [diff] [blame] | 106 | |
| 107 | if (!process.Start()) { |
| 108 | LOG(ERROR) << "Failed to start gsctool process"; |
| 109 | return std::nullopt; |
| 110 | } |
| 111 | |
| 112 | if (process.Wait() < 0) { |
| 113 | LOG(ERROR) << "Failed to wait for the gsctool process"; |
| 114 | return std::nullopt; |
| 115 | } |
| 116 | |
| 117 | std::string output; |
| 118 | if (!base::ReadFileToString(output_path, &output)) { |
| 119 | LOG(ERROR) << "Failed to read output from the gsctool"; |
| 120 | return std::nullopt; |
| 121 | } |
| 122 | |
| 123 | std::optional<GscToolOutput> gsc_tool_output = ParseGscToolOutput(output); |
| 124 | if (!gsc_tool_output) { |
| 125 | LOG(ERROR) << "Failed to parse output from the gsctool"; |
| 126 | return std::nullopt; |
| 127 | } |
| 128 | |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 129 | return gsc_tool_output; |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 130 | } |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 131 | } // namespace |
| 132 | |
| 133 | FeatureManagementInterface::FeatureLevel |
| 134 | FeatureManagementImpl::GetFeatureLevel() { |
| 135 | if (cached_device_info_) { |
| 136 | return FeatureManagementUtil::ConvertProtoFeatureLevel( |
| 137 | cached_device_info_->feature_level()); |
| 138 | } |
| 139 | |
| 140 | if (!CacheDeviceInfo()) { |
| 141 | return FeatureLevel::FEATURE_LEVEL_UNKNOWN; |
| 142 | } |
| 143 | |
| 144 | return FeatureManagementUtil::ConvertProtoFeatureLevel( |
| 145 | cached_device_info_->feature_level()); |
| 146 | } |
| 147 | |
Abhishek Bhardwaj | 30d2ee8 | 2023-05-05 01:22:28 | [diff] [blame] | 148 | FeatureManagementInterface::ScopeLevel FeatureManagementImpl::GetScopeLevel() { |
| 149 | if (cached_device_info_) { |
| 150 | return FeatureManagementUtil::ConvertProtoScopeLevel( |
| 151 | cached_device_info_->scope_level()); |
| 152 | } |
| 153 | |
| 154 | if (!CacheDeviceInfo()) { |
| 155 | return ScopeLevel::SCOPE_LEVEL_UNKNOWN; |
| 156 | } |
| 157 | |
| 158 | return FeatureManagementUtil::ConvertProtoScopeLevel( |
| 159 | cached_device_info_->scope_level()); |
| 160 | } |
| 161 | |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 162 | bool FeatureManagementImpl::CacheDeviceInfo() { |
Gwendal Grignou | bb4925a | 2023-06-09 23:04:37 | [diff] [blame] | 163 | std::optional<libsegmentation::DeviceInfo> device_info_result; |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 164 | |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 165 | base::FilePath tpmfs_cache = base::FilePath(kTempDeviceInfoPath); |
| 166 | // Read from the tmpfs file if it exists. |
| 167 | if (base::PathExists(tpmfs_cache)) { |
| 168 | device_info_result = FeatureManagementUtil::ReadDeviceInfo(tpmfs_cache); |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 169 | // To overwrite hash check: it eases testing and prevent entering the real |
| 170 | // logic. |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 171 | if (device_info_result) { |
Gwendal Grignou | bb4925a | 2023-06-09 23:04:37 | [diff] [blame] | 172 | device_info_result->set_cached_version_hash(current_version_hash_); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 173 | } |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 174 | } |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 175 | // No luck from tmpfs, read from the cached location in vpd. |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 176 | if (!device_info_result) { |
| 177 | std::optional<std::string> encoded = |
| 178 | vpd_->GetValue(vpd::VpdRw, kVpdKeyDeviceInfo); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 179 | if (encoded) { |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 180 | device_info_result = FeatureManagementUtil::ReadDeviceInfo(*encoded); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 181 | } |
Gwendal Grignou | bb4925a | 2023-06-09 23:04:37 | [diff] [blame] | 182 | } |
| 183 | |
| 184 | // If the device info isn't cached, read it form the hardware id and write it |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 185 | // to tpmfs for subsequent calls until reboots. |
| 186 | // A upstart job may save the value in the VPD when the device is stable. |
Gwendal Grignou | ddf9362 | 2023-06-02 20:12:09 | [diff] [blame] | 187 | if (!device_info_result || |
| 188 | device_info_result->cached_version_hash() != current_version_hash_) { |
Gwendal Grignou | fd1439b | 2024-01-10 23:51:41 | [diff] [blame] | 189 | // If we are running in a VM, do not check HWID/GSC. |
| 190 | std::optional<int> inside_vm = |
| 191 | crossystem_->VbGetSystemPropertyInt("inside_vm"); |
| 192 | if (!inside_vm || *inside_vm) { |
| 193 | LOG(WARNING) << "Skip HIWD/GSC checking inside VM."; |
| 194 | return false; |
| 195 | } |
| 196 | |
| 197 | std::optional<GscToolOutput> gsc_tool_output = GetDeviceInfoFromGSC(); |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 198 | if (!gsc_tool_output) { |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 199 | LOG(ERROR) << "Failed to get device info from the hardware id"; |
| 200 | return false; |
| 201 | } |
| 202 | |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 203 | FeatureManagementHwid::GetDeviceSelectionFn get_device_callback = |
| 204 | [this](bool check) { return this->GetDeviceInfoFromHwid(check); }; |
| 205 | device_info_result = FeatureManagementHwid::GetDeviceInfo( |
| 206 | get_device_callback, gsc_tool_output->chassis_x_branded, |
| 207 | gsc_tool_output->hw_compliance_version); |
Gwendal Grignou | ddf9362 | 2023-06-02 20:12:09 | [diff] [blame] | 208 | device_info_result->set_cached_version_hash(current_version_hash_); |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 209 | |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 210 | // Write in the tmpfs cache. Do not write in the VPD since the API call |
| 211 | // could be done early at boot, in a time-critical section. It will be |
| 212 | // written later in the VPD by a call to "feature_check --flash". |
| 213 | if (!brillo::WriteStringToFile( |
| 214 | tpmfs_cache, |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 215 | FeatureManagementUtil::EncodeDeviceInfo(*device_info_result))) { |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 216 | LOG(ERROR) << "Failed to cache device info in " << tpmfs_cache.value(); |
Gwendal Grignou | 9a8d51e | 2024-01-10 17:50:10 | [diff] [blame] | 217 | return false; |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 218 | } |
| 219 | } |
| 220 | |
| 221 | // At this point device information is present on stateful. We can cache it. |
| 222 | cached_device_info_ = device_info_result.value(); |
| 223 | return true; |
| 224 | } |
| 225 | |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 226 | std::optional<DeviceSelection> FeatureManagementImpl::GetDeviceInfoFromHwid( |
| 227 | bool check_prefix_only) { |
| 228 | std::optional<std::string> hwid = |
| 229 | crossystem_->VbGetSystemPropertyString("hwid"); |
| 230 | if (!hwid) { |
| 231 | LOG(ERROR) << "Unable to retrieve HWID"; |
| 232 | return std::nullopt; |
| 233 | } |
| 234 | std::optional<DeviceSelection> selection = |
| 235 | FeatureManagementHwid::GetSelectionFromHWID( |
| 236 | selection_bundle_, hwid.value(), check_prefix_only); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 237 | if (!selection) { |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 238 | return std::nullopt; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 239 | } |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 240 | |
| 241 | if (!check_prefix_only && !Check_HW_Requirement(selection.value())) { |
| 242 | LOG(ERROR) << hwid.value() << " do not meet feature level " |
| 243 | << selection->feature_level() << " requirement."; |
| 244 | return std::nullopt; |
| 245 | } |
| 246 | return selection; |
| 247 | } |
| 248 | |
| 249 | bool FeatureManagementImpl::Check_HW_Requirement( |
| 250 | const DeviceSelection& selection) { |
| 251 | if (selection.feature_level() == 0) { |
| 252 | LOG(ERROR) << "Unexpected feature level: 0"; |
| 253 | return false; |
| 254 | } |
| 255 | |
Tony Chang | 308c26b | 2025-10-04 02:55:50 | [diff] [blame] | 256 | if (selection.feature_level() > 2) { |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 257 | LOG(ERROR) << "Requirement not defined yet for feature_level " |
| 258 | << selection.feature_level(); |
| 259 | return false; |
| 260 | } |
| 261 | |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 262 | // Feature level 1: |
Gwendal Grignou | 0adaca6 | 2023-09-06 01:37:04 | [diff] [blame] | 263 | // DRAM >= 8GiB. But since not all the physical RAM is available (PCI hole), |
| 264 | // settle for 7GiB. |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 265 | // Obtain the size of the physical memory of the system. |
Wei-Luan Wang | 2ba94a8 | 2025-08-22 02:13:45 | [diff] [blame] | 266 | if (base::SysInfo::AmountOfPhysicalMemory() < base::GiB(7)) { |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 267 | return false; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 268 | } |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 269 | |
Gwendal Grignou | 0adaca6 | 2023-09-06 01:37:04 | [diff] [blame] | 270 | // SSD >= 128GB |
| 271 | // But since SSD counts in power of 10 and controller may even take a bigger |
| 272 | // share, settle for 110GiB. |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 273 | // sysinfo AmountOfTotalDiskSpace can not be used, it returns the size of the |
| 274 | // underlying filesystem. |
| 275 | std::optional<base::FilePath> root_device = |
| 276 | FeatureManagementUtil::GetDefaultRoot(base::FilePath("/")); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 277 | if (!root_device) { |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 278 | return false; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 279 | } |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 280 | |
| 281 | std::optional<int64_t> size = |
| 282 | FeatureManagementUtil::GetDiskSpace(*root_device); |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 283 | if (!size) { |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 284 | return false; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 285 | } |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 286 | |
Gwendal Grignou | 0adaca6 | 2023-09-06 01:37:04 | [diff] [blame] | 287 | const size_t k110GiB = 110 * 1024 * 1024 * 1024ULL; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 288 | if (*size < k110GiB) { |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 289 | return false; |
George Burgess IV | 3f50420 | 2024-11-06 16:26:43 | [diff] [blame] | 290 | } |
Gwendal Grignou | 98c9e71 | 2023-06-21 22:48:00 | [diff] [blame] | 291 | |
Gwendal Grignou | f9533dd | 2023-06-08 04:31:47 | [diff] [blame] | 292 | return true; |
| 293 | } |
| 294 | |
Gwendal Grignou | 36874f8 | 2024-04-19 17:49:39 | [diff] [blame] | 295 | bool FeatureManagementImpl::FlashLevels() { |
| 296 | base::FilePath tpmfs_cache = base::FilePath(kTempDeviceInfoPath); |
| 297 | if (!base::PathExists(tpmfs_cache)) { |
| 298 | // Usual case: the VPD is up to date, CacheDeviceInfo() did not have to |
| 299 | // query the device internals. |
| 300 | VLOG(1) << "Segmentation level has not been computed since boot."; |
| 301 | return true; |
| 302 | } |
| 303 | |
| 304 | std::string encoded_cached; |
| 305 | if (!base::ReadFileToString(tpmfs_cache, &encoded_cached)) { |
| 306 | LOG(WARNING) << "Unable to read cached value"; |
| 307 | return false; |
| 308 | } |
| 309 | |
| 310 | std::optional<std::string> encoded_saved = |
| 311 | vpd_->GetValue(vpd::VpdRw, kVpdKeyDeviceInfo); |
| 312 | if (!encoded_saved || encoded_saved != encoded_cached) { |
| 313 | LOG(INFO) << "Update VPD information"; |
| 314 | return vpd_->WriteValue(vpd::VpdRw, kVpdKeyDeviceInfo, encoded_cached); |
| 315 | } |
| 316 | |
| 317 | // What CacheDeviceInfo() calculated ended up being the same as the |
| 318 | // one in the VPD. It can happen during testing. |
| 319 | LOG(INFO) << "VPD already up to date"; |
| 320 | return true; |
| 321 | } |
| 322 | |
Abhishek Bhardwaj | 0249a2c | 2023-05-01 23:39:11 | [diff] [blame] | 323 | } // namespace segmentation |