From ffadf85f74fe9e1c710e591e051c55e2ab245379 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 | 129 +++++++++++++++++++++++ src/switchroot/ostree-prepare-root.c | 6 +- src/switchroot/ostree-system-generator.c | 5 +- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/switchroot/ostree-mount-util.h b/src/switchroot/ostree-mount-util.h index 92bc802786..231ee476ca 100644 --- a/src/switchroot/ostree-mount-util.h +++ b/src/switchroot/ostree-mount-util.h @@ -21,6 +21,7 @@ #ifndef __OSTREE_MOUNT_UTIL_H_ #define __OSTREE_MOUNT_UTIL_H_ +#include #include #include #include @@ -32,6 +33,7 @@ #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 +74,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 +128,113 @@ read_proc_cmdline_ostree (void) return ret; } +/* If the key matches the start of the line, strip the key from the line just + * to get the value + */ +static inline bool +rm_key_if_match(char *line, const char *key, const size_t key_len, char *out) { + if (strstr (line, key) == line) + { + char* value = line + key_len; + strcpy (out, value); + return true; + } + + return false; +} + +/* 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 int +copy_if_higher_version(FILE *f, char *version, char *options) { + char __attribute__ ((cleanup(free_char))) *line = NULL; + char version_local[PATH_MAX] = ""; + char options_local[PATH_MAX] = ""; + /* Note getline() will reuse the previous buffer */ + 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 (rm_key_if_match (line, "version ", sizeof("version"), version_local)) + { + if (options_local[1]) + break; + + continue; + } + + if (rm_key_if_match (line, "options ", sizeof("options"), options_local)) + { + if (version_local[1]) + break; + + continue; + } + } + + const int ret = strverscmp (version_local, version); + if (ret > 0) + { + strcpy (version, version_local); + strcpy (options, options_local); + } + + return ret; +} + +/* 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 strdup(""); + } + + char version[PATH_MAX] = ""; + char options[PATH_MAX] = ""; + for (struct dirent *ent = 0; (ent = readdir (dir)); ) + { + if (ent->d_name[0] == '.') + 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); + } + + if (options[0]) + { + char* start_of_ostree = strstr (options, "ostree="); + if (start_of_ostree > options) + { + start_of_ostree += sizeof ("ostree"); + strtok (start_of_ostree, " \t\r\n"); // trim everything to the right + return strdup (start_of_ostree); + } + } + + return strdup (""); +} + /* 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..5d6cf86844 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..4a4b5c3ec5 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);