bootloader: Add an aboot (Android) bootloader backend

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>
This commit is contained in:
Eric Curtin 2022-12-13 14:12:25 +00:00
parent ad96b8afea
commit 97117753e3
7 changed files with 333 additions and 0 deletions

View 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 \

View 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],

View File

@ -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;
}

View File

@ -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

View 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,
};

View 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);

View 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: