• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 26610669849

28 May 2026 07:34PM UTC coverage: 72.973% (-0.02%) from 72.995%
26610669849

push

github

yuwata
units: drop Before=sockets.target from networkd resolve hook

Otherwise, it introduces cyclic dependencies:
```
systemd[1]: sockets.target: Found ordering cycle:
    systemd-networkd-resolve-hook.socket/start after network-pre.target/start after
    iptables.service/start after basic.target/start after sockets.target/start -
    after systemd-networkd-resolve-hook.socket
systemd[1]: sockets.target: Job systemd-networkd-resolve-hook.socket/start deleted
    to break ordering cycle starting with sockets.target/start
```

Follow-up for 37adb410a.
Fixes #42353.

337351 of 462293 relevant lines covered (72.97%)

1290265.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.73
/src/shared/pe-binary.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/stat.h>
4
#include <unistd.h>
5

6
#include "alloc-util.h"
7
#include "crypto-util.h"
8
#include "hexdecoct.h"
9
#include "log.h"
10
#include "pe-binary.h"
11
#include "sort-util.h"
12
#include "stat-util.h"
13
#include "string-table.h"
14
#include "string-util.h"
15

16
/* Cap on the (VirtualSize - SizeOfRawData) zero-padding the UKI hasher
17
 * will produce for a single section.  Any value beyond this is treated as
18
 * a malformed PE — bounds the hash work an attacker can drive (#42344). */
19
#define UKI_HASH_VIRTUAL_SIZE_PADDING_MAX (64U * 1024U * 1024U)
20

21
/* Note: none of these function change the file position of the provided fd, as they use pread() */
22

23
bool pe_header_is_64bit(const PeHeader *h) {
2,758✔
24
        assert(h);
2,758✔
25

26
        if (le16toh(h->optional.Magic) == UINT16_C(0x010B)) /* PE32 */
2,758✔
27
                return false;
28

29
        if (le16toh(h->optional.Magic) == UINT16_C(0x020B)) /* PE32+ */
2,089✔
30
                return true;
31

32
        assert_not_reached();
×
33
}
34

35
static size_t pe_header_size(const PeHeader *pe_header) {
3,428✔
36
        assert(pe_header);
3,428✔
37

38
        return offsetof(PeHeader, optional) + le16toh(pe_header->pe.SizeOfOptionalHeader);
3,428✔
39
}
40

41
const IMAGE_DATA_DIRECTORY* pe_header_get_data_directory(
23✔
42
                const PeHeader *h,
43
                size_t i) {
44

45
        assert(h);
23✔
46

47
        if (i >= le32toh(PE_HEADER_OPTIONAL_FIELD(h, NumberOfRvaAndSizes)))
23✔
48
                return NULL;
49

50
        return PE_HEADER_OPTIONAL_FIELD(h, DataDirectory) + i;
23✔
51
}
52

53
const IMAGE_SECTION_HEADER* pe_section_table_find(
1,207✔
54
                const IMAGE_SECTION_HEADER *sections,
55
                size_t n_sections,
56
                const char *name) {
57

58
        size_t n;
1,207✔
59

60
        assert(name);
1,207✔
61
        assert(sections || n_sections == 0);
1,207✔
62

63
        n = strlen(name);
1,207✔
64
        if (n > sizeof(sections[0].Name)) /* Too long? */
1,207✔
65
                return NULL;
66

67
        FOREACH_ARRAY(section, sections, n_sections)
6,970✔
68
                if (memcmp(section->Name, name, n) == 0 &&
6,831✔
69
                    (n == sizeof(sections[0].Name) || memeqzero(section->Name + n, sizeof(section->Name) - n)))
196✔
70
                        return section;
71

72
        return NULL;
73
}
74

75
const IMAGE_SECTION_HEADER* pe_header_find_section(
776✔
76
                const PeHeader *pe_header,
77
                const IMAGE_SECTION_HEADER *sections,
78
                const char *name) {
79

80
        assert(pe_header);
776✔
81

82
        return pe_section_table_find(sections, le16toh(pe_header->pe.NumberOfSections), name);
776✔
83
}
84

85
int pe_load_headers(
901✔
86
                int fd,
87
                IMAGE_DOS_HEADER **ret_dos_header,
88
                PeHeader **ret_pe_header) {
89

90
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
1,802✔
91
        _cleanup_free_ PeHeader *pe_header = NULL;
901✔
92
        ssize_t n;
901✔
93

94
        assert(fd >= 0);
901✔
95

96
        dos_header = new(IMAGE_DOS_HEADER, 1);
901✔
97
        if (!dos_header)
901✔
98
                return log_oom_debug();
×
99

100
        n = pread(fd,
901✔
101
                  dos_header,
102
                  sizeof(IMAGE_DOS_HEADER),
103
                  0);
104
        if (n < 0)
901✔
105
                return log_debug_errno(errno, "Failed to read DOS header: %m");
×
106
        if ((size_t) n != sizeof(IMAGE_DOS_HEADER))
901✔
107
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading MZ executable header.");
2✔
108

109
        if (le16toh(dos_header->e_magic) != UINT16_C(0x5A4D))
899✔
110
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks MZ executable header.");
2✔
111

112
        pe_header = new(PeHeader, 1);
897✔
113
        if (!pe_header)
897✔
114
                return log_oom_debug();
×
115

116
        n = pread(fd,
1,794✔
117
                  pe_header,
118
                  offsetof(PeHeader, optional),
119
                  le32toh(dos_header->e_lfanew));
897✔
120
        if (n < 0)
897✔
121
                return log_debug_errno(errno, "Failed to read PE executable header: %m");
×
122
        if ((size_t) n != offsetof(PeHeader, optional))
897✔
123
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable header.");
×
124

125
        if (le32toh(pe_header->signature) != UINT32_C(0x00004550))
897✔
126
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks PE executable header.");
×
127

128
        if (le16toh(pe_header->pe.SizeOfOptionalHeader) < sizeof_field(PeHeader, optional.Magic))
897✔
129
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size too short for magic.");
×
130

131
        PeHeader *pe_header_tmp = realloc(pe_header, MAX(sizeof(PeHeader), pe_header_size(pe_header)));
897✔
132
        if (!pe_header_tmp)
897✔
133
                return log_oom_debug();
×
134
        pe_header = pe_header_tmp;
897✔
135

136
        n = pread(fd,
1,794✔
137
                  &pe_header->optional,
897✔
138
                  le16toh(pe_header->pe.SizeOfOptionalHeader),
897✔
139
                  le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional));
897✔
140
        if (n < 0)
897✔
141
                return log_debug_errno(errno, "Failed to read PE executable optional header: %m");
×
142
        if ((size_t) n != le16toh(pe_header->pe.SizeOfOptionalHeader))
897✔
143
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading PE executable optional header.");
×
144

145
        if (!IN_SET(le16toh(pe_header->optional.Magic), UINT16_C(0x010B), UINT16_C(0x020B)))
897✔
146
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header magic invalid.");
×
147

148
        /* The optional header must be at least large enough to cover everything
149
         * up to and including NumberOfRvaAndSizes — otherwise the equality
150
         * check below would read uninitialised memory for that field. */
151
        if (pe_header_size(pe_header) < PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory))
1,097✔
152
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header too small.");
1✔
153

154
        if (pe_header_size(pe_header) !=
896✔
155
            PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory) +
896✔
156
            sizeof(IMAGE_DATA_DIRECTORY) * (uint64_t) le32toh(PE_HEADER_OPTIONAL_FIELD(pe_header, NumberOfRvaAndSizes)))
896✔
157
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header size mismatch.");
×
158

159
        if (ret_dos_header)
