bootloader: Add an aboot (Android) bootloader backend
authorEric Curtin <ecurtin@redhat.com>
Tue, 13 Dec 2022 14:12:25 +0000 (14:12 +0000)
committerEric Curtin <ecurtin@redhat.com>
Thu, 15 Dec 2022 16:09:08 +0000 (16:09 +0000)
aboot is special in that it packages kernel, initrd, cmdline, dtb and
signature one combined image (similar to upcoming unified kernel
images). This is then loaded as an image into an aboot partition.

This image is signed by the OS vendor and covers everything in the
image. So locally on the deployed system it should not be possible to
boot an unsigned image (unless signature checking is turned off).

We call a shell script aboot-deploy when it is required to write a new
image to the aboot partition (a file typically starting with aboot and
ending in .img extension). This shell script may also read some
configurations from a .cfg file.

Signed-off-by: Eric Curtin <ecurtin@redhat.com>
Makefile-libostree.am
rust-bindings/sys/src/lib.rs
src/libostree/ostree-bootloader-aboot.c [new file with mode: 0644]
src/libostree/ostree-bootloader-aboot.h [new file with mode: 0644]
src/libostree/ostree-repo-private.h
src/libostree/ostree-sysroot-deploy.c
src/libostree/ostree-sysroot.c

index cf54d2b00f21e83a6274af917d960ce1e0f7d12d..4b8a46f5947f53889e65873451e3e1593a67e92a 100644 (file)
@@ -111,6 +111,8 @@ libostree_1_la_SOURCES = \
        src/libostree/ostree-deployment.c \
        src/libostree/ostree-bootloader.h \
        src/libostree/ostree-bootloader.c \
+       src/libostree/ostree-bootloader-aboot.h \
+       src/libostree/ostree-bootloader-aboot.c \
        src/libostree/ostree-bootloader-grub2.h \
        src/libostree/ostree-bootloader-grub2.c \
        src/libostree/ostree-bootloader-zipl.h \
