From 03aa8f711519f1199da141d17f32fac7eae516d7 Mon Sep 17 00:00:00 2001 From: Eric Curtin Date: Wed, 5 Apr 2023 17:24:36 +0100 Subject: [PATCH] Add ostree=aboot for signed Android Boot Images Some kernel images are delivered in a signed kernel + cmdline + initramfs + dtb blob. When this is added to the commit server side, only after this do you know what the cmdline is, this creates a recursion issue. To avoid this, in the case where we have ostree=aboot karg set, do the bls parsing in the initramfs instead, so we can take advantage of existing bls logic. --- src/switchroot/ostree-mount-util.h | 175 ++++++++++++++++++++++- src/switchroot/ostree-prepare-root.c | 6 +- src/switchroot/ostree-system-generator.c | 5 +- 3 files changed, 180 insertions(+), 6 deletions(-) diff --git a/src/switchroot/ostree-mount-util.h b/src/switchroot/ostree-mount-util.h index 92bc802786..1294701b08 100644 --- a/src/switchroot/ostree-mount-util.h +++ b/src/switchroot/ostree-mount-util.h @@ -21,17 +21,20 @@ #ifndef __OSTREE_MOUNT_UTIL_H_ #define __OSTREE_MOUNT_UTIL_H_ +#include #include +#include +#include +#include #include +#include #include -#include +#include #include -#include -#include -#include #define INITRAMFS_MOUNT_VAR "/run/ostree/initramfs-mount-var" #define _OSTREE_SYSROOT_READONLY_STAMP "/run/ostree-sysroot-ro.stamp" +#define ABOOT_KARG "aboot" static inline int path_is_on_readonly_fs (const char *path) @@ -72,6 +75,26 @@ read_proc_cmdline (void) return cmdline; } +static inline void +free_char (char **to_free) +{ + free (*to_free); +} + +static inline void +close_dir (DIR **dir) +{ + if (*dir) + closedir (*dir); +} + +static inline void +close_file (FILE **f) +{ + if (*f) + fclose (*f); +} + static inline char * read_proc_cmdline_ostree (void) { @@ -106,6 +129,150 @@ read_proc_cmdline_ostree (void) return ret; } +static inline void +cpy_and_null (char **dest, char **src) +{ + *dest = *src; + *src = NULL; +} + +/* If the key matches the start of the line, copy line to out + */ +static inline bool +cpy_if_key_match (char **line, const char *key, char **out) +{ + /* There should only be one of each key per BLS file, so check for NULL, we + * should only parse the first occurance of a key, if there's two, it's a + * malformed BLS file + */ + if (!*out && strstr (*line, key) == *line) + { + cpy_and_null (out, line); + return true; + } + + return false; +} + +static inline bool +has_suffix (const char *str, const char *suffix) +{ + if (!str || !suffix) + return false; + + const size_t str_len = strlen (str); + const size_t suffix_len = strlen (suffix); + if (str_len < suffix_len) + return false; + + return !strcmp (str + str_len - suffix_len, suffix); +} + +/* On completion version and options will be set to new values if the version + * is more recent. Will loop line through line on the passed in open FILE. + */ +static inline void +copy_if_higher_version (FILE *f, char **version, char **options) +{ + char __attribute__ ((cleanup (free_char))) *line = NULL; + char __attribute__ ((cleanup (free_char))) *version_local = NULL; + char __attribute__ ((cleanup (free_char))) *options_local = NULL; + char __attribute__ ((cleanup (free_char))) *linux_local = NULL; + /* Note getline() will reuse the previous buffer when not zero */ + for (size_t len = 0; getline (&line, &len, f) != -1;) + { + /* This is an awful hack to avoid depending on GLib in the + * initramfs right now. + */ + if (cpy_if_key_match (&line, "version ", &version_local)) + continue; + + if (cpy_if_key_match (&line, "options ", &options_local)) + continue; + + if (cpy_if_key_match (&line, "linux ", &linux_local)) + continue; + } + + /* The case where we have no version set yet */ + if (!*version + || strverscmp (version_local + sizeof ("version"), (*version) + sizeof ("version")) > 0) + { + struct utsname buf; + uname (&buf); + strtok (linux_local + sizeof ("linux"), " \t\r\n"); + if (!has_suffix (linux_local, buf.release)) + return; + + cpy_and_null (version, &version_local); + cpy_and_null (options, &options_local); + } + + return; +} + +static inline char * +parse_ostree_from_options (const char *options) +{ + if (options) + { + options += sizeof ("options"); + char *start_of_ostree = strstr (options, "ostree="); + if (start_of_ostree > options) + { + start_of_ostree += sizeof ("ostree"); + /* trim everything to the right */ + strtok (start_of_ostree, " \t\r\n"); + return strdup (start_of_ostree); + } + } + + return NULL; +} + +/* This function is for boot arrangements where it is not possible to use a + * karg/cmdline, this is the case when the cmdline is part of the signed + * boot image, alternatively this function takes the karg from the bls entry + * which will have the correct ostree= karg set. This bls entry is not parsed + * from the bootloader but from the initramfs instead. + */ +static inline char * +bls_parser_get_ostree_option (const char *sysroot) +{ + char out[PATH_MAX] = ""; + int written = snprintf (out, PATH_MAX, "%s/boot/loader/entries", sysroot); + DIR __attribute__ ((cleanup (close_dir))) *dir = opendir (out); + if (!dir) + { + fprintf (stderr, "opendir(\"%s\") failed with %d\n", out, errno); + return NULL; + } + + char __attribute__ ((cleanup (free_char))) *version = NULL; + char __attribute__ ((cleanup (free_char))) *options = NULL; + for (struct dirent *ent = 0; (ent = readdir (dir));) + { + if (ent->d_name[0] == '.') + continue; + + if (!has_suffix (ent->d_name, ".conf")) + continue; + + snprintf (out + written, PATH_MAX - written, "/%s", ent->d_name); + + FILE __attribute__ ((cleanup (close_file))) *f = fopen (out, "r"); + if (!f) + { + fprintf (stderr, "fopen(\"%s\", \"r\") failed with %d\n", out, errno); + continue; + } + + copy_if_higher_version (f, &version, &options); + } + + return parse_ostree_from_options (options); +} + /* This is an API for other projects to determine whether or not the * currently running system is ostree-controlled. */ diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index a5fbc8a810..6f9620eee1 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -124,9 +124,13 @@ resolve_deploy_path (const char * root_mountpoint) { char destpath[PATH_MAX]; struct stat stbuf; - char *ostree_target, *deploy_path; + char __attribute__ ((cleanup (free_char))) *ostree_target; + char *deploy_path; ostree_target = read_proc_cmdline_ostree (); + if (!strcmp (ostree_target, ABOOT_KARG)) + ostree_target = bls_parser_get_ostree_option (root_mountpoint); + if (!ostree_target) errx (EXIT_FAILURE, "No OSTree target; expected ostree=/ostree/boot.N/..."); diff --git a/src/switchroot/ostree-system-generator.c b/src/switchroot/ostree-system-generator.c index bd0901bcff..7c7fc0d535 100644 --- a/src/switchroot/ostree-system-generator.c +++ b/src/switchroot/ostree-system-generator.c @@ -62,7 +62,10 @@ main(int argc, char *argv[]) * exit so that we don't error, but at the same time work where switchroot * is PID 1 (and so hasn't created /run/ostree-booted). */ - char *ostree_cmdline = read_proc_cmdline_ostree (); + char __attribute__ ((cleanup (free_char))) *ostree_cmdline = read_proc_cmdline_ostree (); + if (!strcmp (ostree_cmdline, ABOOT_KARG)) + ostree_cmdline = bls_parser_get_ostree_option ("/sysroot"); + if (!ostree_cmdline) exit (EXIT_SUCCESS);