896✔
160
                *ret_dos_header = TAKE_PTR(dos_header);
744✔
161
        if (ret_pe_header)
896✔
162
                *ret_pe_header = TAKE_PTR(pe_header);
896✔
163

164
        return 0;
165
}
166

167
int pe_load_sections(
738✔
168
                int fd,
169
                const IMAGE_DOS_HEADER *dos_header,
170
                const PeHeader *pe_header,
171
                IMAGE_SECTION_HEADER **ret_sections) {
172

173
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
738✔
174
        struct stat st;
738✔
175
        size_t nos;
738✔
176
        ssize_t n;
738✔
177

178
        assert(fd >= 0);
738✔
179
        assert(dos_header);
738✔
180
        assert(pe_header);
738✔
181

182
        nos = le16toh(pe_header->pe.NumberOfSections);
738✔
183

184
        sections = new(IMAGE_SECTION_HEADER, nos);
738✔
185
        if (!sections)
738✔
186
                return log_oom_debug();
×
187

188
        n = pread(fd,
2,214✔
189
                  sections,
190
                  sizeof(IMAGE_SECTION_HEADER) * nos,
738✔
191
                  le32toh(dos_header->e_lfanew) + pe_header_size(pe_header));
738✔
192
        if (n < 0)
738✔
193
                return log_debug_errno(errno, "Failed to read section table: %m");
×
194
        if ((size_t) n != sizeof(IMAGE_SECTION_HEADER) * nos)
738✔
195
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading section table.");
×
196

197
        /* The section's raw bytes must fit inside the file.  This is the
198
         * fundamental invariant the parser relies on later (pe_hash, uki_hash,
199
         * pe_read_section_data, ...); reject obvious malformations early so
200
         * downstream loops don't get driven by attacker-controlled sizes. */
201
        if (fstat(fd, &st) < 0)
738✔
202
                return log_debug_errno(errno, "Failed to stat PE file: %m");
×
203

204
        FOREACH_ARRAY(section, sections, nos) {
6,469✔
205
                uint64_t prd = le32toh(section->PointerToRawData), srd = le32toh(section->SizeOfRawData), end;
5,735✔
206

207
                /* SizeOfRawData == 0 is legitimate (BSS-like, uninitialised) —
208
                 * PointerToRawData is then meaningless and not used as an offset. */
209
                if (srd == 0)
5,735✔
210
                        continue;
11✔
211

212
                if (!ADD_SAFE(&end, prd, srd) || end > (uint64_t) st.st_size)
5,724✔
213
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
4✔
214
                                               "PE section raw data exceeds file size.");
215
        }
216

217
        if (ret_sections)
734✔
218
                *ret_sections = TAKE_PTR(sections);
734✔
219

220
        return 0;
221
}
222

223
int pe_read_section_data(
736✔
224
                int fd,
225
                const IMAGE_SECTION_HEADER *section,
226
                size_t max_size,
227
                void **ret,
228
                size_t *ret_size) {
229

230
        assert(fd >= 0);
736✔
231
        assert(section);
736✔
232

233
        size_t n = le32toh(section->VirtualSize);
736✔
234
        if (n > MIN(max_size, (size_t) SSIZE_MAX))
736✔
235
                return -EBADMSG;
736✔
236

237
        _cleanup_free_ void *data = malloc(n+1);
736✔
238
        if (!data)
736✔
239
                return -ENOMEM;
240

241
        ssize_t ss = pread(fd, data, n, le32toh(section->PointerToRawData));
736✔
242
        if (ss < 0)
736✔
243
                return -errno;
×
244
        if ((size_t) ss != n)
736✔
245
                return -EBADMSG;
246

247
        if (ret_size)
736✔
248
                *ret_size = n;
×
249
        else {
250
                /* Check that there are no embedded NUL bytes if the caller doesn't want to know the size
251
                 * (i.e. treats the blob as a string) */
252
                const char *nul;
736✔
253

254
                nul = memchr(data, 0, n);
736✔
255
                if (nul && !memeqzero(nul, n - (nul - (const char*) data))) /* If there's a NUL it must only be NULs from there on */
736✔
256
                        return -EBADMSG;
257
        }
258
        if (ret) {
736✔
259
                ((uint8_t*) data)[n] = 0; /* NUL terminate, no matter what */
736✔
260
                *ret = TAKE_PTR(data);
736✔
261
        }
262

263
        return 0;
264
}
265

266
int pe_read_section_data_by_name(
600✔
267
                int fd,
268
                const PeHeader *pe_header,
269
                const IMAGE_SECTION_HEADER *sections,
270
                const char *name,
271
                size_t max_size,
272
                void **ret,
273
                size_t *ret_size) {
274

275
        const IMAGE_SECTION_HEADER *section;
600✔
276

277
        assert(fd >= 0);
600✔
278
        assert(pe_header);
600✔
279
        assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
600✔
280
        assert(name);
600✔
281

282
        section = pe_header_find_section(pe_header, sections, name);
600✔
283
        if (!section)
600✔
284
                return -ENXIO;
285

286
        return pe_read_section_data(fd, section, max_size, ret, ret_size);
600✔
287
}
288

289
bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) {
74✔
290
        assert(pe_header);
74✔
291
        assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
74✔
292

293
        if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
74✔
294
                return false;
295

296
        /* Note that the UKI spec only requires .linux, but we are stricter here, and require .osrel too,
297
         * since for sd-boot it just doesn't make sense to not have that. */
298
        return
74✔
299
                pe_header_find_section(pe_header, sections, ".osrel") &&
148✔
300
                pe_header_find_section(pe_header, sections, ".linux");
74✔
301
}
302

303
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) {
12✔
304
        assert(pe_header);
12✔
305
        assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
12✔
306

307
        if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION)
12✔
308
                return false;
309

310
        /* Add-ons do not have a Linux kernel, but do have one of .cmdline, .dtb, .initrd or .ucode (currently) */
311
        return !pe_header_find_section(pe_header, sections, ".linux") &&
24✔
312
                (pe_header_find_section(pe_header, sections, ".cmdline") ||
12✔
313
                 pe_header_find_section(pe_header, sections, ".dtb") ||
×
314
                 pe_header_find_section(pe_header, sections, ".initrd") ||
×
315
                 pe_header_find_section(pe_header, sections, ".ucode"));
×
316
}
317

318
bool pe_is_native(const PeHeader *pe_header) {
227✔
319
        assert(pe_header);
227✔
320

321
#ifdef _IMAGE_FILE_MACHINE_NATIVE
322
        return le16toh(pe_header->pe.Machine) == _IMAGE_FILE_MACHINE_NATIVE;
227✔
323
#else
324
        return false;
325
#endif
326
}
327

328
int pe_is_native_fd(int fd) {
152✔
329
        _cleanup_free_ PeHeader *pe_header = NULL;
152✔
330
        int r;
152✔
331

332
        r = pe_load_headers(fd, /* ret_dos_header= */ NULL, &pe_header);
152✔
333
        if (r < 0)
152✔
334
                return r;
335

336
        return pe_is_native(pe_header);
152✔
337
}
338

339
/* Implements:
340
 *
341
 * https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
342
 * → Section "Calculating the PE Image Hash"
343
 */
344

345
#if HAVE_OPENSSL
346
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
224✔
347
        uint8_t buffer[64*1024];
224✔
348

349
        log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
224✔
350

351
        while (size > 0) {
483✔
352
                size_t m = MIN(size, sizeof(buffer));
259✔
353
                ssize_t n;
259✔
354

355
                n = pread(fd, buffer, m, offset);
259✔
356
                if (n < 0)
259✔
357
                        return log_debug_errno(errno, "Failed to read file for hashing: %m");
×
358
                if ((size_t) n != m)
259✔
359
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
×
360

361
                if (sym_EVP_DigestUpdate(md_ctx, buffer, m) != 1)
259✔
362
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
×
363

364
                offset += m;
259✔
365
                size -= m;
259✔
366
        }
367

368
        return 0;
369
}
370

371
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
180✔
372
        return CMP(le32toh(ASSERT_PTR(a)->PointerToRawData), le32toh(ASSERT_PTR(b)->PointerToRawData));
180✔
373
}
374

375
int pe_hash(int fd,
24✔
376
            const EVP_MD *md,
377
            void **ret_hash,
378
            size_t *ret_hash_size) {
379

380
        _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
×
381
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
382
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
383
        _cleanup_free_ PeHeader *pe_header = NULL;
24✔
384
        const IMAGE_DATA_DIRECTORY *certificate_table;
24✔
385
        struct stat st;
24✔
386
        uint64_t p, q;
24✔
387
        int r;
24✔
388

389
        assert(fd >= 0);
24✔
390
        assert(md);
24✔
391
        assert(ret_hash_size);
24✔
392
        assert(ret_hash);
24✔
393

394
        r = dlopen_libcrypto(LOG_DEBUG);
24✔
395
        if (r < 0)
24✔
396
                return r;
397

398
        if (fstat(fd, &st) < 0)
24✔
399
                return log_debug_errno(errno, "Failed to stat file: %m");
×
400
        r = stat_verify_regular(&st);
24✔
401
        if (r < 0)
24✔
402
                return log_debug_errno(r, "Not a regular file: %m");
2✔
403

404
        r = pe_load_headers(fd, &dos_header, &pe_header);
22✔
405
        if (r < 0)
22✔
406
                return r;
407

408
        r = pe_load_sections(fd, dos_header, pe_header, &sections);
20✔
409
        if (r < 0)
20✔
410
                return r;
411

412
        certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
20✔
413
        if (!certificate_table)
20✔
414
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
×
415

416
        mdctx = sym_EVP_MD_CTX_new();
20✔
417
        if (!mdctx)
20✔
418
                return log_oom_debug();
×
419

420
        if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1)
20✔
421
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
×
422

423
        /* Everything from beginning of file to CheckSum field in PE header */
424
        p = (uint64_t) le32toh(dos_header->e_lfanew) +
20✔
425
                offsetof(PeHeader, optional.CheckSum);
426
        r = hash_file(fd, mdctx, 0, p);
20✔
427
        if (r < 0)
20✔
428
                return r;
429
        p += sizeof(le32_t);
20✔
430

431
        /* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
432
        q = (uint64_t) le32toh(dos_header->e_lfanew) +
40✔
433
                PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
20✔
434
        r = hash_file(fd, mdctx, p, q - p);
20✔
435
        if (r < 0)
20✔
436
                return r;
437
        q += sizeof(IMAGE_DATA_DIRECTORY);
20✔
438

439
        /* The rest of the header + the section table */
440
        p = le32toh(pe_header->optional.SizeOfHeaders);
20✔
441
        if (p < q)
20✔
442
                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
×
443
        r = hash_file(fd, mdctx, q, p - q);
20✔
444
        if (r < 0)
20✔
445
                return r;
446

447
        /* Sort by location in file */
448
        typesafe_qsort(sections, le16toh(pe_header->pe.NumberOfSections), section_offset_cmp);
20✔
449

450
        FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
160✔
451
                r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), le32toh(section->SizeOfRawData));
140✔
452
                if (r < 0)
140✔
453
                        return r;
454

455
                p += le32toh(section->SizeOfRawData);
140✔
456
        }
457

458
        if ((uint64_t) st.st_size > p) {
20✔
459

460
                if ((uint64_t) st.st_size - p < le32toh(certificate_table->Size))
1✔
461
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "No space for certificate table, refusing.");
×
462