index f71a18dec25f22e88069e8fd7ebd710d7161bc0b..9c270d3d7bd48eef344ca789f2eacf51876e8a6e 100644 (file)
@@ -333,6 +333,14 @@ pub struct _OstreeBootloaderSyslinux {
 
 pub type OstreeBootloaderSyslinux = *mut _OstreeBootloaderSyslinux;
 
+#[repr(C)]
+pub struct _OstreeBootloaderAboot {
+    _data: [u8; 0],
+    _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
+}
+
+pub type OstreeBootloaderAboot = *mut _OstreeBootloaderAboot;
+
 #[repr(C)]
 pub struct _OstreeBootloaderUboot {
     _data: [u8; 0],
diff --git a/src/libostree/ostree-bootloader-aboot.c b/src/libostree/ostree-bootloader-aboot.c
new file mode 100644 (file)
index 0000000..9370aa6
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 Eric Curtin <ericcurtin17@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ostree-sysroot-private.h"
+#include "ostree-bootloader-aboot.h"
+#include "ostree-deployment-private.h"
+#include "ostree-libarchive-private.h"
+#include "otutil.h"
+#include <sys/mount.h>
+
+#include <string.h>
+
+/* This is specific to aboot and zipl today, but in the future we could also
+ * use it for the grub2-mkconfig case.
+ */
+static const char aboot_requires_execute_path[] = "boot/ostree-bootloader-update.stamp";
+
+struct _OstreeBootloaderAboot
+{
+  GObject       parent_instance;
+
+  OstreeSysroot  *sysroot;
+};
+
+typedef GObjectClass OstreeBootloaderAbootClass;
+static void _ostree_bootloader_aboot_bootloader_iface_init (OstreeBootloaderInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (OstreeBootloaderAboot, _ostree_bootloader_aboot, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BOOTLOADER, _ostree_bootloader_aboot_bootloader_iface_init));
+
+static gboolean
+_ostree_bootloader_aboot_query (OstreeBootloader *bootloader,
+                                   gboolean         *out_is_active,
+                                   GCancellable     *cancellable,
+                                   GError          **error)
+{
+  /* We don't auto-detect this one; should be explicitly chosen right now.
+   * see also https://github.com/coreos/coreos-assembler/pull/849
+   */
+  *out_is_active = FALSE;
+  return TRUE;
+}
+
+static const char *
+_ostree_bootloader_aboot_get_name (OstreeBootloader *bootloader)
+{
+  return "aboot";
+}
+
+static gboolean
+_ostree_bootloader_aboot_write_config (OstreeBootloader  *bootloader,
+                                         int                bootversion,
+                                         GPtrArray         *new_deployments,
+                                         GCancellable      *cancellable,
+                                         GError           **error)
+{
+  OstreeBootloaderAboot *self = OSTREE_BOOTLOADER_ABOOT (bootloader);
+
+  /* Write our stamp file */
+  if (!glnx_file_replace_contents_at (self->sysroot->sysroot_fd, aboot_requires_execute_path,
+                                      (guint8*)"", 0, GLNX_FILE_REPLACE_NODATASYNC,
+                                      cancellable, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+_ostree_aboot_get_bls_config (OstreeBootloaderAboot *self,
+                                         int bootversion,
+                                         gchar **aboot,
+                                         gchar **abootcfg,
+                                         gchar **version,
+                                         gchar **vmlinuz,
+                                         gchar **initramfs,
+                                         gchar **options,
+                                         GCancellable *cancellable,
+                                         GError **error)
+{
+  g_autoptr (GPtrArray) configs = NULL;
+  if ( !_ostree_sysroot_read_boot_loader_configs (self->sysroot, bootversion, &configs, cancellable, error))
+    return glnx_prefix_error (error, "aboot: loading bls configs");
+
+  if (!configs || configs->len == 0)
+    return glnx_throw (error, "aboot: no bls config");
+
+  OstreeBootconfigParser *parser = (OstreeBootconfigParser *) g_ptr_array_index (configs, 0);
+  const gchar *val = NULL;
+
+  val = ostree_bootconfig_parser_get (parser, "aboot");
+  if (!val) {
+    return glnx_throw (error, "aboot: no \"aboot\" key in bootloader config");
+  }
+  *aboot = g_strdup(val);
+
+  val = ostree_bootconfig_parser_get (parser, "abootcfg");
+  if (!val) {
+    return glnx_throw (error, "aboot: no \"abootcfg\" key in bootloader config");
+  }
+  *abootcfg = g_strdup(val);
+
+  val = ostree_bootconfig_parser_get (parser, "version");
+  if (!val)
+    return glnx_throw (error, "aboot: no \"version\" key in bootloader config");
+  *version = g_strdup(val);
+
+  val = ostree_bootconfig_parser_get (parser, "linux");
+  if (!val)
+    return glnx_throw (error, "aboot: no \"linux\" key in bootloader config");
+  *vmlinuz = g_build_filename ("/boot", val, NULL);
+
+  val = ostree_bootconfig_parser_get (parser, "initrd");
+  if (!val)
+    return glnx_throw (error, "aboot: no \"initrd\" key in bootloader config");
+  *initramfs = g_build_filename ("/boot", val, NULL);
+
+  val = ostree_bootconfig_parser_get (parser, "options");
+  if (!val)
+    return glnx_throw (error, "aboot: no \"options\" key in bootloader config");
+  *options = g_strdup(val);
+
+  return TRUE;
+}
+
+static gboolean
+_ostree_bootloader_aboot_post_bls_sync (OstreeBootloader  *bootloader,
+                                        int bootversion,
+                                        GCancellable  *cancellable,
+                                        GError       **error)
+{
+  OstreeBootloaderAboot *self = OSTREE_BOOTLOADER_ABOOT (bootloader);
+
+  /* Note that unlike the grub2-mkconfig backend, we make no attempt to
+   * chroot().
+   */
+  // g_assert (self->sysroot->booted_deployment);
+
+  if (!glnx_fstatat_allow_noent (self->sysroot->sysroot_fd, aboot_requires_execute_path, NULL, 0, error))
+    return FALSE;
+
+  /* If there's no stamp file, nothing to do */
+  if (errno == ENOENT)
+    return TRUE;
+
+  g_autofree gchar* aboot = NULL;
+  g_autofree gchar* abootcfg = NULL;
+  g_autofree gchar* version = NULL;
+  g_autofree gchar* vmlinuz = NULL;
+  g_autofree gchar* initramfs = NULL;
+  g_autofree gchar* options = NULL;
+  if (!_ostree_aboot_get_bls_config (self, bootversion, &aboot, &abootcfg, &version, &vmlinuz, &initramfs, &options, cancellable, error))
+    return FALSE;
+
+  g_autofree char *path_str = g_file_get_path(self->sysroot->path);
+
+  const char *const aboot_argv[] = {"aboot-deploy", "-r", path_str, "-c", abootcfg, aboot, NULL};
+  int estatus;
+  if (!g_spawn_sync (NULL, (char**)aboot_argv, NULL, G_SPAWN_SEARCH_PATH,
+                     NULL, NULL, NULL, NULL, &estatus, error)) {
+    return FALSE;
+  }
+
+  if (!g_spawn_check_exit_status (estatus, error)) {
+    return FALSE;
+  }
+
+  if (!glnx_unlinkat (self->sysroot->sysroot_fd, aboot_requires_execute_path, 0, error)) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static void
+_ostree_bootloader_aboot_finalize (GObject *object)
+{
+  OstreeBootloaderAboot *self = OSTREE_BOOTLOADER_ABOOT (object);
+
+  g_clear_object (&self->sysroot);
+
+  G_OBJECT_CLASS (_ostree_bootloader_aboot_parent_class)->finalize (object);
+}
+
+void
+_ostree_bootloader_aboot_init (OstreeBootloaderAboot *self)
+{
+}
+
+static void
+_ostree_bootloader_aboot_bootloader_iface_init (OstreeBootloaderInterface *iface)
+{
+  iface->query = _ostree_bootloader_aboot_query;
+  iface->get_name = _ostree_bootloader_aboot_get_name;
+  iface->write_config = _ostree_bootloader_aboot_write_config;
+  iface->post_bls_sync = _ostree_bootloader_aboot_post_bls_sync;
+}
+
+void
+_ostree_bootloader_aboot_class_init (OstreeBootloaderAbootClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = _ostree_bootloader_aboot_finalize;
+}
+
+OstreeBootloaderAboot *
+_ostree_bootloader_aboot_new (OstreeSysroot *sysroot)
+{
+  OstreeBootloaderAboot *self = g_object_new (OSTREE_TYPE_BOOTLOADER_ABOOT, NULL);
+  self->sysroot = g_object_ref (sysroot);
+  return self;
+}
diff --git a/src/libostree/ostree-bootloader-aboot.h b/src/libostree/ostree-bootloader-aboot.h
new file mode 100644 (file)
index 0000000..f05457d
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 Eric Curtin <ericcurtin17@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ostree-bootloader.h"
+
+G_BEGIN_DECLS
+
+#define OSTREE_TYPE_BOOTLOADER_ABOOT (_ostree_bootloader_aboot_get_type ())
+#define OSTREE_BOOTLOADER_ABOOT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OSTREE_TYPE_BOOTLOADER_ABOOT, OstreeBootloaderAboot))
+#define OSTREE_IS_BOOTLOADER_ABOOT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OSTREE_TYPE_BOOTLOADER_ABOOT))
+
+typedef struct _OstreeBootloaderAboot OstreeBootloaderAboot;
+
+GType _ostree_bootloader_aboot_get_type (void) G_GNUC_CONST;
+
+OstreeBootloaderAboot * _ostree_bootloader_aboot_new (OstreeSysroot *sysroot);
+G_END_DECLS
index 0d33f7c2d05e032359900d0daafa3f8a01de8118..18e0199e2d451e6cfc02986bf6c03b72dffbbfc4 100644 (file)
@@ -124,6 +124,7 @@ typedef enum {
   CFG_SYSROOT_BOOTLOADER_OPT_SYSLINUX,
   CFG_SYSROOT_BOOTLOADER_OPT_UBOOT,
   CFG_SYSROOT_BOOTLOADER_OPT_ZIPL,
+  CFG_SYSROOT_BOOTLOADER_OPT_ABOOT,
   /* Non-exhaustive */
 } OstreeCfgSysrootBootloaderOpt;
 
@@ -135,6 +136,7 @@ static const char* const CFG_SYSROOT_BOOTLOADER_OPTS_STR[] = {
   "syslinux",
   "uboot",
   "zipl",
+  "aboot",
   NULL,
 };
 
index 482124e2b64166a1a2a13b9ea7e32def23f8fd40..7554e56c370a89f8eb10f61791723df4b73e2df5 100644 (file)
@@ -992,6 +992,8 @@ typedef struct {
   char *initramfs_namever;
   char *devicetree_srcpath;
   char *devicetree_namever;
+  char *aboot_srcpath;
+  char *aboot_namever;
   char *bootcsum;
 } OstreeKernelLayout;
 static void
@@ -1006,6 +1008,8 @@ _ostree_kernel_layout_free (OstreeKernelLayout *layout)
   g_free (layout->initramfs_namever);
   g_free (layout->devicetree_srcpath);
   g_free (layout->devicetree_namever);
+  g_free (layout->aboot_srcpath);
+  g_free (layout->aboot_namever);
   g_free (layout->bootcsum);
   g_free (layout);
 }
@@ -1131,6 +1135,21 @@ get_kernel_from_tree_usrlib_modules (OstreeSysroot       *sysroot,
   g_clear_object (&in);
   glnx_close_fd (&fd);
 
+  /* look for a aboot.img file. */
+  if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "aboot.img", &fd, error))
+    return FALSE;
+
+  if (fd != -1)
+    {
+      ret_layout->aboot_srcpath = g_strdup ("aboot.img");
+      ret_layout->aboot_namever = g_strdup_printf ("aboot-%s.img", kver);
+    }
+  glnx_close_fd (&fd);
+
+  /* look for a aboot.cfg file. */
+  if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "aboot.cfg", &fd, error))
+    return FALSE;
+
   /* Testing aid for https://github.com/ostreedev/ostree/issues/2154 */
   const gboolean no_dtb = (sysroot->debug_flags & OSTREE_SYSROOT_DEBUG_TEST_NO_DTB) > 0;
   if (!no_dtb)
@@ -1923,6 +1942,21 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
         }
     }
 
