7e0fcaf4ab
Resolves: rhbz#2157966
1009 lines
33 KiB
Diff
1009 lines
33 KiB
Diff
From beb5d6df5c5785db7c32a24a5d2a351cb964bfbc Mon Sep 17 00:00:00 2001
|
|
From: Clemens Lang via Gcrypt-devel <gcrypt-devel@lists.gnupg.org>
|
|
Date: Mon, 14 Feb 2022 18:49:59 +0100
|
|
Subject: [PATCH] fips: Use ELF header to find hmac file offset
|
|
|
|
* src/fips.c [ENABLE_HMAC_BINARY_CHECK] (hmac256_check): Use ELF headers
|
|
to locate the file offset for the HMAC in addition to 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 value in memory at runtime, but the offset
|
|
in the file can be different. Specifically, the old code computed
|
|
a value relative to ElfW(Phdr).p_vaddr, but the offset in the file is
|
|
relative to ElfW(Phdr).p_offset. These values can differ, so the
|
|
computed address at runtime must be translated into a file offset
|
|
relative to p_offset.
|
|
|
|
This is largely cosmetic, since the text section that should contain the
|
|
HMAC usually has both p_vaddr and p_offset set to 0.
|
|
|
|
Signed-off-by: Clemens Lang <cllang@redhat.com>
|
|
---
|
|
README | 3 ++-
|
|
src/fips.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++-----
|
|
2 files changed, 69 insertions(+), 7 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..c40274d9 100644
|
|
--- a/src/fips.c
|
|
+++ b/src/fips.c
|
|
@@ -25,6 +25,8 @@
|
|
#include <string.h>
|
|
#ifdef ENABLE_HMAC_BINARY_CHECK
|
|
# include <dlfcn.h>
|
|
+# include <elf.h>
|
|
+# include <limits.h>
|
|
# include <link.h>
|
|
#endif
|
|
#ifdef HAVE_SYSLOG
|
|
@@ -594,6 +596,57 @@ run_random_selftests (void)
|
|
static const unsigned char __attribute__ ((section (".rodata1")))
|
|
hmac_for_the_implementation[HMAC_LEN];
|
|
|
|
+/**
|
|
+ * Determine the offset of the given virtual address in the ELF file opened as
|
|
+ * fp and return it in offset. Rewinds fp to the beginning on success.
|
|
+ */
|
|
+static gpg_error_t
|
|
+get_file_offset (FILE *fp, unsigned long paddr, unsigned long *offset)
|
|
+{
|
|
+ ElfW (Ehdr) ehdr;
|
|
+ ElfW (Phdr) phdr;
|
|
+ uint16_t e_phidx;
|
|
+
|
|
+ // 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_phentsize != sizeof (phdr))
|
|
+ return gpg_error (GPG_ERR_INV_OBJ);
|
|
+ if (ehdr.e_phoff == 0)
|
|
+ return gpg_error (GPG_ERR_INV_OBJ);
|
|
+
|
|
+ // jump to the first program header
|
|
+ if (0 != fseek (fp, ehdr.e_phoff, SEEK_SET))
|
|
+ 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 paddr in the file using the program header's
|
|
+ // p_offset field.
|
|
+ for (e_phidx = 0; e_phidx < ehdr.e_phnum; e_phidx++)
|
|
+ {
|
|
+ if (1 != fread (&phdr, sizeof (phdr), 1, fp))
|
|
+ return gpg_error_from_syserror ();
|
|
+ if (phdr.p_type == PT_LOAD && phdr.p_vaddr <= paddr
|
|
+ && phdr.p_vaddr + phdr.p_memsz > paddr)
|
|
+ {
|
|
+ // found section, compute the offset of paddr in the file
|
|
+ *offset = phdr.p_offset + (paddr - phdr.p_vaddr);
|
|
+
|
|
+ if (0 != fseek (fp, 0, SEEK_SET))
|
|
+ return gpg_error_from_syserror ();
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // section not found in the file
|
|
+ return gpg_error (GPG_ERR_INV_OBJ);
|
|
+}
|
|
+
|
|
static gpg_error_t
|
|
hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
{
|
|
@@ -603,6 +656,7 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
size_t buffer_size, nread;
|
|
char *buffer;
|
|
unsigned long paddr;
|
|
+ unsigned long offset = 0;
|
|
unsigned long off = 0;
|
|
|
|
paddr = (unsigned long)hmac_for_the_implementation - lm->l_addr;
|
|
@@ -611,6 +665,13 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
if (!fp)
|
|
return gpg_error (GPG_ERR_INV_OBJ);
|
|
|
|
+ err = get_file_offset (fp, paddr, &offset);
|
|
+ if (err)
|
|
+ {
|
|
+ fclose (fp);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
err = _gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
|
|
if (err)
|
|
{
|
|
@@ -651,14 +712,14 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
nread = fread (buffer+HMAC_LEN, 1, buffer_size, fp);
|
|
if (nread < buffer_size)
|
|
{
|
|
- if (off - HMAC_LEN <= paddr && paddr <= off + nread)
|
|
- memset (buffer + HMAC_LEN + paddr - off, 0, HMAC_LEN);
|
|
+ if (off - HMAC_LEN <= offset && offset <= off + nread)
|
|
+ memset (buffer + HMAC_LEN + offset - off, 0, HMAC_LEN);
|
|
_gcry_md_write (hd, buffer, nread+HMAC_LEN);
|
|
break;
|
|
}
|
|
|
|
- if (off - HMAC_LEN <= paddr && paddr <= off + nread)
|
|
- memset (buffer + HMAC_LEN + paddr - off, 0, HMAC_LEN);
|
|
+ if (off - HMAC_LEN <= offset && offset <= off + nread)
|
|
+ memset (buffer + HMAC_LEN + offset - off, 0, HMAC_LEN);
|
|
_gcry_md_write (hd, buffer, nread);
|
|
memcpy (buffer, buffer+buffer_size, HMAC_LEN);
|
|
off += nread;
|
|
@@ -694,8 +755,8 @@ check_binary_integrity (void)
|
|
const char *key = KEY_FOR_BINARY_CHECK;
|
|
void *extra_info;
|
|
|
|
- if (!dladdr1 (hmac_for_the_implementation,
|
|
- &info, &extra_info, RTLD_DL_LINKMAP))
|
|
+ if (!dladdr1 (hmac_for_the_implementation, &info, &extra_info,
|
|
+ RTLD_DL_LINKMAP))
|
|
err = gpg_error_from_syserror ();
|
|
else
|
|
err = hmac256_check (info.dli_fname, key, extra_info);
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From 521500624b4b11538d206137205e2a511dad7072 Mon Sep 17 00:00:00 2001
|
|
From: NIIBE Yutaka <gniibe@fsij.org>
|
|
Date: Tue, 15 Feb 2022 20:38:02 +0900
|
|
Subject: [PATCH] fips: Fix previous commit.
|
|
|
|
--
|
|
|
|
Coding style fix.
|
|
|
|
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
|
|
---
|
|
src/fips.c | 64 +++++++++++++++++++++++++++---------------------------
|
|
1 file changed, 32 insertions(+), 32 deletions(-)
|
|
|
|
diff --git a/src/fips.c b/src/fips.c
|
|
index c40274d9..f16bce8b 100644
|
|
--- a/src/fips.c
|
|
+++ b/src/fips.c
|
|
@@ -596,54 +596,55 @@ run_random_selftests (void)
|
|
static const unsigned char __attribute__ ((section (".rodata1")))
|
|
hmac_for_the_implementation[HMAC_LEN];
|
|
|
|
-/**
|
|
- * Determine the offset of the given virtual address in the ELF file opened as
|
|
- * fp and return it in offset. Rewinds fp to the beginning on success.
|
|
+/*
|
|
+ * 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
|
|
+ * beginning on success.
|
|
*/
|
|
static gpg_error_t
|
|
-get_file_offset (FILE *fp, unsigned long paddr, unsigned long *offset)
|
|
+get_file_offset (FILE *fp, unsigned long addr, unsigned long *offset)
|
|
{
|
|
ElfW (Ehdr) ehdr;
|
|
ElfW (Phdr) phdr;
|
|
uint16_t e_phidx;
|
|
|
|
- // read the ELF header
|
|
- if (0 != fseek (fp, 0, SEEK_SET))
|
|
+ /* Read the ELF header */
|
|
+ if (fseek (fp, 0, SEEK_SET) != 0)
|
|
return gpg_error_from_syserror ();
|
|
- if (1 != fread (&ehdr, sizeof (ehdr), 1, fp))
|
|
+ if (fread (&ehdr, sizeof (ehdr), 1, fp) != 1)
|
|
return gpg_error_from_syserror ();
|
|
|
|
- // the section header entry size should match the size of the shdr struct
|
|
+ /* The program header entry size should match the size of the phdr struct */
|
|
if (ehdr.e_phentsize != sizeof (phdr))
|
|
return gpg_error (GPG_ERR_INV_OBJ);
|
|
if (ehdr.e_phoff == 0)
|
|
return gpg_error (GPG_ERR_INV_OBJ);
|
|
|
|
- // jump to the first program header
|
|
- if (0 != fseek (fp, ehdr.e_phoff, SEEK_SET))
|
|
+ /* Jump to the first program header */
|
|
+ if (fseek (fp, ehdr.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 paddr in the file using the program header's
|
|
- // p_offset field.
|
|
+ /* 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++)
|
|
{
|
|
- if (1 != fread (&phdr, sizeof (phdr), 1, fp))
|
|
+ if (fread (&phdr, sizeof (phdr), 1, fp) != 1)
|
|
return gpg_error_from_syserror ();
|
|
- if (phdr.p_type == PT_LOAD && phdr.p_vaddr <= paddr
|
|
- && phdr.p_vaddr + phdr.p_memsz > paddr)
|
|
+ if (phdr.p_type == PT_LOAD
|
|
+ && phdr.p_vaddr <= addr && addr < phdr.p_vaddr + phdr.p_memsz)
|
|
{
|
|
- // found section, compute the offset of paddr in the file
|
|
- *offset = phdr.p_offset + (paddr - phdr.p_vaddr);
|
|
+ /* Found segment, compute the offset of ADDR in the file */
|
|
+ *offset = phdr.p_offset + (addr - phdr.p_vaddr);
|
|
|
|
- if (0 != fseek (fp, 0, SEEK_SET))
|
|
+ if (fseek (fp, 0, SEEK_SET) != 0)
|
|
return gpg_error_from_syserror ();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
- // section not found in the file
|
|
+ /* Segment not found in the file */
|
|
return gpg_error (GPG_ERR_INV_OBJ);
|
|
}
|
|
|
|
@@ -655,17 +656,16 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
gcry_md_hd_t hd;
|
|
size_t buffer_size, nread;
|
|
char *buffer;
|
|
- unsigned long paddr;
|
|
+ unsigned long addr;
|
|
unsigned long offset = 0;
|
|
- unsigned long off = 0;
|
|
-
|
|
- paddr = (unsigned long)hmac_for_the_implementation - lm->l_addr;
|
|
+ unsigned long pos = 0;
|
|
|
|
+ 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, paddr, &offset);
|
|
+ err = get_file_offset (fp, addr, &offset);
|
|
if (err)
|
|
{
|
|
fclose (fp);
|
|
@@ -698,7 +698,7 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
}
|
|
|
|
nread = fread (buffer, 1, HMAC_LEN, fp);
|
|
- off += nread;
|
|
+ pos += nread;
|
|
if (nread < HMAC_LEN)
|
|
{
|
|
xfree (buffer);
|
|
@@ -712,17 +712,17 @@ hmac256_check (const char *filename, const char *key, struct link_map *lm)
|
|
nread = fread (buffer+HMAC_LEN, 1, buffer_size, fp);
|
|
if (nread < buffer_size)
|
|
{
|
|
- if (off - HMAC_LEN <= offset && offset <= off + nread)
|
|
- memset (buffer + HMAC_LEN + offset - off, 0, HMAC_LEN);
|
|
+ if (pos - HMAC_LEN <= offset && offset <= pos + nread)
|
|
+ memset (buffer + HMAC_LEN + offset - pos, 0, HMAC_LEN);
|
|
_gcry_md_write (hd, buffer, nread+HMAC_LEN);
|
|
break;
|
|
}
|
|
|
|
- if (off - HMAC_LEN <= offset && offset <= off + nread)
|
|
- memset (buffer + HMAC_LEN + offset - off, 0, HMAC_LEN);
|
|
+ if (pos - HMAC_LEN <= offset && offset <= pos + nread)
|
|
+ memset (buffer + HMAC_LEN + offset - pos, 0, HMAC_LEN);
|
|
_gcry_md_write (hd, buffer, nread);
|
|
memcpy (buffer, buffer+buffer_size, HMAC_LEN);
|
|
- off += nread;
|
|
+ pos += nread;
|
|
}
|
|
|
|
if (ferror (fp))
|
|
--
|
|
2.39.1
|
|
|
|
|
|
From 9dcf9305962b90febdf2d7cc73b49feadbf6a01f Mon Sep 17 00:00:00 2001
|
|
From: NIIBE Yutaka <gniibe@fsij.org>
|
|
Date: Wed, 16 Feb 2022 14:06:02 +0900
|
|
Subject: [PATCH] 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 <gniibe@fsij.org>
|
|
---
|
|
configure.ac | 1 +
|
|
src/Makefile.am | 4 +--
|
|
src/fips.c | 73 +++++++++++++++++++++++++++++--------------
|
|
src/genhmac.sh | 83 +++++++++++++++++++++++++++++++++++++++++++++++++
|
|
4 files changed, 136 insertions(+), 25 deletions(-)
|
|
create mode 100755 src/genhmac.sh
|
|
|
|
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 > $@
|
|
+ READELF=$(READELF) AWK=$(AWK) $(srcdir)/genhmac.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 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
|
|
--
|
|
2.39.1
|
|
|
|
|
|
From a340e980388243ceae6df57d101036f3f2a955be Mon Sep 17 00:00:00 2001
|
|
From: NIIBE Yutaka <gniibe@fsij.org>
|
|
Date: Wed, 16 Feb 2022 20:08:15 +0900
|
|
Subject: [PATCH] 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.
|
|
|
|
--
|
|
|
|
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>
|
|
---
|
|
src/Makefile.am | 8 +-
|
|
src/fips.c | 167 +++++++++++++---------
|
|
src/{genhmac.sh => gen-note-integrity.sh} | 34 ++++-
|
|
3 files changed, 134 insertions(+), 75 deletions(-)
|
|
rename src/{genhmac.sh => gen-note-integrity.sh} (78%)
|
|
|
|
diff --git a/src/Makefile.am b/src/Makefile.am
|
|
index 72100671..b8bb187a 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
|
|
@@ -143,13 +143,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
|
|
similarity index 78%
|
|
rename from src/genhmac.sh
|
|
rename to 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.
|
|
--
|
|
2.39.1
|
|
|
|
|