mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
measure: add new tool to precalculate PCR values for a kernel image
For now, this simply outputs the PCR hash values expected for a kernel image, if it's measured like sd-stub would do it. (Later on, we can extend the tool, to optionally sign these pre-calculated measurements, in order to implement signed PCR policies for disk encryption.)
This commit is contained in:
parent
51470e1e56
commit
ca1092dc15
@ -953,6 +953,7 @@ manpages = [
|
||||
'systemd-makefs',
|
||||
'systemd-mkswap@.service'],
|
||||
''],
|
||||
['systemd-measure', '1', [], 'HAVE_GNU_EFI'],
|
||||
['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'],
|
||||
['systemd-mount', '1', ['systemd-umount'], ''],
|
||||
['systemd-network-generator.service', '8', ['systemd-network-generator'], ''],
|
||||
|
154
man/systemd-measure.xml
Normal file
154
man/systemd-measure.xml
Normal file
@ -0,0 +1,154 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
<refentry id="systemd-measure" xmlns:xi="http://www.w3.org/2001/XInclude" conditional='HAVE_GNU_EFI'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-measure</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-measure</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-measure</refname>
|
||||
<refpurpose>Pre-calculate expected TPM2 PCR values for booted unified kernel images</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>/usr/lib/systemd/systemd-measure <arg choice="opt" rep="repeat">OPTIONS</arg></command>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para>Note: this command is experimental for now. While it is likely to become a regular component of
|
||||
systemd, it might still change in behaviour and interface.</para>
|
||||
|
||||
<para><command>systemd-measure</command> is a tool that may be used to pre-calculate the expected TPM2
|
||||
PCR 11 values that should be seen when a unified Linux kernel image based on
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> is
|
||||
booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file,
|
||||
kernel command line file,
|
||||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry> file, and
|
||||
boot splash file that make up the unified kernel image, and determines the PCR values expected to be in
|
||||
place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a
|
||||
fashion compatible with what <filename>systemd-stub</filename> does at boot.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><command>status</command></term>
|
||||
|
||||
<listitem><para>This is the default command if none is specified. This queries the local system's
|
||||
TPM2 PCR 11+12+13 values and displays them. The data is written in a similar format as the
|
||||
<command>calculate</command> command below, and may be used to quickly compare expectation with
|
||||
reality.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>calculate</command></term>
|
||||
|
||||
<listitem><para>Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified
|
||||
kernel image consisting of the components specified with <option>--linux=</option>,
|
||||
<option>--osrel=</option>, <option>--cmdline=</option>, <option>--initrd=</option>,
|
||||
<option>--splash=</option>, <option>--dtb=</option>, see below. Only <option>--linux=</option> is
|
||||
mandatory.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--linux=PATH</option></term>
|
||||
<term><option>--osrel=PATH</option></term>
|
||||
<term><option>--cmdline=PATH</option></term>
|
||||
<term><option>--initrd=PATH</option></term>
|
||||
<term><option>--splash=PATH</option></term>
|
||||
<term><option>--dtb=PATH</option></term>
|
||||
|
||||
<listitem><para>When used with the <command>calculate</command> verb, configures the files to read
|
||||
the unified kernel image components from. Each option corresponds with the equally named section in
|
||||
the unified kernel PE file. The <option>--linux=</option> switch expects the path to the ELF kernel
|
||||
file that the unified PE kernel will wrap. All switches except <option>--linux=</option> are
|
||||
optional. Each option may be used at most once.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--bank=DIGEST</option></term>
|
||||
|
||||
<listitem><para>Controls the PCR banks to pre-calculate the PCR values for – in case
|
||||
<command>calculate</command> is invoked –, or the banks to show in the <command>status</command>
|
||||
output. May be used more then once to specify multiple banks. If not specified, defaults to the four
|
||||
banks <literal>sha1</literal>, <literal>sha256</literal>, <literal>sha384</literal>,
|
||||
<literal>sha512</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Examples</title>
|
||||
|
||||
<example>
|
||||
<title>Generate a unified kernel image, and calculate the expected TPM PCR 11 value</title>
|
||||
|
||||
<programlisting># objcopy \
|
||||
--add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
|
||||
--add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
|
||||
--add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
|
||||
--add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
|
||||
--add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
|
||||
--add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
|
||||
/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
|
||||
foo.efi
|
||||
# systemd-measure calculate \
|
||||
--linux=vmlinux \
|
||||
--osrel=os-release \
|
||||
--cmdline=cmdline.txt \
|
||||
--initrd=initrd.cpio \
|
||||
--splash=splash.bmp \
|
||||
--dtb=devicetree.dtb
|
||||
11:sha1=d775a7b4482450ac77e03ee19bda90bd792d6ec7
|
||||
11:sha256=bc6170f9ce28eb051ab465cd62be8cf63985276766cf9faf527ffefb66f45651
|
||||
11:sha384=1cf67dff4757e61e5a73d2a21a6694d668629bbc3761747d493f7f49ad720be02fd07263e1f93061243aec599d1ee4b4
|
||||
11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da
|
||||
</programlisting>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
12
meson.build
12
meson.build
@ -2541,6 +2541,18 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : systemgeneratordir)
|
||||
|
||||
if conf.get('HAVE_OPENSSL') == 1
|
||||
executable(
|
||||
'systemd-measure',
|
||||
'src/boot/measure.c',
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [libopenssl],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
endif
|
||||
endif
|
||||
|
||||
executable(
|
||||
|
533
src/boot/measure.c
Normal file
533
src/boot/measure.c
Normal file
@ -0,0 +1,533 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "efi-loader.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "main-func.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tpm-pcr.h"
|
||||
#include "tpm2-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used
|
||||
* to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */
|
||||
|
||||
static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
|
||||
static char **arg_banks = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
|
||||
|
||||
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
|
||||
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
|
||||
free((*sections)[c]);
|
||||
}
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections);
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-measure", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sPre-calculate PCR hash for kernel image.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" status Show current PCR values\n"
|
||||
" calculate Calculate expected PCR values\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Print version\n"
|
||||
" --linux=PATH Path Linux kernel ELF image\n"
|
||||
" --osrel=PATH Path to os-release file\n"
|
||||
" --cmdline=PATH Path to file with kernel command line\n"
|
||||
" --initrd=PATH Path to initrd image\n"
|
||||
" --splash=PATH Path to splash bitmap\n"
|
||||
" --dtb=PATH Path to Devicetree file\n"
|
||||
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
_ARG_SECTION_FIRST,
|
||||
ARG_LINUX = _ARG_SECTION_FIRST,
|
||||
ARG_OSREL,
|
||||
ARG_CMDLINE,
|
||||
ARG_INITRD,
|
||||
ARG_SPLASH,
|
||||
_ARG_SECTION_LAST,
|
||||
ARG_DTB = _ARG_SECTION_LAST,
|
||||
ARG_BANK,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "linux", required_argument, NULL, ARG_LINUX },
|
||||
{ "osrel", required_argument, NULL, ARG_OSREL },
|
||||
{ "cmdline", required_argument, NULL, ARG_CMDLINE },
|
||||
{ "initrd", required_argument, NULL, ARG_INITRD },
|
||||
{ "splash", required_argument, NULL, ARG_SPLASH },
|
||||
{ "dtb", required_argument, NULL, ARG_DTB },
|
||||
{ "bank", required_argument, NULL, ARG_BANK },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
/* Make sure the arguments list and the section list, stays in sync */
|
||||
assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
help(0, NULL, NULL);
|
||||
return 0;
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: {
|
||||
UnifiedSection section = c - _ARG_SECTION_FIRST;
|
||||
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_BANK: {
|
||||
const EVP_MD *implementation;
|
||||
|
||||
implementation = EVP_get_digestbyname(optarg);
|
||||
if (!implementation)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg);
|
||||
|
||||
if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0)
|
||||
return log_oom();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (strv_isempty(arg_banks)) {
|
||||
/* If no banks are specifically selected, pick all known banks */
|
||||
arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512");
|
||||
if (!arg_banks)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
strv_sort(arg_banks);
|
||||
strv_uniq(arg_banks);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef struct PcrState {
|
||||
const EVP_MD *md;
|
||||
void *value;
|
||||
size_t value_size;
|
||||
} PcrState;
|
||||
|
||||
static void pcr_state_free_all(PcrState **pcr_state) {
|
||||
assert(pcr_state);
|
||||
|
||||
if (!*pcr_state)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; (*pcr_state)[i].value; i++)
|
||||
free((*pcr_state)[i].value);
|
||||
|
||||
*pcr_state = mfree(*pcr_state);
|
||||
}
|
||||
|
||||
static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) {
|
||||
assert(md);
|
||||
|
||||
if (!*md)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; (*md)[i]; i++)
|
||||
EVP_MD_CTX_free((*md)[i]);
|
||||
|
||||
*md = mfree(*md);
|
||||
}
|
||||
|
||||
static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL;
|
||||
unsigned value_size;
|
||||
|
||||
assert(pcr_state);
|
||||
assert(data || sz == 0);
|
||||
assert(pcr_state->md);
|
||||
assert(pcr_state->value);
|
||||
assert(pcr_state->value_size > 0);
|
||||
|
||||
/* Extends a (virtual) PCR by the given data */
|
||||
|
||||
mc = EVP_MD_CTX_new();
|
||||
if (!mc)
|
||||
return log_oom();
|
||||
|
||||
if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(pcr_state->md));
|
||||
|
||||
/* First thing we do, is hash the old PCR value */
|
||||
if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
|
||||
|
||||
/* Then, we hash the new data */
|
||||
if (EVP_DigestUpdate(mc, data, sz) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
|
||||
|
||||
if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
|
||||
|
||||
assert(value_size == pcr_state->value_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BUFFER_SIZE (16U * 1024U)
|
||||
|
||||
static int measure_pcr(PcrState *pcr_states, size_t n) {
|
||||
_cleanup_free_ void *buffer = NULL;
|
||||
int r;
|
||||
|
||||
assert(n > 0);
|
||||
assert(pcr_states);
|
||||
|
||||
buffer = malloc(BUFFER_SIZE);
|
||||
if (!buffer)
|
||||
return log_oom();
|
||||
|
||||
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) {
|
||||
_cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
uint64_t m = 0;
|
||||
|
||||
if (!arg_sections[c])
|
||||
continue;
|
||||
|
||||
fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]);
|
||||
|
||||
/* Allocate one message digest context per bank (NULL terminated) */
|
||||
mdctx = new0(EVP_MD_CTX*, n + 1);
|
||||
if (!mdctx)
|
||||
return log_oom();
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
mdctx[i] = EVP_MD_CTX_new();
|
||||
if (!mdctx[i])
|
||||
return log_oom();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", EVP_MD_name(pcr_states[i].md));
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
ssize_t sz;
|
||||
|
||||
sz = read(fd, buffer, BUFFER_SIZE);
|
||||
if (sz < 0)
|
||||
return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]);
|
||||
if (sz == 0) /* EOF */
|
||||
break;
|
||||
|
||||
for (size_t i = 0; i < n; i++)
|
||||
if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest.");
|
||||
|
||||
m += sz;
|
||||
}
|
||||
|
||||
fd = safe_close(fd);
|
||||
|
||||
if (m == 0) /* We skip over empty files, the stub does so too */
|
||||
continue;
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
_cleanup_free_ void *data_hash = NULL;
|
||||
unsigned data_hash_size;
|
||||
|
||||
data_hash = malloc(pcr_states[i].value_size);
|
||||
if (!data_hash)
|
||||
return log_oom();
|
||||
|
||||
/* Measure name of section */
|
||||
if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", EVP_MD_name(pcr_states[i].md));
|
||||
|
||||
assert(data_hash_size == (unsigned) pcr_states[i].value_size);
|
||||
|
||||
r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Retrieve hash of data an measure it*/
|
||||
if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
|
||||
|
||||
assert(data_hash_size == (unsigned) pcr_states[i].value_size);
|
||||
|
||||
r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_calculate(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
|
||||
size_t n = 0;
|
||||
int r;
|
||||
|
||||
if (!arg_sections[UNIFIED_SECTION_LINUX])
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--linux= switch must be specified, refusing.");
|
||||
|
||||
pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
|
||||
if (!pcr_states)
|
||||
return log_oom();
|
||||
|
||||
/* Allocate a PCR state structure, one for each bank */
|
||||
STRV_FOREACH(d, arg_banks) {
|
||||
const EVP_MD *implementation;
|
||||
_cleanup_free_ void *v = NULL;
|
||||
int sz;
|
||||
|
||||
assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */
|
||||
|
||||
sz = EVP_MD_size(implementation);
|
||||
if (sz <= 0 || sz >= INT_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz);
|
||||
|
||||
v = malloc0(sz); /* initial PCR state is all zeroes */
|
||||
if (!v)
|
||||
return log_oom();
|
||||
|
||||
pcr_states[n++] = (struct PcrState) {
|
||||
.md = implementation,
|
||||
.value = TAKE_PTR(v),
|
||||
.value_size = sz,
|
||||
};
|
||||
}
|
||||
|
||||
r = measure_pcr(pcr_states, n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
_cleanup_free_ char *hd = NULL, *b = NULL;
|
||||
|
||||
hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
|
||||
if (!hd)
|
||||
return log_oom();
|
||||
|
||||
b = strdup(EVP_MD_name(pcr_states[i].md));
|
||||
if (!b)
|
||||
return log_oom();
|
||||
|
||||
printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, ascii_strlower(b), hd);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
uint32_t v;
|
||||
int r;
|
||||
|
||||
r = efi_get_variable_string(varname, &s);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname);
|
||||
|
||||
r = safe_atou32(s, &v);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s);
|
||||
|
||||
if (pcr != v)
|
||||
log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n"
|
||||
"The measurements are likely inconsistent.", description, v, pcr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int validate_stub(void) {
|
||||
uint64_t features;
|
||||
bool found = false;
|
||||
int r;
|
||||
|
||||
if (tpm2_support() != TPM2_SUPPORT_FULL)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support.");
|
||||
|
||||
r = efi_stub_get_features(&features);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Unable to get stub features: %m");
|
||||
|
||||
if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS))
|
||||
log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n"
|
||||
"The PCR measurements seen are unlikely to be valid.");
|
||||
|
||||
r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE("StubPcrKernelImage"), "kernel image");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE("StubPcrKernelParameters"), "kernel parameters");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE("StubPcrInitRDSysExts"), "initrd system extension images");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(bank, arg_banks) {
|
||||
_cleanup_free_ char *b = NULL, *p = NULL;
|
||||
|
||||
b = strdup(*bank);
|
||||
if (!b)
|
||||
return log_oom();
|
||||
|
||||
if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (access(p, F_OK) < 0) {
|
||||
if (errno != ENOENT)
|
||||
return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b);
|
||||
} else
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_status(int argc, char *argv[], void *userdata) {
|
||||
|
||||
static const struct {
|
||||
uint32_t nr;
|
||||
const char *description;
|
||||
} relevant_pcrs[] = {
|
||||
{ TPM_PCR_INDEX_KERNEL_IMAGE, "Unified Kernel Image" },
|
||||
{ TPM_PCR_INDEX_KERNEL_PARAMETERS, "Kernel Parameters" },
|
||||
{ TPM_PCR_INDEX_INITRD_SYSEXTS, "initrd System Extensions" },
|
||||
};
|
||||
|
||||
int r;
|
||||
|
||||
r = validate_stub();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(relevant_pcrs); i++) {
|
||||
|
||||
STRV_FOREACH(bank, arg_banks) {
|
||||
_cleanup_free_ char *b = NULL, *p = NULL, *s = NULL, *f = NULL;
|
||||
_cleanup_free_ void *h = NULL;
|
||||
size_t l;
|
||||
|
||||
b = strdup(*bank);
|
||||
if (!b)
|
||||
return log_oom();
|
||||
|
||||
if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), relevant_pcrs[i].nr) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = read_virtual_file(p, 4096, &s, NULL);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read '%s': %m", p);
|
||||
|
||||
r = unhexmem(strstrip(s), SIZE_MAX, &h, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to decode PCR value '%s': %m", s);
|
||||
|
||||
f = hexmem(h, l);
|
||||
if (!h)
|
||||
return log_oom();
|
||||
|
||||
if (bank == arg_banks) {
|
||||
/* before the first line for each PCR, write a short descriptive text to
|
||||
* stderr, and leave the primary content on stdout */
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n",
|
||||
ansi_grey(),
|
||||
relevant_pcrs[i].nr,
|
||||
relevant_pcrs[i].description,
|
||||
memeqzero(h, l) ? " (NOT SET!)" : "",
|
||||
ansi_normal());
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
printf("%" PRIu32 ":%s=%s\n", relevant_pcrs[i].nr, b, f);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int measure_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, help },
|
||||
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
|
||||
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_show_color(true);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return measure_main(argc, argv);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
Loading…
Reference in New Issue
Block a user