blob: e83f0e6209c713431533be0d15272b206ad39c48 [file] [log] [blame]
Abhishek Bhardwaj0249a2c2023-05-01 23:39:111// 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 Bhardwaj64a204b2023-05-08 22:23:415#include <cstdint>
Gwendal Grignouf9533dd2023-06-08 04:31:476#include <functional>
Abhishek Bhardwaj64a204b2023-05-08 22:23:417#include <optional>
8#include <string>
9
Abhishek Bhardwaj4618643e2023-05-11 15:25:3010#include <base/base64.h>
Wei-Luan Wang2ba94a82025-08-22 02:13:4511#include <base/byte_count.h>
Gwendal Grignoufd1439b2024-01-10 23:51:4112#include <base/check.h>
Abhishek Bhardwaj0249a2c2023-05-01 23:39:1113#include <base/files/file_path.h>
14#include <base/files/file_util.h>
Abhishek Bhardwaj0249a2c2023-05-01 23:39:1115#include <base/logging.h>
Gwendal Grignouf9533dd2023-06-08 04:31:4716#include <base/system/sys_info.h>
Abhishek Bhardwaj0249a2c2023-05-01 23:39:1117#include <base/values.h>
Gwendal Grignou36874f82024-04-19 17:49:3918#include <brillo/file_utils.h>
Abhishek Bhardwajd30802a2023-05-09 01:28:1419#include <brillo/process/process.h>
Gwendal Grignouf9533dd2023-06-08 04:31:4720#include <rootdev/rootdev.h>
Gwendal Grignou9a8d51e2024-01-10 17:50:1021#include <vpd/vpd.h>
Abhishek Bhardwaj0249a2c2023-05-01 23:39:1122
Abhishek Bhardwaj64a204b2023-05-08 22:23:4123#include "libsegmentation/device_info.pb.h"
Gwendal Grignouf9533dd2023-06-08 04:31:4724#include "libsegmentation/feature_management_hwid.h"
Abhishek Bhardwaj64a204b2023-05-08 22:23:4125#include "libsegmentation/feature_management_impl.h"
Abhishek Bhardwaj0249a2c2023-05-01 23:39:1126#include "libsegmentation/feature_management_interface.h"
27#include "libsegmentation/feature_management_util.h"
28
29namespace segmentation {
30
31namespace {
32
Abhishek Bhardwajd30802a2023-05-09 01:28:1433// The path for the "gsctool" binary.
Gwendal Grignou55c65d02023-06-23 16:57:0434const char kGscToolBinaryPath[] = "/usr/sbin/gsctool";
Abhishek Bhardwajd30802a2023-05-09 01:28:1435
36// The output of |kGscToolBinaryPath| will contain a "chassis_x_branded:" line.
Gwendal Grignou55c65d02023-06-23 16:57:0437const char kChassisXBrandedKey[] = "chassis_x_branded:";
Abhishek Bhardwajd30802a2023-05-09 01:28:1438
39// The output of |kGscToolBinaryPath| will contain a "hw_compliance_version:"
40// line.
Gwendal Grignou55c65d02023-06-23 16:57:0441const char kHwXComplianceVersion[] = "hw_x_compliance_version:";
Abhishek Bhardwajd30802a2023-05-09 01:28:1442
43// The output from the "gsctool" binary. Some or all of these fields may not be
44// present in the output.
45struct GscToolOutput {
46 bool chassis_x_branded;
47 int32_t hw_compliance_version;
48};
49
50// Parses output from running |kGscToolBinaryPath| into GscToolOutput.
51std::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 Bhardwajd30802a2023-05-09 01:28:1488// Returns the device information parsed from the output of the GSC tool binary
89// on the device.
Gwendal Grignouf9533dd2023-06-08 04:31:4790std::optional<GscToolOutput> GetDeviceInfoFromGSC() {
Gwendal Grignoufd1439b2024-01-10 23:51:4191 CHECK(base::PathExists(base::FilePath(kGscToolBinaryPath)));
Abhishek Bhardwajd30802a2023-05-09 01:28:1492
Abhishek Bhardwajd30802a2023-05-09 01:28:1493 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 Jung48317ff2023-08-19 01:12:18105 process.RedirectOutput(output_path);
Abhishek Bhardwajd30802a2023-05-09 01:28:14106
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 Grignouf9533dd2023-06-08 04:31:47129 return gsc_tool_output;
Abhishek Bhardwaj0249a2c2023-05-01 23:39:11130}
Abhishek Bhardwaj0249a2c2023-05-01 23:39:11131} // namespace
132
133FeatureManagementInterface::FeatureLevel
134FeatureManagementImpl::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 Bhardwaj30d2ee82023-05-05 01:22:28148FeatureManagementInterface::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 Bhardwaj0249a2c2023-05-01 23:39:11162bool FeatureManagementImpl::CacheDeviceInfo() {
Gwendal Grignoubb4925a2023-06-09 23:04:37163 std::optional<libsegmentation::DeviceInfo> device_info_result;
Gwendal Grignou9a8d51e2024-01-10 17:50:10164
Gwendal Grignou36874f82024-04-19 17:49:39165 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 Grignou9a8d51e2024-01-10 17:50:10169 // To overwrite hash check: it eases testing and prevent entering the real
170 // logic.
George Burgess IV3f504202024-11-06 16:26:43171 if (device_info_result) {
Gwendal Grignoubb4925a2023-06-09 23:04:37172 device_info_result->set_cached_version_hash(current_version_hash_);
George Burgess IV3f504202024-11-06 16:26:43173 }
Gwendal Grignou9a8d51e2024-01-10 17:50:10174 }
Gwendal Grignou36874f82024-04-19 17:49:39175 // No luck from tmpfs, read from the cached location in vpd.
Gwendal Grignou9a8d51e2024-01-10 17:50:10176 if (!device_info_result) {
177 std::optional<std::string> encoded =
178 vpd_->GetValue(vpd::VpdRw, kVpdKeyDeviceInfo);
George Burgess IV3f504202024-11-06 16:26:43179 if (encoded) {
Gwendal Grignou9a8d51e2024-01-10 17:50:10180 device_info_result = FeatureManagementUtil::ReadDeviceInfo(*encoded);
George Burgess IV3f504202024-11-06 16:26:43181 }
Gwendal Grignoubb4925a2023-06-09 23:04:37182 }
183
184 // If the device info isn't cached, read it form the hardware id and write it
Gwendal Grignou36874f82024-04-19 17:49:39185 // to tpmfs for subsequent calls until reboots.
186 // A upstart job may save the value in the VPD when the device is stable.
Gwendal Grignouddf93622023-06-02 20:12:09187 if (!device_info_result ||
188 device_info_result->cached_version_hash() != current_version_hash_) {
Gwendal Grignoufd1439b2024-01-10 23:51:41189 // 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 Grignouf9533dd2023-06-08 04:31:47198 if (!gsc_tool_output) {
Abhishek Bhardwaj0249a2c2023-05-01 23:39:11199 LOG(ERROR) << "Failed to get device info from the hardware id";
200 return false;
201 }
202
Gwendal Grignouf9533dd2023-06-08 04:31:47203 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 Grignouddf93622023-06-02 20:12:09208 device_info_result->set_cached_version_hash(current_version_hash_);
Gwendal Grignouf9533dd2023-06-08 04:31:47209
Gwendal Grignou36874f82024-04-19 17:49:39210 // 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 Grignou9a8d51e2024-01-10 17:50:10215 FeatureManagementUtil::EncodeDeviceInfo(*device_info_result))) {
Gwendal Grignou36874f82024-04-19 17:49:39216 LOG(ERROR) << "Failed to cache device info in " << tpmfs_cache.value();
Gwendal Grignou9a8d51e2024-01-10 17:50:10217 return false;
Abhishek Bhardwaj0249a2c2023-05-01 23:39:11218 }
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 Grignouf9533dd2023-06-08 04:31:47226std::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 IV3f504202024-11-06 16:26:43237 if (!selection) {
Gwendal Grignouf9533dd2023-06-08 04:31:47238 return std::nullopt;
George Burgess IV3f504202024-11-06 16:26:43239 }
Gwendal Grignouf9533dd2023-06-08 04:31:47240
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
249bool 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 Chang308c26b2025-10-04 02:55:50256 if (selection.feature_level() > 2) {
Gwendal Grignouf9533dd2023-06-08 04:31:47257 LOG(ERROR) << "Requirement not defined yet for feature_level "
258 << selection.feature_level();
259 return false;
260 }
261
Gwendal Grignou98c9e712023-06-21 22:48:00262 // Feature level 1:
Gwendal Grignou0adaca62023-09-06 01:37:04263 // DRAM >= 8GiB. But since not all the physical RAM is available (PCI hole),
264 // settle for 7GiB.
Gwendal Grignou98c9e712023-06-21 22:48:00265 // Obtain the size of the physical memory of the system.
Wei-Luan Wang2ba94a82025-08-22 02:13:45266 if (base::SysInfo::AmountOfPhysicalMemory() < base::GiB(7)) {
Gwendal Grignou98c9e712023-06-21 22:48:00267 return false;
George Burgess IV3f504202024-11-06 16:26:43268 }
Gwendal Grignou98c9e712023-06-21 22:48:00269
Gwendal Grignou0adaca62023-09-06 01:37:04270 // SSD >= 128GB
271 // But since SSD counts in power of 10 and controller may even take a bigger
272 // share, settle for 110GiB.
Gwendal Grignou98c9e712023-06-21 22:48:00273 // 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 IV3f504202024-11-06 16:26:43277 if (!root_device) {
Gwendal Grignou98c9e712023-06-21 22:48:00278 return false;
George Burgess IV3f504202024-11-06 16:26:43279 }
Gwendal Grignou98c9e712023-06-21 22:48:00280
281 std::optional<int64_t> size =
282 FeatureManagementUtil::GetDiskSpace(*root_device);
George Burgess IV3f504202024-11-06 16:26:43283 if (!size) {
Gwendal Grignou98c9e712023-06-21 22:48:00284 return false;
George Burgess IV3f504202024-11-06 16:26:43285 }
Gwendal Grignou98c9e712023-06-21 22:48:00286
Gwendal Grignou0adaca62023-09-06 01:37:04287 const size_t k110GiB = 110 * 1024 * 1024 * 1024ULL;
George Burgess IV3f504202024-11-06 16:26:43288 if (*size < k110GiB) {
Gwendal Grignou98c9e712023-06-21 22:48:00289 return false;
George Burgess IV3f504202024-11-06 16:26:43290 }
Gwendal Grignou98c9e712023-06-21 22:48:00291
Gwendal Grignouf9533dd2023-06-08 04:31:47292 return true;
293}
294
Gwendal Grignou36874f82024-04-19 17:49:39295bool 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 Bhardwaj0249a2c2023-05-01 23:39:11323} // namespace segmentation