Skip to content

Commit

Permalink
libebgenv: only probe config on root dev (opt-out)
Browse files Browse the repository at this point in the history
This patch limits the probing of ebg config environments to the block
device the bootloader was started on. This setting can be overwritten by
configuring using the new API ebg_set_opt before creating / opening the
environment.

The boot block device is queried using the systemd boot loader
interface, or more precisely the LoaderDevicePartUUID efi variable. In
case this variable is not set, or the value does not point to a valid
device, all devices are searched.

We believe that changing the default to "this device" only is a
reasonable decision, because:

- parsing only the current device makes updates more robust, as no
  environments from other disks (e.g. USB drives) are considered.
- more efficient as no hanging / slow devices are queried
- less errors in the syslog

To support the recovery use-case, tools that use libebgenv can
explicitly enable the search on all devices.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
  • Loading branch information
fmoessbauer authored and jan-kiszka committed Oct 18, 2023
1 parent 7c90e82 commit ffbd35f
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 24 deletions.
3 changes: 3 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ int main(void)
{
ebgenv_t e;

// optionally set ebg options before creating the environment
ebg_set_opt_bool(EBG_OPT_PROBE_ALL_DEVICES, true);

ebg_env_create_new(&e);
ebg_env_set(&e, "kernelfile", "vmlinux-new");
ebg_env_set(&e, "kernelparams", "root=/dev/bootdevice");
Expand Down
30 changes: 29 additions & 1 deletion env/env_api.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
* EFI Boot Guard
*
* Copyright (c) Siemens AG, 2017
* Copyright (c) Siemens AG, 2017-2023
*
* Authors:
* Andreas Reichel <andreas.reichel.ext@siemens.com>
* Felix Moessbauer <felix.moessbauer@siemens.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
Expand All @@ -16,6 +17,9 @@
#include "ebgenv.h"
#include "uservars.h"

/* global EBG options */
ebgenv_opts_t ebgenv_opts;

/* UEFI uses 16-bit wide unicode strings.
* However, wchar_t support functions are fixed to 32-bit wide
* characters in glibc. This code is compiled with
Expand Down Expand Up @@ -56,6 +60,30 @@ char16_t *str8to16(char16_t *buffer, const char *src)
return tmp;
}

int ebg_set_opt_bool(ebg_opt_t opt, bool value)
{
switch (opt) {
case EBG_OPT_PROBE_ALL_DEVICES:
ebgenv_opts.search_all_devices = value;
break;
default:
return EINVAL;
}
return 0;
}

int ebg_get_opt_bool(ebg_opt_t opt, bool *value)
{
switch (opt) {
case EBG_OPT_PROBE_ALL_DEVICES:
*value = ebgenv_opts.search_all_devices;
break;
default:
return EINVAL;
}
return 0;
}

void ebg_beverbose(ebgenv_t __attribute__((unused)) *e, bool v)
{
bgenv_be_verbose(v);
Expand Down
4 changes: 3 additions & 1 deletion env/env_api_fat.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "ebgpart.h"

bool bgenv_verbosity = false;
extern ebgenv_opts_t ebgenv_opts;

EBGENVKEY bgenv_str2enum(char *key)
{
Expand Down Expand Up @@ -174,7 +175,8 @@ bool bgenv_init(void)
return true;
}
/* enumerate all config partitions */
if (!probe_config_partitions(config_parts)) {
if (!probe_config_partitions(config_parts,
ebgenv_opts.search_all_devices)) {
VERBOSE(stderr, "Error finding config partitions.\n");
return false;
}
Expand Down
90 changes: 87 additions & 3 deletions env/env_config_partitions.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
* EFI Boot Guard
*
* Copyright (c) Siemens AG, 2017
* Copyright (c) Siemens AG, 2017-2023
*
* Authors:
* Andreas Reichel <andreas.reichel.ext@siemens.com>
* Felix Moessbauer <felix.moessbauer@siemens.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
Expand All @@ -17,17 +18,100 @@
#include "env_config_partitions.h"
#include "env_config_file.h"

bool probe_config_partitions(CONFIG_PART *cfgpart)
#define LOADER_PROT_VENDOR_GUID "4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
#define GUID_LEN_CHARS 36
#define EFI_ATTR_LEN_IN_WCHAR 2
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

/**
* Read the ESP UUID from the efivars. This only works if the bootloader
* implements the LoaderDevicePartUUID from the systemd bootloader interface
* spec. Returns a device name (e.g. sda) or NULL. The returned string needs
* to be freed by the caller.
*/
static char *get_rootdev_from_efi(void)
{
const char *vendor_guid = LOADER_PROT_VENDOR_GUID;
const char *basepath = "/sys/firmware/efi/efivars/";
char part_uuid[GUID_LEN_CHARS + 1];
FILE *f = 0;
union {
char aschar[512];
char16_t aswchar[256];
} buffer;

// read LoaderDevicePartUUID efi variable
snprintf(buffer.aschar, sizeof(buffer.aschar),
"%s/LoaderDevicePartUUID-%s", basepath, vendor_guid);
if (!(f = fopen(buffer.aschar, "r"))) {
VERBOSE(stderr, "Error, cannot access efi var at %s.\n",
buffer.aschar);
return NULL;
}
const size_t readnb = fread(buffer.aswchar, sizeof(*buffer.aswchar),
ARRAY_SIZE(buffer.aswchar), f);
if (readnb != GUID_LEN_CHARS + EFI_ATTR_LEN_IN_WCHAR) {
VERBOSE(stderr, "Data in LoaderDevicePartUUID not valid\n");
fclose(f);
return NULL;
}
fclose(f);

// convert char16_t to char and lowercase uuid, skip attributes
for (int i = 0; i < GUID_LEN_CHARS; i++) {
part_uuid[i] = tolower(
(char)buffer.aswchar[i + EFI_ATTR_LEN_IN_WCHAR]);
}
part_uuid[GUID_LEN_CHARS] = '\0';

// resolve device based on partition uuid
snprintf(buffer.aschar, sizeof(buffer.aschar),
"/dev/disk/by-partuuid/%s", part_uuid);
char *devpath = realpath(buffer.aschar, NULL);
if (!devpath) {
VERBOSE(stderr, "Error, no disk in %s\n", buffer.aschar);
return NULL;
}
VERBOSE(stdout, "resolved ESP to %s\n", devpath);
// get disk name from path
char *partition = strrchr(devpath, '/') + 1;

// resolve parent device. As the ESP must be a primary partition, the
// parent is the block device.
snprintf(buffer.aschar, sizeof(buffer.aschar), "/sys/class/block/%s/..",
partition);
free(devpath);

// resolve to e.g. /sys/devices/pci0000:00/0000:00:1f.2/<...>/block/sda
char *blockpath = realpath(buffer.aschar, NULL);
char *_blockdev = strrchr(blockpath, '/') + 1;
char *blockdev = strdup(_blockdev);
free(blockpath);
return blockdev;
}

bool probe_config_partitions(CONFIG_PART *cfgpart, bool search_all_devices)
{
PedDevice *dev = NULL;
char devpath[4096];
char *rootdev = NULL;
int count = 0;

if (!cfgpart) {
return false;
}

ped_device_probe_all();
if (!search_all_devices) {
if (!(rootdev = get_rootdev_from_efi())) {
VERBOSE(stderr, "Warning, could not determine root "
"dev. Search on all devices\n");
} else {
VERBOSE(stdout, "Limit probing to disk %s\n", rootdev);
}
}

ped_device_probe_all(rootdev);
free(rootdev);

while ((dev = ped_device_get_next(dev))) {
printf_debug("Device: %s\n", dev->model);
Expand Down
23 changes: 23 additions & 0 deletions include/ebgenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,34 @@

#define USERVAR_STANDARD_TYPE_MASK ((1ULL << 32) - 1)

typedef struct {
bool search_all_devices;
} ebgenv_opts_t;

typedef struct {
void *bgenv;
void *gc_registry;
ebgenv_opts_t opts;
} ebgenv_t;

typedef enum { EBG_OPT_PROBE_ALL_DEVICES } ebg_opt_t;

/**
* @brief Set a global EBG option. Call before creating the ebg env.
* @param opt option to set
* @param value option value
* @return 0 on success
*/
int ebg_set_opt_bool(ebg_opt_t opt, bool value);

/**
* @brief Get a global EBG option.
* @param opt option to set
* @param value out variable to retrieve option value
* @return 0 on success
*/
int ebg_get_opt_bool(ebg_opt_t opt, bool *value);

/** @brief Tell the library to output information for the user.
* @param e A pointer to an ebgenv_t context.
* @param v A boolean to set verbosity.
Expand Down
2 changes: 1 addition & 1 deletion include/ebgpart.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ typedef struct _PedDisk {
PedPartition *part_list;
} PedDisk;

void ped_device_probe_all(void);
void ped_device_probe_all(char *rootdev);
PedDevice *ped_device_get_next(const PedDevice *dev);
PedDisk *ped_disk_new(const PedDevice *dev);
PedPartition *ped_disk_next_partition(const PedDisk *pd,
Expand Down
2 changes: 1 addition & 1 deletion include/env_config_partitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
#include <stdbool.h>
#include "env_api.h"

bool probe_config_partitions(CONFIG_PART *cfgpart);
bool probe_config_partitions(CONFIG_PART *cfgpart, bool search_all_devices);
4 changes: 4 additions & 0 deletions tools/bg_envtools.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ error_t parse_common_opt(int key, char *arg, bool compat_mode,
bool found = false;
int i;
switch (key) {
case 'A':
found = true;
arguments->search_all_devices = true;
break;
case 'f':
found = true;
free(arguments->envfilepath);
Expand Down
4 changes: 4 additions & 0 deletions tools/bg_envtools.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"Set environment partition to use. If no partition is " \
"specified, the one with the smallest revision value above " \
"zero is selected.") \
, OPT("all", 'A', 0, 0, \
"search on all devices instead of root device only") \
, OPT("verbose", 'v', 0, 0, "Be verbose") \
, OPT("version", 'V', 0, 0, "Print version")

Expand All @@ -46,6 +48,8 @@ struct arguments_common {
* was specified. */
int which_part;
bool part_specified;
/* inspect all devices for bootenvs instead of current root only */
bool search_all_devices;
};

int parse_int(char *arg);
Expand Down
3 changes: 3 additions & 0 deletions tools/bg_printenv.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ error_t bg_printenv(int argc, char **argv)
}

/* not in file mode */
if (arguments.common.search_all_devices) {
ebg_set_opt_bool(EBG_OPT_PROBE_ALL_DEVICES, true);
}
if (!bgenv_init()) {
fprintf(stderr, "Error initializing FAT environment.\n");
return 1;
Expand Down
4 changes: 4 additions & 0 deletions tools/bg_setenv.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ static error_t journal_add_action(BGENV_TASK task, char *key, uint64_t type,
static void journal_process_action(BGENV *env, struct env_action *action)
{
ebgenv_t e;
memset(&e, 0, sizeof(ebgenv_t));

switch (action->task) {
case ENV_TASK_SET:
Expand Down Expand Up @@ -418,6 +419,9 @@ error_t bg_setenv(int argc, char **argv)
}

/* not in file mode */
if (arguments.common.search_all_devices) {
ebg_set_opt_bool(EBG_OPT_PROBE_ALL_DEVICES, true);
}
if (!bgenv_init()) {
fprintf(stderr, "Error initializing FAT environment.\n");
return 1;
Expand Down
27 changes: 16 additions & 11 deletions tools/ebgpart.c
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,9 @@ static int get_major_minor(char *filename, unsigned int *major, unsigned int *mi
return 0;
}

void ped_device_probe_all(void)
void ped_device_probe_all(char *rootdev)
{
struct dirent *sysblockfile;
struct dirent *sysblockfile = NULL;
char fullname[DEV_FILENAME_LEN+16];

DIR *sysblockdir = opendir(SYSBLOCKDIR);
Expand All @@ -429,16 +429,21 @@ void ped_device_probe_all(void)

/* get all files from sysblockdir */
do {
sysblockfile = readdir(sysblockdir);
if (!sysblockfile) {
break;
}
if (strcmp(sysblockfile->d_name, ".") == 0 ||
strcmp(sysblockfile->d_name, "..") == 0) {
continue;
char *devname = rootdev;
if (!rootdev) {
sysblockfile = readdir(sysblockdir);
if (!sysblockfile) {
break;
}
if (strcmp(sysblockfile->d_name, ".") == 0 ||
strcmp(sysblockfile->d_name, "..") == 0) {
continue;
}
devname = sysblockfile->d_name;
}

(void)snprintf(fullname, sizeof(fullname), "/sys/block/%s/dev",
sysblockfile->d_name);
devname);
/* Get major and minor revision from /sys/block/sdX/dev */
unsigned int fmajor, fminor;
if (get_major_minor(fullname, &fmajor, &fminor) < 0) {
Expand All @@ -449,7 +454,7 @@ void ped_device_probe_all(void)
fmajor, fminor, fullname);
/* Check if this file is really in the dev directory */
(void)snprintf(fullname, sizeof(fullname), "%s/%s", DEVDIR,
sysblockfile->d_name);
devname);
struct stat statbuf;
if (stat(fullname, &statbuf) == -1) {
/* Node with same name not found in /dev, thus search
Expand Down

0 comments on commit ffbd35f

Please sign in to comment.