463
                r = hash_file(fd, mdctx, p, (uint64_t) st.st_size - p - le32toh(certificate_table->Size));
1✔
464
                if (r < 0)
1✔
465
                        return r;
466

467
                /* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
468
                if (st.st_size % 8 != 0 && sym_EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
1✔
469
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
×
470
        }
471

472
        int hsz = sym_EVP_MD_CTX_get_size(mdctx);
20✔
473
        if (hsz < 0)
20✔
474
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
×
475

476
        unsigned hash_size = (unsigned) hsz;
20✔
477
        _cleanup_free_ void *hash = malloc(hsz);
20✔
478
        if (!hash)
20✔
479
                return log_oom_debug();
×
480

481
        if (sym_EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
20✔
482
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
×
483

484
        assert(hash_size == (unsigned) hsz);
20✔
485

486
        *ret_hash = TAKE_PTR(hash);
20✔
487
        *ret_hash_size = hash_size;
20✔
488

489
        return 0;
20✔
490
}
491

492
int pe_checksum(int fd, uint32_t *ret) {
3✔
493
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
6✔
494
        _cleanup_free_ PeHeader *pe_header = NULL;
3✔
495
        struct stat st;
3✔
496
        int r;
3✔
497

498
        assert(fd >= 0);
3✔
499
        assert(ret);
3✔
500

501
        if (fstat(fd, &st) < 0)
3✔
502
                return log_debug_errno(errno, "Failed to stat file: %m");
×
503

504
        r = pe_load_headers(fd, &dos_header, &pe_header);
3✔
505
        if (r < 0)
3✔
506
                return r;
507

508
        uint32_t checksum = 0, checksum_offset = le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum);
3✔
509
        size_t off = 0;
3✔
510
        for (;;) {
27✔
511
                uint16_t buf[32*1024];
15✔
512

513
                ssize_t n = pread(fd, buf, sizeof(buf), off);
15✔
514
                if (n == 0)
15✔
515
                        break;
516
                if (n < 0)
12✔
517
                        return log_debug_errno(errno, "Failed to read from PE file: %m");
×
518
                if (n % sizeof(uint16_t) != 0)
12✔
519
                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file");
×
520

521
                for (size_t i = 0; i < (size_t) n / 2; i++) {
303,804✔
522
                        size_t pos = off + i * sizeof(uint16_t);
303,792✔
523
                        if (pos >= checksum_offset && pos < checksum_offset + sizeof(pe_header->optional.CheckSum))
303,792✔
524
                                continue;
6✔
525

526
                        uint16_t val = le16toh(buf[i]);
303,786✔
527

528
                        checksum += val;
303,786✔
529
                        checksum = (checksum >> 16) + (checksum & 0xffff);
303,786✔
530
                }
531

532
                off += n;
12✔
533
        }
534

535
        checksum = (checksum >> 16) + (checksum & 0xffff);
3✔
536
        checksum += off;
3✔
537

538
        *ret = checksum;
3✔
539
        return 0;
3✔
540
}
541

542
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
543

544
static void section_hash_array_done(SectionHashArray *array) {
15✔
545
        assert(array);
15✔
546

547
        for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
240✔
548
                free((*array)[i]);
225✔
549
}
15✔
550

551
int uki_hash(int fd,
15✔
552
             const EVP_MD *md,
553
             void* ret_hashes[static _UNIFIED_SECTION_MAX],
554
             size_t *ret_hash_size) {
555

556
        _cleanup_(section_hash_array_done) SectionHashArray hashes = {};
×
557
        _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
×
558
        _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
×
559
        _cleanup_free_ PeHeader *pe_header = NULL;
15✔
560
        int r;
15✔
561

562
        assert(fd >= 0);
15✔
563
        assert(ret_hashes);
15✔
564
        assert(ret_hash_size);
15✔
565

566
        r = dlopen_libcrypto(LOG_DEBUG);
15✔
567
        if (r < 0)
15✔
568
                return r;
569

570
        r = pe_load_headers(fd, &dos_header, &pe_header);
15✔
571
        if (r < 0)
15✔
572
                return r;
573

574
        r = pe_load_sections(fd, dos_header, pe_header, &sections);
15✔
575
        if (r < 0)
15✔
576
                return r;
577

578
        int hsz = sym_EVP_MD_get_size(md);
15✔
579
        if (hsz < 0)
15✔
580
                return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
×
581

582
        FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
74✔
583
                _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
×
584
                _cleanup_free_ char *n = NULL;
63✔
585
                ssize_t i;
63✔
586

587
                n = memdup_suffix0(section->Name, sizeof(section->Name));
63✔
588
                if (!n)
63✔
589
                        return log_oom_debug();
×
590

591
                i = string_table_lookup_from_string(unified_sections, _UNIFIED_SECTION_MAX, n);
63✔
592
                if (i < 0)
63✔
593
                        continue;
40✔
594

595
                if (hashes[i])
23✔
596
                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
×
597

598
                mdctx = sym_EVP_MD_CTX_new();
23✔
599
                if (!mdctx)
23✔
600
                        return log_oom_debug();
×
601

602
                if (sym_EVP_DigestInit_ex(mdctx, md, NULL) != 1)
23✔
603
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
×
604

605
                r = hash_file(fd, mdctx, le32toh(section->PointerToRawData), MIN(le32toh(section->VirtualSize), le32toh(section->SizeOfRawData)));
23✔
606
                if (r < 0)
23✔
607
                        return r;
608

609
                if (le32toh(section->SizeOfRawData) < le32toh(section->VirtualSize)) {
23✔
610
                        uint8_t zeroes[1024] = {};
5✔
611
                        size_t remaining = le32toh(section->VirtualSize) - le32toh(section->SizeOfRawData);
5✔
612

613
                        /* Bound the zero-padding hash work.  An attacker can otherwise
614
                         * set VirtualSize close to UINT32_MAX with SizeOfRawData = 0,
615
                         * driving ~4 GiB of SHA-256 work per section on a tiny file
616
                         * (issue #42344 — wedges the parser for >10 s on 382 B). */