+  if (kernel_layout->aboot_srcpath)
+    {
+      g_assert (kernel_layout->aboot_namever);
+      if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->aboot_namever, &stbuf, 0, error))
+        return FALSE;
+
+      if (errno == ENOENT)
+        {
+          if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->aboot_srcpath,
+                                  bootcsum_dfd, kernel_layout->aboot_namever,
+                                  cancellable, error))
+            return FALSE;
+        }
+    }
+
   g_autoptr(GPtrArray) overlay_initrds = NULL;
   for (char **it = _ostree_deployment_get_overlay_initrds (deployment); it && *it; it++)
     {
@@ -2059,6 +2093,30 @@ install_deployment_kernel (OstreeSysroot   *sysroot,
       ostree_kernel_args_replace_take (kargs, g_steal_pointer (&prepare_root_arg));
     }
 
+  const char* aboot_fn = NULL;
+  if (kernel_layout->aboot_namever)
+    {
+      aboot_fn = kernel_layout->aboot_namever;
+    }
+  else if (kernel_layout->aboot_srcpath)
+    {
+      aboot_fn = kernel_layout->aboot_srcpath;
+    }
+
+  if (aboot_fn)
+    {
+      g_autofree char * aboot_relpath = g_strconcat ("/", bootcsumdir, "/", aboot_fn, NULL);
+      ostree_bootconfig_parser_set (bootconfig, "aboot", aboot_relpath);
+    }
+  else
+    {
+      g_autofree char * aboot_relpath = g_strconcat ("/", deployment_dirpath, "/usr/lib/ostree-boot/aboot.img", NULL);
+      ostree_bootconfig_parser_set (bootconfig, "aboot", aboot_relpath);
+    }
+
+  g_autofree char * abootcfg_relpath = g_strconcat ("/", deployment_dirpath, "/usr/lib/ostree-boot/aboot.cfg", NULL);
+  ostree_bootconfig_parser_set (bootconfig, "abootcfg", abootcfg_relpath);
+
   if (kernel_layout->devicetree_namever)
     {
       g_autofree char * dt_boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL);
index 4c63a657a39dacdae4bd946c08f502f66375d564..63d79b417b96ee5921a9210c7ad082008a05d938 100644 (file)
@@ -32,6 +32,7 @@
 #include "ostree-sepolicy-private.h"
 #include "ostree-sysroot-private.h"
 #include "ostree-deployment-private.h"
+#include "ostree-bootloader-aboot.h"
 #include "ostree-bootloader-uboot.h"
 #include "ostree-bootloader-syslinux.h"
 #include "ostree-bootloader-grub2.h"
@@ -1475,6 +1476,8 @@ _ostree_sysroot_new_bootloader_by_type (
       return (OstreeBootloader*) _ostree_bootloader_grub2_new (sysroot);
     case CFG_SYSROOT_BOOTLOADER_OPT_SYSLINUX:
       return (OstreeBootloader*) _ostree_bootloader_syslinux_new (sysroot);
+    case CFG_SYSROOT_BOOTLOADER_OPT_ABOOT:
+      return (OstreeBootloader*) _ostree_bootloader_aboot_new (sysroot);
     case CFG_SYSROOT_BOOTLOADER_OPT_UBOOT:
       return (OstreeBootloader*) _ostree_bootloader_uboot_new (sysroot);
     case CFG_SYSROOT_BOOTLOADER_OPT_ZIPL: