1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-12 13:18:14 +03:00

pcrlock: Move pe_hash() and uki_hash() to pe-binary.h

Let's move these to shared so we can reuse pe_hash() in the upcoming
systemd-sbsign.
This commit is contained in:
Daan De Meyer 2024-11-03 18:48:53 +01:00
parent 48c5a4cd67
commit cf0238d854
7 changed files with 276 additions and 278 deletions

View File

@ -10,7 +10,6 @@ executables += [
'sources' : files(
'pcrlock.c',
'pcrlock-firmware.c',
'pehash.c',
),
'dependencies' : [
libm,

View File

@ -40,7 +40,7 @@
#include "path-util.h"
#include "pcrextend-util.h"
#include "pcrlock-firmware.h"
#include "pehash.h"
#include "pe-binary.h"
#include "pretty-print.h"
#include "proc-cmdline.h"
#include "random-util.h"

View File

@ -1,264 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "hexdecoct.h"
#include "pe-binary.h"
#include "pehash.h"
#include "sort-util.h"
#include "stat-util.h"
#include "string-table.h"
/* Implements:
*
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
* Section "Calculating the PE Image Hash"
*/
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
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 (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(ASSERT_PTR(a)->PointerToRawData, 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);
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 = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (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) 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) 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 = 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, pe_header->pe.NumberOfSections, section_offset_cmp);
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
if (r < 0)
return r;
p += section->SizeOfRawData;
}
if ((uint64_t) st.st_size > p) {
if (st.st_size - p < certificate_table->Size)
return log_debug_errno(errno, "No space for certificate table, refusing.");
r = hash_file(fd, mdctx, p, st.st_size - p - 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 && 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 = EVP_MD_CTX_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 (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;
}
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 = 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 = EVP_MD_size(md);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
FOREACH_ARRAY(section, sections, 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(unified_sections, _UNIFIED_SECTION_MAX, n);
if (i < 0)
continue;
if (hashes[i])
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
mdctx = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
if (r < 0)
return r;
if (section->SizeOfRawData < section->VirtualSize) {
uint8_t zeroes[1024] = {};
size_t remaining = section->VirtualSize - section->SizeOfRawData;
while (remaining > 0) {
size_t sz = MIN(sizeof(zeroes), remaining);
if (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 (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, EVP_MD_name(md), strna(hs));
}
}
memcpy(ret_hashes, hashes, sizeof(hashes));
zero(hashes);
*ret_hash_size = (unsigned) hsz;
return 0;
}

View File

@ -1,11 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "openssl-util.h"
#include "uki.h"
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);

View File

@ -140,6 +140,7 @@ int openssl_load_key_from_token(KeySourceType private_key_source_type, const cha
typedef struct X509 X509;
typedef struct EVP_PKEY EVP_PKEY;
typedef struct EVP_MD EVP_MD;
typedef struct UI_METHOD UI_METHOD;
static inline void *X509_free(X509 *p) {

View File

@ -1,12 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-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"
#include "uki.h"
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
bool pe_header_is_64bit(const PeHeader *h) {
assert(h);
@ -284,3 +290,263 @@ bool pe_is_native(const PeHeader *pe_header) {
return false;
#endif
}
/* 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 (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(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
}
#endif
int pe_hash(int fd,
const EVP_MD *md,
void **ret_hash,
size_t *ret_hash_size) {
#if HAVE_OPENSSL
_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);
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 = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (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) 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) 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 = 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, pe_header->pe.NumberOfSections, section_offset_cmp);
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
if (r < 0)
return r;
p += section->SizeOfRawData;
}
if ((uint64_t) st.st_size > p) {
if (st.st_size - p < certificate_table->Size)
return log_debug_errno(errno, "No space for certificate table, refusing.");
r = hash_file(fd, mdctx, p, st.st_size - p - 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 && 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 = EVP_MD_CTX_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 (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;
#else
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate PE hash.");
#endif
}
#if HAVE_OPENSSL
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]);
}
#endif
int uki_hash(int fd,
const EVP_MD *md,
void* ret_hashes[static _UNIFIED_SECTION_MAX],
size_t *ret_hash_size) {
#if HAVE_OPENSSL
_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 = 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 = EVP_MD_size(md);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
FOREACH_ARRAY(section, sections, 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(unified_sections, _UNIFIED_SECTION_MAX, n);
if (i < 0)
continue;
if (hashes[i])
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
mdctx = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
if (r < 0)
return r;
if (section->SizeOfRawData < section->VirtualSize) {
uint8_t zeroes[1024] = {};
size_t remaining = section->VirtualSize - section->SizeOfRawData;
while (remaining > 0) {
size_t sz = MIN(sizeof(zeroes), remaining);
if (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 (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, EVP_MD_name(md), strna(hs));
}
}
memcpy(ret_hashes, hashes, sizeof(hashes));
zero(hashes);
*ret_hash_size = (unsigned) hsz;
return 0;
#else
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate UKI hash.");
#endif
}

View File

@ -3,7 +3,10 @@
#include <sys/types.h>
#include "openssl-util.h"
#include "macro-fundamental.h"
#include "sparse-endian.h"
#include "uki.h"
/* When naming things we try to stay close to the official Windows APIs as per:
* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format */
@ -147,3 +150,7 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
bool pe_is_native(const PeHeader *pe_header);
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);