summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNIIBE Yutaka <gniibe@fsij.org>2022-02-16 20:08:15 +0900
committerNIIBE Yutaka <gniibe@fsij.org>2022-02-22 16:17:31 +0900
commitdcc6979fd2ed32bb5a5e448e2c9da1158c1d93c2 (patch)
tree63ba584c5ef34663d63a457fa563d925d7f42322
parent974f4c7e698b0c1ffe3de82bad9b3f8813d1f42b (diff)
downloadlibgcrypt-dcc6979fd2ed32bb5a5e448e2c9da1158c1d93c2.tar.gz
fips: More portable integrity check.
* src/Makefile.am (EXTRA_DIST): Change the name of the script. (libgcrypt.la.done): Invoce OBJCOPY with --add-section. (libgcrypt.so.hmac): Specify ECHO_N. * src/fips.c (get_file_offset): Rename from get_file_offsets. Find the note section and return the value in HMAC. (hmac256_check): Simplify by HMAC from the note section, not loaded. (check_binary_integrity): Use dladdr instead of dladdr1. * src/gen-note-integrity.sh: Rename from genhmac.sh. Generate ElfN_Nhdr, and then the hmac. -- Backport master commit of: a340e980388243ceae6df57d101036f3f2a955be The idea of use of .note is by Daiki Ueno. https://gitlab.com/dueno/integrity-notes Further, instead of NOTE segment loaded onto memory, use noload section in the file. Thanks to Clemens Lang for initiating this direction of improvement. The namespace "FDO" would need to be changed. GnuPG-bug-id: 5835 Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
-rw-r--r--src/Makefile.am8
-rw-r--r--src/fips.c167
-rwxr-xr-xsrc/gen-note-integrity.sh (renamed from src/genhmac.sh)34
3 files changed, 134 insertions, 75 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 2bf53d09..aa3548f6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,7 +24,7 @@ pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libgcrypt.pc
EXTRA_DIST = libgcrypt-config.in libgcrypt.m4 libgcrypt.vers \
- gcrypt.h.in libgcrypt.def libgcrypt.pc.in genhmac.sh
+ gcrypt.h.in libgcrypt.def libgcrypt.pc.in gen-note-integrity.sh
bin_SCRIPTS = libgcrypt-config
m4datadir = $(datadir)/aclocal
@@ -155,13 +155,15 @@ if USE_HMAC_BINARY_CHECK
CLEANFILES += libgcrypt.so.hmac
libgcrypt.la.done: libgcrypt.so.hmac
- $(OBJCOPY) --update-section .rodata1=libgcrypt.so.hmac \
+ $(OBJCOPY) --add-section .note.fdo.integrity=libgcrypt.so.hmac \
+ --set-section-flags .note.fdo.integrity=noload,readonly \
.libs/libgcrypt.so .libs/libgcrypt.so.new
mv -f .libs/libgcrypt.so.new .libs/libgcrypt.so.*.*
@touch libgcrypt.la.done
libgcrypt.so.hmac: hmac256 libgcrypt.la
- READELF=$(READELF) AWK=$(AWK) $(srcdir)/genhmac.sh > $@
+ ECHO_N=$(ECHO_N) READELF=$(READELF) AWK=$(AWK) \
+ $(srcdir)/gen-note-integrity.sh > $@
else !USE_HMAC_BINARY_CHECK
libgcrypt.la.done: libgcrypt.la
@touch libgcrypt.la.done
diff --git a/src/fips.c b/src/fips.c
index 134d0eae..d798d577 100644
--- a/src/fips.c
+++ b/src/fips.c
@@ -593,22 +593,20 @@ run_random_selftests (void)
# endif
#define HMAC_LEN 32
-static const unsigned char __attribute__ ((section (".rodata1")))
-hmac_for_the_implementation[HMAC_LEN];
-
/*
- * In the ELF file opened as FP, determine the offset of the given
- * virtual address ADDR and return it in R_OFFSET1. Determine the
- * offset of last loadable section in R_OFFSET2. Rewinds FP to the
- * beginning on success.
+ * In the ELF file opened as FP, fill the ELF header to the pointer
+ * EHDR_P, determine the offset of last loadable segment in R_OFFSET.
+ * Also, find the section which contains the hmac value and return it
+ * in HMAC. Rewinds FP to the beginning on success.
*/
static gpg_error_t
-get_file_offsets (FILE *fp, unsigned long addr, ElfW (Ehdr) *ehdr_p,
- unsigned long *r_offset1, unsigned long *r_offset2)
+get_file_offset (FILE *fp, ElfW (Ehdr) *ehdr_p,
+ unsigned long *r_offset, unsigned char hmac[HMAC_LEN])
{
ElfW (Phdr) phdr;
- uint16_t e_phidx;
- long pos = 0;
+ ElfW (Shdr) shdr;
+ int i;
+ unsigned long off_segment = 0;
/* Read the ELF header */
if (fseek (fp, 0, SEEK_SET) != 0)
@@ -616,12 +614,6 @@ get_file_offsets (FILE *fp, unsigned long addr, ElfW (Ehdr) *ehdr_p,
if (fread (ehdr_p, sizeof (*ehdr_p), 1, fp) != 1)
return gpg_error_from_syserror ();
- /* Fix up the ELF header, clean all section information. */
- ehdr_p->e_shoff = 0;
- ehdr_p->e_shentsize = 0;
- ehdr_p->e_shnum = 0;
- ehdr_p->e_shstrndx = 0;
-
/* The program header entry size should match the size of the phdr struct */
if (ehdr_p->e_phentsize != sizeof (phdr))
return gpg_error (GPG_ERR_INV_OBJ);
@@ -632,11 +624,9 @@ get_file_offsets (FILE *fp, unsigned long addr, ElfW (Ehdr) *ehdr_p,
if (fseek (fp, ehdr_p->e_phoff, SEEK_SET) != 0)
return gpg_error_from_syserror ();
- /* Iterate over the program headers, compare their virtual addresses
- with the address we are looking for, and if the program header
- matches, calculate the offset of the given ADDR in the file using
- the program header's p_offset field. */
- for (e_phidx = 0; e_phidx < ehdr_p->e_phnum; e_phidx++)
+ /* Iterate over the program headers, determine the last loadable
+ segment. */
+ for (i = 0; i < ehdr_p->e_phnum; i++)
{
if (fread (&phdr, sizeof (phdr), 1, fp) != 1)
return gpg_error_from_syserror ();
@@ -647,45 +637,100 @@ get_file_offsets (FILE *fp, unsigned long addr, ElfW (Ehdr) *ehdr_p,
if (phdr.p_type != PT_LOAD)
break;
- pos = phdr.p_offset + phdr.p_filesz;
- if (phdr.p_vaddr <= addr && addr < phdr.p_vaddr + phdr.p_memsz)
- /* Found segment, compute the offset of ADDR in the file */
- *r_offset1 = phdr.p_offset + (addr - phdr.p_vaddr);
+ off_segment = phdr.p_offset + phdr.p_filesz;
}
- if (*r_offset1)
+ if (!off_segment)
+ /* The segment not found in the file */
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ /* The section header entry size should match the size of the shdr struct */
+ if (ehdr_p->e_shentsize != sizeof (shdr))
+ return gpg_error (GPG_ERR_INV_OBJ);
+ if (ehdr_p->e_shoff == 0)
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ /* Jump to the first section header */
+ if (fseek (fp, ehdr_p->e_shoff, SEEK_SET) != 0)
+ return gpg_error_from_syserror ();
+
+ /* Iterate over the section headers, determine the note section,
+ read the hmac value. */
+ for (i = 0; i < ehdr_p->e_shnum; i++)
{
- if (fseek (fp, 0, SEEK_SET) != 0)
+ long off;
+
+ if (fread (&shdr, sizeof (shdr), 1, fp) != 1)
return gpg_error_from_syserror ();
- *r_offset2 = (unsigned long)pos;
- return 0;
+ off = ftell (fp);
+ if (shdr.sh_type == SHT_NOTE && shdr.sh_flags == 0 && shdr.sh_size == 48)
+ {
+ const char header_of_the_note[] = {
+ 0x04, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ 0xca, 0xfe, 0x2a, 0x8e,
+ 'F', 'D', 'O', 0x00
+ };
+ unsigned char header[16];
+
+ /* Jump to the note section. */
+ if (fseek (fp, shdr.sh_offset, SEEK_SET) != 0)
+ return gpg_error_from_syserror ();
+
+ if (fread (header, sizeof (header), 1, fp) != 1)
+ return gpg_error_from_syserror ();
+
+ if (!memcmp (header, header_of_the_note, 16))
+ {
+ /* Found. Read the hmac value into HMAC. */
+ if (fread (hmac, HMAC_LEN, 1, fp) != 1)
+ return gpg_error_from_syserror ();
+ break;
+ }
+
+ /* Back to the next section header. */
+ if (fseek (fp, off, SEEK_SET) != 0)
+ return gpg_error_from_syserror ();
+ }
}
- /* Segment not found in the file */
- return gpg_error (GPG_ERR_INV_OBJ);
+ if (i == ehdr_p->e_shnum)
+ /* The note section not found. */
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ /* Fix up the ELF header, clean all section information. */
+ ehdr_p->e_shoff = 0;
+ ehdr_p->e_shentsize = 0;
+ ehdr_p->e_shnum = 0;
+ ehdr_p->e_shstrndx = 0;
+
+ *r_offset = off_segment;
+ if (fseek (fp, 0, SEEK_SET) != 0)
+ return gpg_error_from_syserror ();
+
+ return 0;
}
static gpg_error_t
-hmac256_check (const char *filename, const char *key, struct link_map *lm)
+hmac256_check (const char *filename, const char *key)
{
gpg_error_t err;
FILE *fp;
gcry_md_hd_t hd;
- size_t buffer_size, nread;
+ const size_t buffer_size = 32768;
+ size_t nread;
char *buffer;
- unsigned long addr;
- unsigned long offset1 = 0;
- unsigned long offset2 = 0;
+ unsigned long offset = 0;
unsigned long pos = 0;
ElfW (Ehdr) ehdr;
+ unsigned char hmac[HMAC_LEN];
- addr = (unsigned long)hmac_for_the_implementation - lm->l_addr;
fp = fopen (filename, "rb");
if (!fp)
return gpg_error (GPG_ERR_INV_OBJ);
- err = get_file_offsets (fp, addr, &ehdr, &offset1, &offset2);
+ err = get_file_offset (fp, &ehdr, &offset, hmac);
if (err)
{
fclose (fp);
@@ -707,8 +752,7 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
return err;
}
- buffer_size = 32768;
- buffer = xtrymalloc (buffer_size + HMAC_LEN);
+ buffer = xtrymalloc (buffer_size);
if (!buffer)
{
err = gpg_error_from_syserror ();
@@ -717,38 +761,21 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
return err;
}
- nread = fread (buffer, 1, HMAC_LEN, fp);
- pos += nread;
- if (nread < HMAC_LEN)
- {
- xfree (buffer);
- fclose (fp);
- _gcry_md_close (hd);
- return gpg_error (GPG_ERR_TOO_SHORT);
- }
-
while (1)
{
- nread = fread (buffer+HMAC_LEN, 1, buffer_size, fp);
- if (pos + nread >= offset2)
- nread = offset2 - pos;
+ nread = fread (buffer, 1, buffer_size, fp);
+ if (pos + nread >= offset)
+ nread = offset - pos;
- /* Copy, fixed ELF header at the beginning. */
- if (pos - HMAC_LEN == 0)
+ /* Copy the fixed ELF header at the beginning. */
+ if (pos == 0)
memcpy (buffer, &ehdr, sizeof (ehdr));
+ _gcry_md_write (hd, buffer, nread);
+
if (nread < buffer_size)
- {
- if (pos - HMAC_LEN <= offset1 && offset1 <= pos + nread)
- memset (buffer + HMAC_LEN + offset1 - pos, 0, HMAC_LEN);
- _gcry_md_write (hd, buffer, nread+HMAC_LEN);
- break;
- }
+ break;
- if (pos - HMAC_LEN <= offset1 && offset1 <= pos + nread)
- memset (buffer + HMAC_LEN + offset1 - pos, 0, HMAC_LEN);
- _gcry_md_write (hd, buffer, nread);
- memcpy (buffer, buffer+buffer_size, HMAC_LEN);
pos += nread;
}
@@ -759,7 +786,7 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
unsigned char *digest;
digest = _gcry_md_read (hd, 0);
- if (!memcmp (digest, hmac_for_the_implementation, HMAC_LEN))
+ if (!memcmp (digest, hmac, HMAC_LEN))
/* Success. */
err = 0;
else
@@ -780,13 +807,11 @@ check_binary_integrity (void)
gpg_error_t err;
Dl_info info;
const char *key = KEY_FOR_BINARY_CHECK;
- void *extra_info;
- if (!dladdr1 (hmac_for_the_implementation, &info, &extra_info,
- RTLD_DL_LINKMAP))
+ if (!dladdr (hmac256_check, &info))
err = gpg_error_from_syserror ();
else
- err = hmac256_check (info.dli_fname, key, extra_info);
+ err = hmac256_check (info.dli_fname, key);
reporter ("binary", 0, NULL, err? gpg_strerror (err):NULL);
#ifdef HAVE_SYSLOG
diff --git a/src/genhmac.sh b/src/gen-note-integrity.sh
index bb33b9c6..969fdca6 100755
--- a/src/genhmac.sh
+++ b/src/gen-note-integrity.sh
@@ -1,7 +1,7 @@
#! /bin/sh
#
-# genhmac.sh - Build tool to generate hmac hash
+# gen-note-integrity.sh - Build tool to generate hmac hash section
#
# Copyright (C) 2022 g10 Code GmbH
#
@@ -28,8 +28,40 @@ set -e
#
# READELF
# AWK
+# ECHO_N
#
+######## Emit ElfN_Nhdr for note.fdo.integrity ########
+
+NOTE_NAME="FDO"
+
+# n_namesz = 4 including NUL
+printf '%b' '\004'
+printf '%b' '\000'
+printf '%b' '\000'
+printf '%b' '\000'
+
+# n_descsz = 32
+printf '%b' '\040'
+printf '%b' '\000'
+printf '%b' '\000'
+printf '%b' '\000'
+
+# n_type: NT_FDO_INTEGRITY=0xCAFE2A8E
+printf '%b' '\312'
+printf '%b' '\376'
+printf '%b' '\052'
+printf '%b' '\216'
+
+# the name
+echo $ECHO_N $NOTE_NAME
+printf '%b' '\000'
+
+# Here comes the alignment. As the size of name is 4, it's none.
+# NO PADDING HERE.
+
+######## Rest is to generate hmac hash ########
+
AWK_VERSION_OUTPUT=$($AWK 'BEGIN { print PROCINFO["version"] }')
if test -n "$AWK_VERSION_OUTPUT"; then
# It's GNU awk, which supports PROCINFO.