617
                        if (remaining > UKI_HASH_VIRTUAL_SIZE_PADDING_MAX)
5✔
618
                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
4✔
619
                                                       "Section VirtualSize exceeds SizeOfRawData by %zu bytes (cap %zu); refusing to hash.",
620
                                                       remaining,
621
                                                       (size_t) UKI_HASH_VIRTUAL_SIZE_PADDING_MAX);
622

623
                        while (remaining > 0) {
2✔
624
                                size_t sz = MIN(sizeof(zeroes), remaining);
1✔
625

626
                                if (sym_EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
1✔
627
                                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
×
628

629
                                remaining -= sz;
1✔
630
                        }
631
                }
632

633
                hashes[i] = malloc(hsz);
19✔
634
                if (!hashes[i])
19✔
635
                        return log_oom_debug();
×
636

637
                unsigned hash_size = (unsigned) hsz;
19✔
638
                if (sym_EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
19✔
639
                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
×
640

641
                assert(hash_size == (unsigned) hsz);
19✔
642

643
                if (DEBUG_LOGGING) {
19✔
644
                        _cleanup_free_ char *hs = NULL;
19✔
645

646
                        hs = hexmem(hashes[i], hsz);
19✔
647
                        log_debug("Section %s with %s is %s.", n, sym_EVP_MD_get0_name(md), strna(hs));
19✔
648
                }
649
        }
650

651
        memcpy(ret_hashes, hashes, sizeof(hashes));
11✔
652
        zero(hashes);
11✔
653
        *ret_hash_size = (unsigned) hsz;
11✔
654

655
        return 0;
11✔
656
}
657
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc