blob: d18ed626a2e92289c7cdba76351bd8da6ca99e3a [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "installer/efivar.h"
#include <cstring>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
// TODO(tbrandston): upstream extern "C" to efivar.
// https://github.com/rhboot/efivar/issues/205
extern "C" {
#include "efivar/efiboot.h"
}
namespace {
// Wrapper around the libefivar error logging interface.
// libefivar stores a list of errors that it encounters, and lets you access
// them by index. The list is cleared when certain calls succeed, but successive
// errors can accumulate.
void LogEfiErrors() {
uint32_t index = 0;
char* filename = nullptr;
char* function = nullptr;
int line = 0;
char* message = nullptr;
int error = 0;
while (true) {
int rc =
efi_error_get(index, &filename, &function, &line, &message, &error);
if (rc == -1) {
LOG(ERROR) << "programmer error, invalid args.";
return;
} else if (rc == 0) {
// No more errors, for now.
break;
}
// We don't know here whether it should be treated as a warning or an error,
// so we'll call everything a warning and let further logging clarify.
LOG(WARNING) << "efi error " << index << ": " << filename << ":" << line
<< ":" << function << " rc=" << rc << " " << message << ": "
<< std::strerror(error);
index += 1;
}
// Clear the errors we've just printed, so we don't hit them again next time.
efi_error_clear();
}
} // namespace
const uint32_t kBootVariableAttributes = EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS |
EFI_VARIABLE_NON_VOLATILE;
std::string EfiVarInterface::LoadoptDesc(uint8_t* const data,
size_t data_size) {
efi_load_option* load_opt = reinterpret_cast<efi_load_option*>(data);
// Memory returned by efi_loadopt_desc doesn't need to be freed.
// Internally it frees the previously loaded description each time you
// load a new one.
const unsigned char* unsigned_desc = efi_loadopt_desc(load_opt, data_size);
// Store the unsigned chars in signed chars for ease of use outside this api.
return reinterpret_cast<const char*>(unsigned_desc);
}
std::vector<uint8_t> EfiVarInterface::LoadoptPath(uint8_t* const data,
size_t data_size) {
efi_load_option* load_opt = reinterpret_cast<efi_load_option*>(data);
// This path_data is just a pointer into `data`, not separately allocated.
efidp path_data = efi_loadopt_path(load_opt, data_size);
const ssize_t path_data_size = efi_loadopt_pathlen(load_opt, data_size);
// Copy the path data into a vector.
return std::vector<uint8_t>(
reinterpret_cast<uint8_t*>(path_data),
reinterpret_cast<uint8_t*>(path_data) + path_data_size);
}
bool EfiVarInterface::LoadoptCreate(uint32_t loadopt_attributes,
std::vector<uint8_t>& efidp_data,
std::string& description,
std::vector<uint8_t>* data) {
efidp device_path = reinterpret_cast<efidp>(efidp_data.data());
unsigned char* description_data =
reinterpret_cast<unsigned char*>(description.data());
// Passing a size of 0 will simply return the sum of the lengths of the
// relevant arguments, which tells us how much space to allocate.
const ssize_t entry_data_size =
efi_loadopt_create(nullptr, 0, loadopt_attributes, device_path,
efidp_data.size(), description_data,
// optional, for EDD10 or something?
nullptr, 0);
data->resize(entry_data_size);
const ssize_t rv = efi_loadopt_create(
data->data(), entry_data_size, loadopt_attributes, device_path,
efidp_data.size(), description_data, nullptr, 0);
if (rv < 0) {
LogEfiErrors();
LOG(ERROR) << "Error formatting data for efi variable.\n"
<< "attributes: " << loadopt_attributes << "\n"
<< "efidp_data: "
<< base::HexEncode(efidp_data.data(), efidp_data.size()) << "\n"
<< "description: " << description << "\n";
return false;
}
return true;
}
bool EfiVarImpl::EfiVariablesSupported() {
return efi_variables_supported();
}
base::Optional<std::string> EfiVarImpl::GetNextVariableName() {
efi_guid_t* ignored_guid = nullptr;
char* name = nullptr;
// efi_get_next_variable_name repeatedly returns the same static char[].
if (efi_get_next_variable_name(&ignored_guid, &name) < 0) {
LogEfiErrors();
return base::nullopt;
}
if (!name) {
return base::nullopt;
}
return std::string(name);
}
bool EfiVarImpl::GetVariable(const std::string& name,
Bytes& data,
size_t* data_size) {
// efi_get_variable will malloc some space and store it in `data`,
// so we have to make sure it gets freed.
uint8_t* data_ptr;
// All the variables we manage have well defined attributes by the efi spec,
// so we can safely ignore these -- if they're somehow different we'd want to
// fix them.
uint32_t ignored_attributes;
if (efi_get_variable(EFI_GLOBAL_GUID, name.c_str(), &data_ptr, data_size,
&ignored_attributes) < 0) {
LogEfiErrors();
LOG(ERROR) << "Error getting '" << name << "'";
// Okay to return without freeing data if ret < 0 (at least in the
// current (v37) efivar implementation ...).
return false;
}
// Pass data out, ensuring it gets freed when it's no longer needed.
data.reset(data_ptr);
return true;
}
bool EfiVarImpl::SetVariable(const std::string& name,
uint32_t attributes,
std::vector<uint8_t>& data) {
if (efi_set_variable(EFI_GLOBAL_GUID, name.c_str(), data.data(), data.size(),
attributes,
// mode
0644) < 0) {
LogEfiErrors();
LOG(ERROR) << "Error setting '" << name
<< "' data: " << base::HexEncode(data.data(), data.size());
return false;
}
return true;
}
bool EfiVarImpl::DelVariable(const std::string& name) {
if (efi_del_variable(EFI_GLOBAL_GUID, name.c_str()) < 0) {
LogEfiErrors();
LOG(ERROR) << "Error deleting '" << name << "'";
return false;
}
return true;
}
bool EfiVarImpl::GenerateFileDevicePathFromEsp(
const std::string& device_path,
int esp_partition,
const std::string& boot_file,
std::vector<uint8_t>& efidp_data) {
// Check how much capacity we'll need in efidp by passing null/0 first.
const ssize_t required_size = efi_generate_file_device_path_from_esp(
nullptr, 0, device_path.c_str(), esp_partition, boot_file.c_str(),
EFIBOOT_ABBREV_HD);
if (required_size < 0) {
LogEfiErrors();
LOG(ERROR) << "Could not generate device path. "
<< "efi_generate_file_device_path_from_esp returned: "
<< required_size;
return false;
}
efidp_data.resize(required_size);
const ssize_t rv = efi_generate_file_device_path_from_esp(
efidp_data.data(), required_size, device_path.c_str(), esp_partition,
boot_file.c_str(), EFIBOOT_ABBREV_HD);
if (rv < 0) {
LogEfiErrors();
LOG(ERROR) << "Could not generate device path. "
<< "efi_generate_file_device_path_from_esp returned: " << rv;
return false;
}
return true;
}