Files
systemd/src/shared/pe-binary.h
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

161 lines
5.8 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sparse-endian.h"
#include "uki.h"
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
/* 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 */
typedef struct _packed_ _IMAGE_DOS_HEADER {
le16_t e_magic;
le16_t e_cblp;
le16_t e_cp;
le16_t e_crlc;
le16_t e_cparhdr;
le16_t e_minalloc;
le16_t e_maxalloc;
le16_t e_ss;
le16_t e_sp;
le16_t e_csum;
le16_t e_ip;
le16_t e_cs;
le16_t e_lfarlc;
le16_t e_ovno;
le16_t e_res[4];
le16_t e_oemid;
le16_t e_oeminfo;
le16_t e_res2[10];
le32_t e_lfanew;
} IMAGE_DOS_HEADER;
typedef struct _packed_ _IMAGE_FILE_HEADER {
le16_t Machine;
le16_t NumberOfSections;
le32_t TimeDateStamp;
le32_t PointerToSymbolTable;
le32_t NumberOfSymbols;
le16_t SizeOfOptionalHeader;
le16_t Characteristics;
} IMAGE_FILE_HEADER;
typedef struct _packed_ _IMAGE_DATA_DIRECTORY {
le32_t VirtualAddress;
le32_t Size;
} IMAGE_DATA_DIRECTORY;
typedef struct _packed_ _IMAGE_OPTIONAL_HEADER {
/* Standard fields */
le16_t Magic;
uint8_t MajorLinkerVersion;
uint8_t MinorLinkerVersion;
le32_t SizeOfCode;
le32_t SizeOfInitializedData;
le32_t SizeOfUninitializedData;
le32_t AddressOfEntryPoint;
le32_t BaseOfCode;
/* Here the PE32 and PE32+ headers differ: PE32+ has one 64bit field, PE32+ has two 32bit fields */
union {
struct {
le32_t BaseOfData;
le32_t pe32_ImageBase;
};
le64_t pe32plus_ImageBase;
};
/* Additional fields */
le32_t SectionAlignment;
le32_t FileAlignment;
le16_t MajorOperatingSystemVersion;
le16_t MinorOperatingSystemVersion;
le16_t MajorImageVersion;
le16_t MinorImageVersion;
le16_t MajorSubsystemVersion;
le16_t MinorSubsystemVersion;
le32_t Win32VersionValue;
le32_t SizeOfImage;
le32_t SizeOfHeaders;
le32_t CheckSum;
le16_t Subsystem;
le16_t DllCharacteristics;
/* Here similar: on PE32+ some fields are 64bit that are 32bit on PE32. */
union {
struct {
le32_t pe32_SizeOfStackReserve;
le32_t pe32_SizeOfStackCommit;
le32_t pe32_SizeOfHeapReserve;
le32_t pe32_SizeOfHeapCommit;
le32_t pe32_LoaderFlags;
le32_t pe32_NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY pe32_DataDirectory[];
};
struct {
le64_t pe32plus_SizeOfStackReserve;
le64_t pe32plus_SizeOfStackCommit;
le64_t pe32plus_SizeOfHeapReserve;
le64_t pe32plus_SizeOfHeapCommit;
le32_t pe32plus_LoaderFlags;
le32_t pe32plus_NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY pe32plus_DataDirectory[];
};
};
} IMAGE_OPTIONAL_HEADER;
typedef struct _packed_ PeHeader {
le32_t signature;
IMAGE_FILE_HEADER pe;
IMAGE_OPTIONAL_HEADER optional;
} PeHeader;
typedef struct _packed_ _IMAGE_SECTION_HEADER {
uint8_t Name[8];
le32_t VirtualSize;
le32_t VirtualAddress;
le32_t SizeOfRawData;
le32_t PointerToRawData;
le32_t PointerToRelocations;
le32_t PointerToLinenumbers;
le16_t NumberOfRelocations;
le16_t NumberOfLinenumbers;
le32_t Characteristics;
} IMAGE_SECTION_HEADER;
#define IMAGE_SUBSYSTEM_EFI_APPLICATION 10
bool pe_header_is_64bit(const PeHeader *h);
#define PE_HEADER_OPTIONAL_FIELD(h, field) \
(pe_header_is_64bit(h) ? (h)->optional.pe32plus_##field : (h)->optional.pe32_##field)
#define PE_HEADER_OPTIONAL_FIELD_OFFSET(h, field) \
(pe_header_is_64bit(h) ? offsetof(PeHeader, optional.pe32plus_##field) : offsetof(PeHeader, optional.pe32_##field))
const IMAGE_DATA_DIRECTORY* pe_header_get_data_directory(const PeHeader *h, size_t i);
const IMAGE_SECTION_HEADER* pe_header_find_section(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections, const char *name);
const IMAGE_SECTION_HEADER* pe_section_table_find(const IMAGE_SECTION_HEADER *sections, size_t n_sections, const char *name);
int pe_load_headers(int fd, IMAGE_DOS_HEADER **ret_dos_header, PeHeader **ret_pe_header);
int pe_load_sections(int fd, const IMAGE_DOS_HEADER *dos_header, const PeHeader *pe_header, IMAGE_SECTION_HEADER **ret_sections);
int pe_read_section_data(int fd, const IMAGE_SECTION_HEADER *section, size_t max_size, void **ret, size_t *ret_size);
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);
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_is_native_fd(int fd);
#if HAVE_OPENSSL
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
/* This does not depend on OpenSSL, but is currently only used by sbsign which requires OpenSSL. */
int pe_checksum(int fd, uint32_t *ret);
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
#endif