| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "init/utils.h" |
| |
| #include <selinux/restorecon.h> |
| #include <selinux/selinux.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/process/process.h> |
| #include <rootdev/rootdev.h> |
| #include <vboot/cgpt_params.h> |
| #include <vboot/vboot_host.h> |
| |
| namespace { |
| |
| // A callback function for SELinux restorecon. |
| PRINTF_FORMAT(2, 3) |
| int RestoreConLogCallback(int type, const char* fmt, ...) { |
| va_list ap; |
| |
| std::string message = "restorecon: "; |
| va_start(ap, fmt); |
| message += base::StringPrintV(fmt, ap); |
| va_end(ap); |
| |
| // This already has a line feed at the end, so trim it off to avoid |
| // empty lines in the log. |
| base::TrimString(message, "\r\n", &message); |
| |
| if (type == SELINUX_INFO) |
| LOG(INFO) << message; |
| else |
| LOG(ERROR) << message; |
| |
| return 0; |
| } |
| |
| void CgptFindShowFunctionNoOp(struct CgptFindParams*, |
| const char*, |
| int, |
| GptEntry*) {} |
| |
| } // namespace |
| |
| namespace utils { |
| |
| // |strip_partition| attempts to remove the partition number from the result. |
| base::FilePath GetRootDevice(bool strip_partition) { |
| char buf[PATH_MAX]; |
| int ret = rootdev(buf, PATH_MAX, /*use_slave=*/true, strip_partition); |
| if (ret == 0) { |
| return base::FilePath(buf); |
| } else { |
| return base::FilePath(); |
| } |
| } |
| |
| bool ReadFileToInt(const base::FilePath& path, int* value) { |
| std::string str; |
| if (!base::ReadFileToString(path, &str)) { |
| return false; |
| } |
| base::TrimWhitespaceASCII(str, base::TRIM_ALL, &str); |
| return base::StringToInt(str, value); |
| } |
| |
| void Reboot() { |
| brillo::ProcessImpl proc; |
| proc.AddArg("/sbin/shutdown"); |
| proc.AddArg("-r"); |
| proc.AddArg("now"); |
| int ret = proc.Run(); |
| if (ret == 0) { |
| // Wait for reboot to finish (it's an async call). |
| sleep(60 * 60 * 24); |
| } |
| // If we've reached here, reboot (probably) failed. |
| LOG(ERROR) << "Requesting reboot failed with failure code " << ret; |
| } |
| |
| void Restorecon(libstorage::Platform* platform_, |
| const base::FilePath& path, |
| const std::vector<base::FilePath>& exclude, |
| bool is_recursive, |
| bool set_digests) { |
| union selinux_callback cb; |
| cb.func_log = RestoreConLogCallback; |
| selinux_set_callback(SELINUX_CB_LOG, cb); |
| |
| if (!exclude.empty()) { |
| std::vector<const char*> exclude_cstring(exclude.size()); |
| std::transform(exclude.begin(), exclude.end(), exclude_cstring.begin(), |
| [](const base::FilePath& path) -> const char* { |
| return path.value().c_str(); |
| }); |
| exclude_cstring.push_back(NULL); |
| // We need to exclude directories because restoring context could |
| // mislabel files if the encrypted filename happens to match something |
| // or could increase boot time. |
| selinux_restorecon_set_exclude_list(exclude_cstring.data()); |
| } |
| |
| const unsigned int recurse_flags = |
| (set_digests |
| ? SELINUX_RESTORECON_RECURSE |
| : SELINUX_RESTORECON_RECURSE | SELINUX_RESTORECON_SKIP_DIGEST); |
| |
| const unsigned int base_flags = |
| (is_recursive ? recurse_flags : 0) | SELINUX_RESTORECON_REALPATH; |
| |
| selinux_restorecon(path.value().c_str(), base_flags); |
| } |
| |
| int GetPartitionNumber(const base::FilePath& drive_name, |
| const std::string& partition_label) { |
| // TODO(C++20): Switch to aggregate initialization once we require C++20. |
| CgptFindParams params = {}; |
| params.set_label = 1; |
| params.label = partition_label.c_str(); |
| params.drive_name = drive_name.value().c_str(); |
| params.show_fn = &CgptFindShowFunctionNoOp; |
| CgptFind(¶ms); |
| if (params.hits != 1) { |
| LOG(ERROR) << "Could not find partition number for partition " |
| << partition_label; |
| return -1; |
| } |
| return params.match_partnum; |
| } |
| |
| bool GetDevicePathComponents(const base::FilePath& device, |
| std::string* base_device_out, |
| int* partition_out) { |
| if (!partition_out || !base_device_out) |
| return false; |
| const std::string& path = device.value(); |
| |
| size_t suffix_index = path.length(); |
| size_t last_non_numeric = |
| path.find_last_not_of("0123456789", suffix_index - 1); |
| |
| // If there are no non-numeric characters, this is a malformed device. |
| if (last_non_numeric == std::string::npos) { |
| return false; |
| } |
| |
| std::string partition_number_string = |
| path.substr(last_non_numeric + 1, suffix_index - (last_non_numeric + 1)); |
| int partition_number; |
| if (!base::StringToInt(partition_number_string, &partition_number)) { |
| return false; |
| } |
| *partition_out = partition_number; |
| *base_device_out = path.substr(0, last_non_numeric + 1); |
| return true; |
| } |
| |
| bool ReadPartitionMetadata(const base::FilePath& disk, |
| int partition_number, |
| bool* successful_out, |
| int* priority_out) { |
| if (!successful_out || !priority_out) |
| return false; |
| // TODO(C++20): Switch to aggregate initialization once we require C++20. |
| CgptAddParams params = {}; |
| params.drive_name = disk.value().c_str(); |
| params.partition = partition_number; |
| if (CgptGetPartitionDetails(¶ms) == CGPT_OK) { |
| *successful_out = params.successful; |
| *priority_out = params.priority; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| void EnsureKernelIsBootable(const base::FilePath root_disk, |
| int kernel_partition) { |
| bool successful = false; |
| int priority = 0; |
| if (!ReadPartitionMetadata(root_disk, kernel_partition, &successful, |
| &priority)) { |
| LOG(ERROR) << "Failed to read partition metadata from partition " |
| << kernel_partition << " on disk " << root_disk.value(); |
| // If we couldn't read, we'll err on the side of caution and try to set the |
| // successful bit and priority anyways. |
| } |
| |
| if (!successful) { |
| // TODO(C++20): Switch to aggregate initialization once we require C++20. |
| CgptAddParams params = {}; |
| params.partition = kernel_partition; |
| params.set_successful = 1; |
| params.drive_name = root_disk.value().c_str(); |
| params.successful = 1; |
| if (CgptAdd(¶ms) != CGPT_OK) { |
| LOG(ERROR) << "Failed to set sucessful for active kernel partition: " |
| << kernel_partition; |
| } |
| } |
| |
| if (priority < 1) { |
| // TODO(C++20): Switch to aggregate initialization once we require C++20. |
| CgptPrioritizeParams params = {}; |
| params.set_partition = kernel_partition; |
| params.drive_name = root_disk.value().c_str(); |
| // When reordering kernel priorities to set the active kernel to highest, |
| // use 3 as the highest value. Since there are only 3 kernel partitions, |
| // this ensures that all priorities are unique. |
| params.max_priority = 3; |
| if (CgptPrioritize(¶ms) != CGPT_OK) { |
| LOG(ERROR) << "Failed to prioritize active kernel partition: " |
| << kernel_partition; |
| } |
| } |
| |
| sync(); |
| } |
| |
| } // namespace utils |