diff --git a/libgcrypt-1.10.0-fips-integrity.patch b/libgcrypt-1.10.0-fips-integrity.patch new file mode 100644 index 0000000..8254381 --- /dev/null +++ b/libgcrypt-1.10.0-fips-integrity.patch @@ -0,0 +1,1008 @@ +From beb5d6df5c5785db7c32a24a5d2a351cb964bfbc Mon Sep 17 00:00:00 2001 +From: Clemens Lang via Gcrypt-devel +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 +--- + 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 + #ifdef ENABLE_HMAC_BINARY_CHECK + # include ++# include ++# include + # include + #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 +Date: Tue, 15 Feb 2022 20:38:02 +0900 +Subject: [PATCH] fips: Fix previous commit. + +-- + +Coding style fix. + +Signed-off-by: NIIBE Yutaka +--- + 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 +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 +--- + 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 . ++# ++ ++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 +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 +--- + 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 + + diff --git a/libgcrypt-1.10.0-fips-integrity2.patch b/libgcrypt-1.10.0-fips-integrity2.patch new file mode 100644 index 0000000..c1b7d9e --- /dev/null +++ b/libgcrypt-1.10.0-fips-integrity2.patch @@ -0,0 +1,158 @@ +From 3c8b6c4a9cad59c5e1db5706f6774a3141b60210 Mon Sep 17 00:00:00 2001 +From: NIIBE Yutaka +Date: Thu, 17 Feb 2022 10:28:05 +0900 +Subject: [PATCH] fips: Fix gen-note-integrity.sh script not to use cmp + utility. + +* src/gen-note-integrity.sh: Simplify detecting 32-bit machine +or 64-bit machine. + +-- + +GnuPG-bug-id: 5835 +Signed-off-by: NIIBE Yutaka +--- + src/gen-note-integrity.sh | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/gen-note-integrity.sh b/src/gen-note-integrity.sh +index 969fdca6..878d7095 100755 +--- a/src/gen-note-integrity.sh ++++ b/src/gen-note-integrity.sh +@@ -73,9 +73,9 @@ 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 ++BYTE002=$(printf '%b' '\002') ++CLASS_BYTE=$(dd ibs=1 skip=4 count=1 if=$FILE status=none) ++if test "$CLASS_BYTE" = "$BYTE002"; then + CLASS=64 + HEADER_SIZE=64 + else +@@ -112,4 +112,4 @@ END { print offset}") + 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 ++rm -f header-fixed.bin +-- +2.39.1 + + +From 052c5ef4cea56772b7015e36f231fa0bcbf91410 Mon Sep 17 00:00:00 2001 +From: NIIBE Yutaka +Date: Thu, 17 Feb 2022 11:21:35 +0900 +Subject: [PATCH] fips: Clarify what to be hashed for the integrity check. + +* src/fips.c (get_file_offset): Compute the maximum offset +of segments. +* src/gen-note-integrity.sh: Likewise. + +-- + +The result is same (in current format of ELF program). +Semantics is more clear. It hashes: + + - From the start of shared library file, + - fixed up the ELF header to exclude link-time information, + - up to the last segment. + +Signed-off-by: NIIBE Yutaka +--- + src/fips.c | 20 +++++++++----------- + src/gen-note-integrity.sh | 20 ++++++++++++++------ + 2 files changed, 23 insertions(+), 17 deletions(-) + +diff --git a/src/fips.c b/src/fips.c +index d798d577..89f8204b 100644 +--- a/src/fips.c ++++ b/src/fips.c +@@ -595,7 +595,7 @@ run_random_selftests (void) + + /* + * 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. ++ * EHDR_P, determine the maximum offset of segments in R_OFFSET. + * Also, find the section which contains the hmac value and return it + * in HMAC. Rewinds FP to the beginning on success. + */ +@@ -624,24 +624,22 @@ get_file_offset (FILE *fp, ElfW (Ehdr) *ehdr_p, + if (fseek (fp, ehdr_p->e_phoff, SEEK_SET) != 0) + return gpg_error_from_syserror (); + +- /* Iterate over the program headers, determine the last loadable +- segment. */ ++ /* Iterate over the program headers, determine the last offset of ++ segments. */ + for (i = 0; i < ehdr_p->e_phnum; i++) + { ++ unsigned long off; ++ + if (fread (&phdr, sizeof (phdr), 1, fp) != 1) + return gpg_error_from_syserror (); + +- if (phdr.p_type == PT_PHDR) +- continue; +- +- if (phdr.p_type != PT_LOAD) +- break; +- +- off_segment = phdr.p_offset + phdr.p_filesz; ++ off = phdr.p_offset + phdr.p_filesz; ++ if (off_segment < off) ++ off_segment = off; + } + + if (!off_segment) +- /* The segment not found in the file */ ++ /* No segment found in the file */ + return gpg_error (GPG_ERR_INV_OBJ); + + /* The section header entry size should match the size of the shdr struct */ +diff --git a/src/gen-note-integrity.sh b/src/gen-note-integrity.sh +index 878d7095..50071bf5 100755 +--- a/src/gen-note-integrity.sh ++++ b/src/gen-note-integrity.sh +@@ -95,21 +95,29 @@ else + dd ibs=1 count=6 if=/dev/zero status=none + fi > header-fixed.bin + +-# Compute the end of loadable segment. ++# ++# Compute the end of segments, and emit the COUNT to read ++# (For each segment in program headers, calculate the offset ++# and select the maximum) + # + # 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}") ++COUNT=$($READELF --wide --program-headers $FILE | \ ++ $AWK $AWK_OPTION \ ++"BEGIN { max_offset=0 } ++/^\$/ { if (program_headers_start) program_headers_end=1 } ++(program_headers_start && !program_headers_end) { offset = \$2 + \$5 } ++(max_offset < offset) { max_offset = offset } ++/^ Type/ { program_headers_start=1 } ++END { print max_offset- $HEADER_SIZE }") + + # +-# Feed the header fixed and loadable segments to HMAC256 ++# Feed the header fixed and all 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) \ ++ dd ibs=1 skip=$HEADER_SIZE count=$COUNT if=$FILE status=none) \ + | ./hmac256 --stdkey --binary + + rm -f header-fixed.bin +-- +2.39.1 + + diff --git a/libgcrypt.spec b/libgcrypt.spec index 79216f4..3e07a37 100644 --- a/libgcrypt.spec +++ b/libgcrypt.spec @@ -38,6 +38,14 @@ Patch11: libgcrypt-1.10.0-fips-kdf.patch # c34c9e70055ee43e5ef257384fa15941f064e5a4 # https://gitlab.com/redhat-crypto/libgcrypt/libgcrypt-mirror/-/merge_requests/13 Patch12: libgcrypt-1.10.0-fips-indicator.patch +# beb5d6df5c5785db7c32a24a5d2a351cb964bfbc +# 521500624b4b11538d206137205e2a511dad7072 +# 9dcf9305962b90febdf2d7cc73b49feadbf6a01f +# a340e980388243ceae6df57d101036f3f2a955be +Patch13: libgcrypt-1.10.0-fips-integrity.patch +# 3c8b6c4a9cad59c5e1db5706f6774a3141b60210 +# 052c5ef4cea56772b7015e36f231fa0bcbf91410 +Patch14: libgcrypt-1.10.0-fips-integrity2.patch %global gcrylibdir %{_libdir} %global gcrysoname libgcrypt.so.20 @@ -83,6 +91,8 @@ applications using libgcrypt. %patch10 -p1 %patch11 -p1 %patch12 -p1 +%patch13 -p1 +%patch14 -p1 %build # This package has a configure test which uses ASMs, but does not link the @@ -126,12 +136,12 @@ LIBGCRYPT_FORCE_FIPS_MODE=1 make check %{?__debug_package:%{__debug_install_post}} \ %{__arch_install_post} \ %{__os_install_post} \ - dd if=/dev/zero of=%{libpath}.hmac bs=32 count=1 \ - objcopy --update-section .rodata1=%{libpath}.hmac %{libpath} %{libpath}.empty \ - src/hmac256 --binary %{hmackey} %{libpath}.empty > %{libpath}.hmac \ - objcopy --update-section .rodata1=%{libpath}.hmac %{libpath}.empty %{libpath}.new \ + cd src \ + sed -i -e 's|FILE=.*|FILE=\\\$1|' gen-note-integrity.sh \ + READELF=readelf AWK=awk ECHO_N="-n" bash gen-note-integrity.sh %{libpath} > %{libpath}.hmac \ + objcopy --update-section .note.fdo.integrity=%{libpath}.hmac %{libpath} %{libpath}.new \ mv -f %{libpath}.new %{libpath} \ - rm -f %{libpath}.hmac %{libpath}.empty + rm -f %{libpath}.hmac %{nil} %install