[PATCH 1/2] fips: Use ELF header to find .rodata1 section
Clemens Lang
cllang at redhat.com
Fri Feb 11 16:57:22 CET 2022
* src/fips.c [ENABLE_HMAC_BINARY_CHECK] (hmac256_check): Use ELF headers
to locate the binary section with the HMAC rather than information
from the loader
--
The previous method of locating the offset of the .rodata1 section in
the ELF file on disk used information obtained from the loader. This
computed the address of the section at runtime (sh_addr in the ElfN_Shdr
struct), but the offset in the file can be different (sh_offset from the
ElfN_Shdr struct).
So far, GCC did include .rodata1 close enough to the beginning of the
section table so that sh_addr and sh_offset were the same. If there were
short sections followed by sections with larger alignment requirements
in the binary, this would have caused sh_addr and sh_offset to differ,
and thus the selfcheck to fail.
To allow clang to pass the FIPS selftest, a follow-up commit will mark
the HMAC as volatile. This causes GCC to move the section closer to the
end of the section table, where sh_addr and sh_offset differ.
Switch to determining the location of the .rodata1 section from the ELF
headers in the file on disk to be robust against future section
re-ordering changes in compilers.
Signed-off-by: Clemens Lang <cllang at redhat.com>
---
README | 3 +-
src/fips.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 146 insertions(+), 10 deletions(-)
diff --git a/README b/README
index 3b465c1b..4d7697dd 100644
--- a/README
+++ b/README
@@ -157,7 +157,8 @@
--enable-hmac-binary-check
Include support to check the binary at runtime
against a HMAC checksum. This works only in FIPS
- mode and on systems providing the dladdr function.
+ mode on systems providing the dladdr function and using
+ the ELF binary format.
--with-fips-module-version=version
Specify a string used as a module version for FIPS
diff --git a/src/fips.c b/src/fips.c
index 391b94f1..193af36b 100644
--- a/src/fips.c
+++ b/src/fips.c
@@ -24,8 +24,9 @@
#include <unistd.h>
#include <string.h>
#ifdef ENABLE_HMAC_BINARY_CHECK
+# include <limits.h>
# include <dlfcn.h>
-# include <link.h>
+# include <elf.h>
#endif
#ifdef HAVE_SYSLOG
# include <syslog.h>
@@ -594,23 +595,159 @@ run_random_selftests (void)
static const unsigned char __attribute__ ((section (".rodata1")))
hmac_for_the_implementation[HMAC_LEN];
+/**
+ * Obtain the ElfN_Shdr.sh_offset value for the section with the given name in
+ * the ELF file opened as fp and return it in offset. Rewinds fp to the
+ * beginning on success.
+ */
static gpg_error_t
-hmac256_check (const char *filename, const char *key, struct link_map *lm)
+get_section_offset (FILE *fp, const char *section, unsigned long *offset)
+{
+ unsigned char e_ident[EI_NIDENT];
+#if __WORDSIZE == 64
+ Elf64_Ehdr ehdr;
+ Elf64_Shdr shdr;
+#define ELFCLASS_NATIVE ELFCLASS64
+#else
+ Elf32_Ehdr ehdr;
+ Elf32_Shdr shdr;
+#define ELFCLASS_NATIVE ELFCLASS32
+#endif
+ char *shstrtab;
+ uint16_t e_shnum;
+ uint16_t e_shstrndx;
+ uint16_t e_shidx;
+
+ // verify binary word size
+ if (1 != fread (e_ident, EI_NIDENT, 1, fp))
+ return gpg_error_from_syserror ();
+
+ if (ELFCLASS_NATIVE != e_ident[EI_CLASS])
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ // read the ELF header
+ if (0 != fseek (fp, 0, SEEK_SET))
+ return gpg_error_from_syserror ();
+ if (1 != fread (&ehdr, sizeof (ehdr), 1, fp))
+ return gpg_error_from_syserror ();
+
+ // the section header entry size should match the size of the shdr struct
+ if (ehdr.e_shentsize != sizeof (shdr))
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ /* elf(5): "If the file has no section name string table, this member holds
+ * the value SHN_UNDEF." Without a section name string table, we can not
+ * locate a named section, so error out. */
+ if (ehdr.e_shstrndx == SHN_UNDEF)
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ // jump to and read the first section header
+ if (0 != fseek (fp, ehdr.e_shoff, SEEK_SET))
+ return gpg_error_from_syserror ();
+ if (1 != fread (&shdr, sizeof (shdr), 1, fp))
+ return gpg_error_from_syserror ();
+
+ /* Number of entries in the section header table
+ *
+ * If the number of entries in the section header table is larger than or
+ * equal to SHN_LORESERVE, e_shnum holds the value zero and the real number
+ * of entries in the section header table is held in the sh_size member of
+ * the initial entry in section header table.
+ */
+ e_shnum = ehdr.e_shnum == 0 ? shdr.sh_size : ehdr.e_shnum;
+ /* Section header table index of the section name string table
+ *
+ * If the index of section name string table section is larger than or equal
+ * to SHN_LORESERVE, this member holds SHN_XINDEX and the real index of the
+ * section name string table section is held in the sh_link member of the
+ * initial entry in section header table.
+ */
+ e_shstrndx = ehdr.e_shstrndx == SHN_XINDEX ? shdr.sh_link : ehdr.e_shstrndx;
+
+ // seek to the section header for the section name string table and read it
+ if (0 != fseek (fp, ehdr.e_shoff + e_shstrndx * sizeof (shdr), SEEK_SET))
+ return gpg_error_from_syserror ();
+ if (1 != fread (&shdr, sizeof (shdr), 1, fp))
+ return gpg_error_from_syserror ();
+
+ // this is the section name string table and should have a type of SHT_STRTAB
+ if (shdr.sh_type != SHT_STRTAB)
+ return gpg_error (GPG_ERR_INV_OBJ);
+
+ // read the string table
+ shstrtab = xtrymalloc (shdr.sh_size);
+ if (!shstrtab)
+ return gpg_error_from_syserror ();
+
+ if (0 != fseek (fp, shdr.sh_offset, SEEK_SET))
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ xfree (shstrtab);
+ return err;
+ }
+ if (1 != fread (shstrtab, shdr.sh_size, 1, fp))
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ xfree (shstrtab);
+ return err;
+ }
+
+ // iterate over the sections, compare their names, and if the section name
+ // matches the expected name, return the offset. We already read section 0,
+ // which is always an empty section.
+ if (0 != fseek (fp, ehdr.e_shoff + 1 * sizeof (shdr), SEEK_SET))
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ xfree (shstrtab);
+ return err;
+ }
+ for (e_shidx = 1; e_shidx < e_shnum; e_shidx++)
+ {
+ if (1 != fread (&shdr, sizeof (shdr), 1, fp))
+ {
+ gpg_error_t err = gpg_error_from_syserror ();
+ xfree (shstrtab);
+ return err;
+ }
+ if (0 == strcmp (shstrtab + shdr.sh_name, section))
+ {
+ // found section, return the offset
+ *offset = shdr.sh_offset;
+ xfree (shstrtab);
+
+ if (0 != fseek (fp, 0, SEEK_SET))
+ return gpg_error_from_syserror ();
+ return 0;
+ }
+ }
+
+ // section not found in the file
+ xfree (shstrtab);
+ return gpg_error (GPG_ERR_INV_OBJ);
+}
+
+static gpg_error_t
+hmac256_check (const char *filename, const char *key)
{
gpg_error_t err;
FILE *fp;
gcry_md_hd_t hd;
size_t buffer_size, nread;
char *buffer;
- unsigned long paddr;
+ unsigned long paddr = 0;
unsigned long off = 0;
- paddr = (unsigned long)hmac_for_the_implementation - lm->l_addr;
-
fp = fopen (filename, "rb");
if (!fp)
return gpg_error (GPG_ERR_INV_OBJ);
+ err = get_section_offset (fp, ".rodata1", &paddr);
+ if (err)
+ {
+ fclose (fp);
+ return err;
+ }
+
err = _gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
if (err)
{
@@ -692,13 +829,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 (hmac_for_the_implementation, &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
--
2.34.1
More information about the Gcrypt-devel
mailing list