aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNIIBE Yutaka <[email protected]>2022-02-16 14:06:02 +0900
committerNIIBE Yutaka <[email protected]>2022-02-16 14:06:02 +0900
commit9dcf9305962b90febdf2d7cc73b49feadbf6a01f (patch)
tree1d3c4680ed8a0fcb98b5e659072034f2947f2474
parentb2f110f99626afce84c23c76db0ebaaadac4ee48 (diff)
downloadlibgcrypt-9dcf9305962b90febdf2d7cc73b49feadbf6a01f.tar.gz
libgcrypt-9dcf9305962b90febdf2d7cc73b49feadbf6a01f.tar.bz2
libgcrypt-9dcf9305962b90febdf2d7cc73b49feadbf6a01f.zip
fips: Integrity check improvement, with only loadable segments.
* configure.ac (READELF): Check the tool. * src/Makefile.am (libgcrypt.so.hmac): Use genhmac.sh with hmac256. * src/fips.c (get_file_offsets): Rename from get_file_offset. Determine the OFFSET2 at the end of loadable segments, too. Add fixup of the ELF header to exclude section information. (hmac256_check): Finish scanning at the end of loadble segments. * src/genhmac.sh: New. -- This change fixes the build with ld.gold. GnuPG-bug-id: 5835 Signed-off-by: NIIBE Yutaka <[email protected]>
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am4
-rw-r--r--src/fips.c73
-rwxr-xr-xsrc/genhmac.sh83
4 files changed, 136 insertions, 25 deletions
diff --git a/configure.ac b/configure.ac
index f0f1637f..ea01f5a6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -579,6 +579,7 @@ else
AC_DEFINE(ENABLE_HMAC_BINARY_CHECK,1,
[Define to support an HMAC based integrity check])
AC_CHECK_TOOL(OBJCOPY, [objcopy])
+ AC_CHECK_TOOL(READELF, [readelf])
if test "$use_hmac_binary_check" != yes ; then
DEF_HMAC_BINARY_CHECK=-DKEY_FOR_BINARY_CHECK="'\"$use_hmac_binary_check\"'"
fi
diff --git a/src/Makefile.am b/src/Makefile.am
index 018d5761..72100671 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
+ gcrypt.h.in libgcrypt.def libgcrypt.pc.in genhmac.sh
bin_SCRIPTS = libgcrypt-config
m4datadir = $(datadir)/aclocal
@@ -149,7 +149,7 @@ libgcrypt.la.done: libgcrypt.so.hmac
@touch libgcrypt.la.done
libgcrypt.so.hmac: hmac256 libgcrypt.la
- ./hmac256 --stdkey --binary < .libs/libgcrypt.so > [email protected]
+ READELF=$(READELF) AWK=$(AWK) $(srcdir)/genhmac.sh > [email protected]
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 f16bce8b..134d0eae 100644
--- a/src/fips.c
+++ b/src/fips.c
@@ -598,50 +598,68 @@ 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 OFFSET. Rewinds FP to the
+ * 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.
*/
static gpg_error_t
-get_file_offset (FILE *fp, unsigned long addr, unsigned long *offset)
+get_file_offsets (FILE *fp, unsigned long addr, ElfW (Ehdr) *ehdr_p,
+ unsigned long *r_offset1, unsigned long *r_offset2)
{
- ElfW (Ehdr) ehdr;
ElfW (Phdr) phdr;
uint16_t e_phidx;
+ long pos = 0;
/* Read the ELF header */
if (fseek (fp, 0, SEEK_SET) != 0)
return gpg_error_from_syserror ();
- if (fread (&ehdr, sizeof (ehdr), 1, fp) != 1)
+ 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.e_phentsize != sizeof (phdr))
+ if (ehdr_p->e_phentsize != sizeof (phdr))
return gpg_error (GPG_ERR_INV_OBJ);
- if (ehdr.e_phoff == 0)
+ if (ehdr_p->e_phoff == 0)
return gpg_error (GPG_ERR_INV_OBJ);
/* Jump to the first program header */
- if (fseek (fp, ehdr.e_phoff, SEEK_SET) != 0)
+ 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.e_phnum; e_phidx++)
+ for (e_phidx = 0; e_phidx < ehdr_p->e_phnum; e_phidx++)
{
if (fread (&phdr, sizeof (phdr), 1, fp) != 1)
return gpg_error_from_syserror ();
- if (phdr.p_type == PT_LOAD
- && phdr.p_vaddr <= addr && addr < phdr.p_vaddr + phdr.p_memsz)
- {
- /* Found segment, compute the offset of ADDR in the file */
- *offset = phdr.p_offset + (addr - phdr.p_vaddr);
- if (fseek (fp, 0, SEEK_SET) != 0)
- return gpg_error_from_syserror ();
- return 0;
- }
+ if (phdr.p_type == PT_PHDR)
+ continue;
+
+ 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);
+ }
+
+ if (*r_offset1)
+ {
+ if (fseek (fp, 0, SEEK_SET) != 0)
+ return gpg_error_from_syserror ();
+
+ *r_offset2 = (unsigned long)pos;
+ return 0;
}
/* Segment not found in the file */
@@ -657,15 +675,17 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
size_t buffer_size, nread;
char *buffer;
unsigned long addr;
- unsigned long offset = 0;
+ unsigned long offset1 = 0;
+ unsigned long offset2 = 0;
unsigned long pos = 0;
+ ElfW (Ehdr) ehdr;
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_offset (fp, addr, &offset);
+ err = get_file_offsets (fp, addr, &ehdr, &offset1, &offset2);
if (err)
{
fclose (fp);
@@ -710,16 +730,23 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
while (1)
{
nread = fread (buffer+HMAC_LEN, 1, buffer_size, fp);
+ if (pos + nread >= offset2)
+ nread = offset2 - pos;
+
+ /* Copy, fixed ELF header at the beginning. */
+ if (pos - HMAC_LEN == 0)
+ memcpy (buffer, &ehdr, sizeof (ehdr));
+
if (nread < buffer_size)
{
- if (pos - HMAC_LEN <= offset && offset <= pos + nread)
- memset (buffer + HMAC_LEN + offset - pos, 0, HMAC_LEN);
+ 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;
}
- if (pos - HMAC_LEN <= offset && offset <= pos + nread)
- memset (buffer + HMAC_LEN + offset - pos, 0, HMAC_LEN);
+ 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;
diff --git a/src/genhmac.sh b/src/genhmac.sh
new file mode 100755
index 00000000..bb33b9c6
--- /dev/null
+++ b/src/genhmac.sh
@@ -0,0 +1,83 @@
+#! /bin/sh
+
+#
+# genhmac.sh - Build tool to generate hmac hash
+#
+# Copyright (C) 2022 g10 Code GmbH
+#
+# This file is part of libgcrypt.
+#
+# libgcrypt 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.1 of
+# the License, or (at your option) any later version.
+#
+# libgcrypt 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 program; if not, see <https://www.gnu.org/licenses/>.
+#
+
+set -e
+
+#
+# Following variables should be defined to invoke this script
+#
+# READELF
+# AWK
+#
+
+AWK_VERSION_OUTPUT=$($AWK 'BEGIN { print PROCINFO["version"] }')
+if test -n "$AWK_VERSION_OUTPUT"; then
+ # It's GNU awk, which supports PROCINFO.
+ AWK_OPTION=--non-decimal-data
+fi
+
+FILE=.libs/libgcrypt.so
+
+#
+# Fixup the ELF header to clean up section information
+#
+printf '%b' '\002' > 2.bin
+dd ibs=1 skip=4 count=1 if=$FILE status=none > class-byte.bin
+if cmp class-byte.bin 2.bin; then
+ CLASS=64
+ HEADER_SIZE=64
+else
+ CLASS=32
+ HEADER_SIZE=52
+fi
+
+if test $CLASS -eq 64; then
+ dd ibs=1 count=40 if=$FILE status=none
+ dd ibs=1 count=8 if=/dev/zero status=none
+ dd ibs=1 skip=48 count=10 if=$FILE status=none
+ dd ibs=1 count=6 if=/dev/zero status=none
+else
+ dd ibs=1 count=32 if=$FILE status=none
+ dd ibs=1 count=4 if=/dev/zero status=none
+ dd ibs=1 skip=36 count=10 if=$FILE status=none
+ dd ibs=1 count=6 if=/dev/zero status=none
+fi > header-fixed.bin
+
+# Compute the end of loadable segment.
+#
+# This require computation in hexadecimal, and GNU awk needs
+# --non-decimal-data option
+#
+OFFSET=$($READELF --wide --program-headers $FILE | \
+ $AWK $AWK_OPTION "/^ LOAD/ { offset=\$2+\$5-$HEADER_SIZE }\
+END { print offset}")
+
+#
+# Feed the header fixed and loadable segments to HMAC256
+# to generate hmac hash of the FILE
+#
+(cat header-fixed.bin; \
+ dd ibs=1 skip=$HEADER_SIZE count=$OFFSET if=$FILE status=none) \
+ | ./hmac256 --stdkey --binary
+
+rm -f 2.bin class-byte.bin header-fixed.bin