Files
systemd/src/shared/pe-binary.c
T
Daan De Meyer 8c3f64e61f tree-wide: Load libcrypto and libssl via dlopen()
Until now OpenSSL was linked into every binary and library that needed
cryptography, pulling libcrypto (and, for resolved, libssl) into the
address space of services that never touch them at runtime. This commit
moves all OpenSSL usage behind the same dlopen helper pattern that we
already use for other optional libraries (libpam, libseccomp, libxz, …)
so libcrypto/libssl are only loaded on demand.

The bulk of the work lives in src/shared/crypto-util.{c,h} (libcrypto)
and src/shared/ssl-util.{c,h} (libssl), which replace the previous
src/shared/openssl-util.{c,h}:

 - crypto-util.{c,h} declares every libcrypto function we call via
   DLSYM_PROTOTYPE() and resolves them inside dlopen_libcrypto().

 - ssl-util.{c,h} holds the libssl-specific DLSYM_PROTOTYPEs,
   dlopen_libssl(), and the SSL_freep cleanup helper, so translation
   units that only need libcrypto do not pull in libssl declarations.

 - Callers refer to the symbols through sym_* aliases rather than the
   original names.

 - Convenience macros that used to be provided by the OpenSSL headers
   (OPENSSL_free, BN_num_bytes, the sk_TYPE_* helpers, …) are
   reimplemented as sym_* wrappers so no code path needs to fall back
   to the linker-resolved symbols.

 - All _cleanup_ helpers are redefined in terms of the sym_* variants
   (EVP_PKEY_freep, X509_freep, BIO_freep, …) so cleanup attributes
   keep working without pulling in libcrypto symbols at link time.

 - The public crypto-util.c entry points (openssl_pubkey_from_pem,
   openssl_digest_many, openssl_hmac_many, openssl_cipher_many,
   kdf_ss_derive, kdf_kb_hmac_derive, rsa_* / ecc_* helpers,
   pubkey_fingerprint, digest_and_sign, pkcs7_new, x509_fingerprint,
   openssl_extract_public_key, pkey_generate_volume_keys, the load_*
   helpers, …) now call dlopen_libcrypto() at entry before touching any
   sym_* pointer.

The call sites across the tree have been converted to call
dlopen_libcrypto()/dlopen_libssl() at the appropriate entry point
before their first sym_* use, and to use sym_* variants throughout:

 - bootctl, sbsign, measure, pcrlock, pcrextend, tpm2-setup, repart,
   cryptsetup, cryptenroll, homectl, homed, homework, keyutil,
   sysupdate, creds, import, dissect-image, pe-binary, pkcs11-util,
   pkcs7-util, tpm2-util, creds-util. resolved additionally dlopens
   libssl for DoT.

The meson build files are updated to depend on libopenssl_cflags (a
new partial dependency that exposes include paths and compile flags
only, not the linker flags) instead of libopenssl for every target that
previously linked against OpenSSL. Nothing links against libcrypto or
libssl directly anymore.

A new src/sbsign/authenticode.c hosts the Authenticode ASN.1 type
definitions that used to live inline in sbsign.c. The OpenSSL
ASN1_SEQUENCE / ASN1_CHOICE / IMPLEMENT_ASN1_FUNCTIONS macros expand to
code that references libcrypto symbols directly, so to keep this
translation unit unlinked from libcrypto we redirect ASN1_item_* to
the sym_* variants via #define and wrap the ASN1_*_it() getters (which
appear as constant function pointers in static initializers) in small
trampoline functions that forward to the sym_* pointers at runtime.

test-dlopen-so gains assertions for dlopen_libcrypto and dlopen_libssl
so the dlopen contract is exercised in CI, and the openssl-specific
test was renamed from test-openssl.c to test-crypto-util.c to match
the new header naming.
2026-04-24 06:32:42 +02:00

615 lines
21 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "crypto-util.h"
#include "hexdecoct.h"
#include "log.h"
#include "pe-binary.h"
#include "sort-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
/* Note: none of these function change the file position of the provided fd, as they use pread() */
bool pe_header_is_64bit(const PeHeader *h) {
assert(h);
if (le16toh(h->optional.Magic) == UINT16_C(0x010B)) /* PE32 */
return false;
if (le16toh(h->optional.Magic) == UINT16_C(0x020B)) /* PE32+ */
return true;
assert_not_reached();
}
static size_t pe_header_size(const PeHeader *pe_header) {
assert(pe_header);
return offsetof(PeHeader, optional) + le16toh(pe_header->pe.SizeOfOptionalHeader);
}
const IMAGE_DATA_DIRECTORY* pe_header_get_data_directory(
const PeHeader *h,
size_t i) {
assert(h);
if (i >= le32toh(PE_HEADER_OPTIONAL_FIELD(h, NumberOfRvaAndSizes)))
return NULL;
return PE_HEADER_OPTIONAL_FIELD(h, DataDirectory) + i;
}
const IMAGE_SECTION_HEADER* pe_section_table_find(
const IMAGE_SECTION_HEADER *sections,
size_t n_sections,
const char *name) {
size_t n;
assert(name);
assert(sections || n_sections == 0);
n = strlen(name);
if (n > sizeof(sections[0].Name)) /* Too long? */
return NULL;
FOREACH_ARRAY(section, sections, n_sections)
if (memcmp(section->Name, name, n) == 0 &&
(n == sizeof(sections[0].Name) || memeqzero(section->Name + n, sizeof(section->Name) - n)))
return section;
return NULL;
}
const IMAGE_SECTION_HEADER* pe_header_find_section(
const PeHeader *pe_header,
const IMAGE_SECTION_HEADER *sections,
const char *name) {
assert(pe_header);
return pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), name);
}
int pe_load_headers(
int fd,
IMAGE_DOS_HEADER **ret_dos_header,
PeHeader **ret_pe_header) {
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
ssize_t n;
assert(fd >= 0);
dos_header = new(IMAGE_DOS_HEADER, 1);
if (!dos_header)
return log_oom_debug();
n = pread(fd,
dos_header,
sizeof(IMAGE_DOS_HEADER),
0);
if (n < 0)
return log_debug_errno(errno, "Failed to read DOS header: %m");
if ((size_t) n != sizeof(IMAGE_DOS_HEADER))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading MZ executable header.");
if (le16toh(dos_header->e_magic) != UINT16_C(0x5A4D))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks MZ executable header.");
pe_header = new(PeHeader, 1);
if (!pe_header)
return log_oom_debug();
n = pread(fd,
pe_header,
offsetof(PeHeader, optional),
le32toh(dos_header->e_lfanew));
if (n < 0)
return log_debug_errno(errno, "Failed to read PE executable header: %m");
if ((size_t) n != offsetof(PeHeader, optional))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable header.");
if (le32toh(pe_header->signature) != UINT32_C(0x00004550))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks PE executable header.");
if (le16toh(pe_header->pe.SizeOfOptionalHeader) < sizeof_field(PeHeader, optional.Magic))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size too short for magic.");
PeHeader *pe_header_tmp = realloc(pe_header, MAX(sizeof(PeHeader), pe_header_size(pe_header)));
if (!pe_header_tmp)
return log_oom_debug();
pe_header = pe_header_tmp;
n = pread(fd,
&pe_header->optional,
le16toh(pe_header->pe.SizeOfOptionalHeader),
le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional));
if (n < 0)
return log_debug_errno(errno, "Failed to read PE executable optional header: %m");
if ((size_t) n != le16toh(pe_header->pe.SizeOfOptionalHeader))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable optional header.");
if (!IN_SET(le16toh(pe_header->optional.Magic), UINT16_C(0x010B), UINT16_C(0x020B)))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header magic invalid.");
if (pe_header_size(pe_header) !=
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory) +
sizeof(IMAGE_DATA_DIRECTORY) * (uint64_t) le32toh(PE_HEADER_OPTIONAL_FIELD(pe_header, NumberOfRvaAndSizes)))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size mismatch.");
if (ret_dos_header)
*ret_dos_header = TAKE_PTR(dos_header);
if (ret_pe_header)
*ret_pe_header = TAKE_PTR(pe_header);
return 0;
}
int pe_load_sections(
int fd,
const IMAGE_DOS_HEADER *dos_header,
const PeHeader *pe_header,
IMAGE_SECTION_HEADER **ret_sections) {
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
size_t nos;
ssize_t n;
assert(fd >= 0);
assert(dos_header);
assert(pe_header);
nos = le16toh(pe_header->pe.NumberOfSections);
sections = new(IMAGE_SECTION_HEADER, nos);
if (!sections)
return log_oom_debug();
n = pread(fd,
sections,
sizeof(IMAGE_SECTION_HEADER) * nos,
le32toh(dos_header->e_lfanew) + pe_header_size(pe_header));
if (n < 0)
return log_debug_errno(errno, "Failed to read section table: %m");
if ((size_t) n != sizeof(IMAGE_SECTION_HEADER) * nos)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading section table.");
if (ret_sections)
*ret_sections = TAKE_PTR(sections);
return 0;
}
int pe_read_section_data(
int fd,
const IMAGE_SECTION_HEADER *section,
size_t max_size,
void **ret,
size_t *ret_size) {
assert(fd >= 0);
assert(section);
size_t n = le32toh(section->VirtualSize);
if (n > MIN(max_size, (size_t) SSIZE_MAX))
return -E2BIG;
_cleanup_free_ void *data = malloc(n+1);
if (!data)
return -ENOMEM;
ssize_t ss = pread(fd, data, n, le32toh(section->PointerToRawData));
if (ss < 0)
return -errno;
if ((size_t) ss != n)
return -EIO;
if (ret_size)
*ret_size = n;
else {
/* Check that there are no embedded NUL bytes if the caller doesn't want to know the size
* (i.e. treats the blob as a string) */
const char *nul;
nul = memchr(data, 0, n);
if (nul && !memeqzero(nul, n - (nul - (const char*) data))) /* If there's a NUL it must only be NULs from there on */
return -EBADMSG;
}
if (ret) {
((uint8_t*) data)[n] = 0; /* NUL terminate, no matter what */
*ret = TAKE_PTR(data);
}
return 0;
}
int pe_read_section_data_by_name(
int fd,
const PeHeader *pe_header,
const IMAGE_SECTION_HEADER *sections,
const char *name,
size_t max_size,
void **ret,
size_t *ret_size) {
const IMAGE_SECTION_HEADER *section;
assert(fd >= 0);
assert(pe_header);
assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
assert(name);
section = pe_header_find_section(pe_header, sections, name);
if (!section)
return -ENXIO;
return pe_read_section_data(fd, section, max_size, ret, ret_size);
}
bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) {
assert(pe_header);
assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
return false;
/* Note that the UKI spec only requires .linux, but we are stricter here, and require .osrel too,
* since for sd-boot it just doesn't make sense to not have that. */
return
pe_header_find_section(pe_header, sections, ".osrel") &&
pe_header_find_section(pe_header, sections, ".linux");
}
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) {
assert(pe_header);
assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
return false;
/* Add-ons do not have a Linux kernel, but do have one of .cmdline, .dtb, .initrd or .ucode (currently) */
return !pe_header_find_section(pe_header, sections, ".linux") &&
(pe_header_find_section(pe_header, sections, ".cmdline") ||
pe_header_find_section(pe_header, sections, ".dtb") ||
pe_header_find_section(pe_header, sections, ".initrd") ||
pe_header_find_section(pe_header, sections, ".ucode"));
}
bool pe_is_native(const PeHeader *pe_header) {
assert(pe_header);
#ifdef _IMAGE_FILE_MACHINE_NATIVE
return le16toh(pe_header->pe.Machine) == _IMAGE_FILE_MACHINE_NATIVE;
#else
return false;
#endif
}
int pe_is_native_fd(int fd) {
_cleanup_free_ PeHeader *pe_header = NULL;
int r;
r = pe_load_headers(fd, /* ret_dos_header= */ NULL, &pe_header);
if (r < 0)
return r;
return pe_is_native(pe_header);
}
/* Implements:
*
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
* → Section "Calculating the PE Image Hash"
*/
#if HAVE_OPENSSL
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
uint8_t buffer[64*1024];
log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
while (size > 0) {
size_t m = MIN(size, sizeof(buffer));
ssize_t n;
n = pread(fd, buffer, m, offset);
if (n < 0)
return log_debug_errno(errno, "Failed to read file for hashing: %m");
if ((size_t) n != m)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
if (sym_EVP_DigestUpdate(md_ctx, buffer, m) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
offset += m;
size -= m;
}
return 0;
}
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
return CMP(le32toh(ASSERT_PTR(a)->PointerToRawData), le32toh(ASSERT_PTR(b)->PointerToRawData));
}
int pe_hash(int fd,
const EVP_MD *md,
void **ret_hash,
size_t *ret_hash_size) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
const IMAGE_DATA_DIRECTORY *certificate_table;
struct stat st;
uint64_t p, q;
int r;
assert(fd >= 0);
assert(md);
assert(ret_hash_size);
assert(ret_hash);
r = dlopen_libcrypto(LOG_DEBUG);
if (r < 0)
return r;
if (fstat(fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat file: %m");
r = stat_verify_regular(&st);
if (r < 0)
return log_debug_errno(r, "Not a regular file: %m");
r = pe_load_headers(fd, &dos_header, &pe_header);
if (r < 0)
return r;
r = pe_load_sections(fd, dos_header, pe_header, &sections);
if (r < 0)
return r;
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
if (!certificate_table)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
mdctx = sym_EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
/* Everything from beginning of file to CheckSum field in PE header */
p = (uint64_t) le32toh(dos_header->e_lfanew) +
offsetof(PeHeader, optional.CheckSum);
r = hash_file(fd, mdctx, 0, p);
if (r < 0)
return r;
p += sizeof(le32_t);
/* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
q = (uint64_t) le32toh(dos_header->e_lfanew) +
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
r = hash_file(fd, mdctx, p, q - p);
if (r < 0)
return r;
q += sizeof(IMAGE_DATA_DIRECTORY);
/* The rest of the header + the section table */
p = le32toh(pe_header->optional.SizeOfHeaders);
if (p < q)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
r = hash_file(fd, mdctx, q, p - q);
if (r < 0)
return r;
/* Sort by location in file */
typesafe_qsort(sections, le16toh(pe_header->pe.NumberOfSections), section_offset_cmp);
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), le32toh(section->SizeOfRawData));
if (r < 0)
return r;
p += le32toh(section->SizeOfRawData);
}
if ((uint64_t) st.st_size > p) {
if ((uint64_t) st.st_size - p < le32toh(certificate_table->Size))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "No space for certificate table, refusing.");
r = hash_file(fd, mdctx, p, (uint64_t) st.st_size - p - le32toh(certificate_table->Size));
if (r < 0)
return r;
/* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
if (st.st_size % 8 != 0 && sym_EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
}
int hsz = sym_EVP_MD_CTX_get_size(mdctx);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
unsigned hash_size = (unsigned) hsz;
_cleanup_free_ void *hash = malloc(hsz);
if (!hash)
return log_oom_debug();
if (sym_EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
assert(hash_size == (unsigned) hsz);
*ret_hash = TAKE_PTR(hash);
*ret_hash_size = hash_size;
return 0;
}
int pe_checksum(int fd, uint32_t *ret) {
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
struct stat st;
int r;
assert(fd >= 0);
assert(ret);
if (fstat(fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat file: %m");
r = pe_load_headers(fd, &dos_header, &pe_header);
if (r < 0)
return r;
uint32_t checksum = 0, checksum_offset = le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum);
size_t off = 0;
for (;;) {
uint16_t buf[32*1024];
ssize_t n = pread(fd, buf, sizeof(buf), off);
if (n == 0)
break;
if (n < 0)
return log_debug_errno(errno, "Failed to read from PE file: %m");
if (n % sizeof(uint16_t) != 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file");
for (size_t i = 0; i < (size_t) n / 2; i++) {
if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum))
continue;
uint16_t val = le16toh(buf[i]);
checksum += val;
checksum = (checksum >> 16) + (checksum & 0xffff);
}
off += n;
}
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += off;
*ret = checksum;
return 0;
}
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
static void section_hash_array_done(SectionHashArray *array) {
assert(array);
for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
free((*array)[i]);
}
int uki_hash(int fd,
const EVP_MD *md,
void* ret_hashes[static _UNIFIED_SECTION_MAX],
size_t *ret_hash_size) {
_cleanup_(section_hash_array_done) SectionHashArray hashes = {};
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
int r;
assert(fd >= 0);
assert(ret_hashes);
assert(ret_hash_size);
r = dlopen_libcrypto(LOG_DEBUG);
if (r < 0)
return r;
r = pe_load_headers(fd, &dos_header, &pe_header);
if (r < 0)
return r;
r = pe_load_sections(fd, dos_header, pe_header, &sections);
if (r < 0)
return r;
int hsz = sym_EVP_MD_get_size(md);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
_cleanup_free_ char *n = NULL;
ssize_t i;
n = memdup_suffix0(section->Name, sizeof(section->Name));
if (!n)
return log_oom_debug();
i = string_table_lookup_from_string(unified_sections, _UNIFIED_SECTION_MAX, n);
if (i < 0)
continue;
if (hashes[i])
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
mdctx = sym_EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData)));
if (r < 0)
return r;
if (le32toh(section->SizeOfRawData) < le32toh(section->VirtualSize)) {
uint8_t zeroes[1024] = {};
size_t remaining = le32toh(section->VirtualSize) - le32toh(section->SizeOfRawData);
while (remaining > 0) {
size_t sz = MIN(sizeof(zeroes), remaining);
if (sym_EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
remaining -= sz;
}
}
hashes[i] = malloc(hsz);
if (!hashes[i])
return log_oom_debug();
unsigned hash_size = (unsigned) hsz;
if (sym_EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
assert(hash_size == (unsigned) hsz);
if (DEBUG_LOGGING) {
_cleanup_free_ char *hs = NULL;
hs = hexmem(hashes[i], hsz);
log_debug("Section %s with %s is %s.", n, sym_EVP_MD_get0_name(md), strna(hs));
}
}
memcpy(ret_hashes, hashes, sizeof(hashes));
zero(hashes);
*ret_hash_size = (unsigned) hsz;
return 0;
}
#endif