Skip to content

Commit d915c60

Browse files
authored
Merge pull request #788 from danielinux/fenrir-fixes-2026-06-05
Fenrir fixes 2026 06 05
2 parents e27e81f + 3a8404b commit d915c60

16 files changed

Lines changed: 648 additions & 21 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ tools/unit-tests/unit-max-space
188188
tools/unit-tests/unit-sdhci-disk-unaligned
189189
tools/unit-tests/unit-fwtpm-stub
190190
tools/unit-tests/unit-gzip
191+
tools/unit-tests/unit-linux-loader-e820
192+
tools/unit-tests/unit-linux-loader-syssize
191193

192194

193195
# Elf preprocessing tools

src/arm_tee_psa_ipc.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -766,16 +766,23 @@ int32_t arm_tee_psa_call(psa_handle_t handle, int32_t type,
766766
const void *data;
767767
const psa_storage_create_flags_t *flags;
768768
struct wolfboot_ps_entry *entry;
769+
size_t data_len;
769770
if (in_vec == NULL || in_len < 3) {
770771
return PSA_ERROR_INVALID_ARGUMENT;
771772
}
772773
uid = (const psa_storage_uid_t *)in_vec[0].base;
773774
data = in_vec[1].base;
774775
flags = (const psa_storage_create_flags_t *)in_vec[2].base;
776+
/* Snapshot the NS-supplied length once into a Secure-stack local.
777+
* in_vec lives in NS memory and may be mutated concurrently (a
778+
* preempting NS interrupt or NS-accessible DMA), so re-reading
779+
* in_vec[1].len after the bounds check would allow a TOCTOU
780+
* double-fetch to grow the copy past WOLFBOOT_PS_MAX_DATA. */
781+
data_len = in_vec[1].len;
775782
if (uid == NULL || flags == NULL) {
776783
return PSA_ERROR_INVALID_ARGUMENT;
777784
}
778-
if (in_vec[1].len > WOLFBOOT_PS_MAX_DATA) {
785+
if (data_len > WOLFBOOT_PS_MAX_DATA) {
779786
return PSA_ERROR_INSUFFICIENT_STORAGE;
780787
}
781788
entry = wolfboot_ps_find(*uid);
@@ -787,13 +794,13 @@ int32_t arm_tee_psa_call(psa_handle_t handle, int32_t type,
787794
} else if ((entry->flags & PSA_STORAGE_FLAG_WRITE_ONCE) != 0U) {
788795
return PSA_ERROR_NOT_PERMITTED;
789796
}
790-
if (in_vec[1].len > 0 && data == NULL) {
797+
if (data_len > 0 && data == NULL) {
791798
return PSA_ERROR_INVALID_ARGUMENT;
792799
}
793-
if (in_vec[1].len > 0) {
794-
XMEMCPY(entry->data, data, in_vec[1].len);
800+
if (data_len > 0) {
801+
XMEMCPY(entry->data, data, data_len);
795802
}
796-
entry->size = in_vec[1].len;
803+
entry->size = data_len;
797804
entry->flags = *flags;
798805
return PSA_SUCCESS;
799806
}

src/fdt.c

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ static uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
125125
{
126126
const uint32_t *tagp, *lenp;
127127
uint32_t tag;
128+
uint32_t proplen;
129+
uint64_t next_off;
128130
int offset = startoffset;
129131
const char *p;
130132

@@ -152,13 +154,30 @@ static uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset)
152154
if (!lenp) {
153155
return FDT_END; /* premature end */
154156
}
155-
/* skip-name offset, length and value */
156-
offset += sizeof(struct fdt_property) - FDT_TAGSIZE
157-
+ fdt32_to_cpu(*lenp);
158-
if (fdt_version(fdt) < 0x10 && fdt32_to_cpu(*lenp) >= 8 &&
159-
((offset - fdt32_to_cpu(*lenp)) % 8) != 0) {
160-
offset += 4;
157+
proplen = fdt32_to_cpu(*lenp);
158+
/* A property value can never be larger than the blob itself.
159+
* Reject an oversized length up front: otherwise the unsigned
160+
* cursor arithmetic below wraps (e.g. len=0xFFFFFFFF advances
161+
* offset by only 7 bytes), the malformed node slips past the
162+
* fdt_offset_ptr() bounds check, and the bogus length propagates
163+
* to callers as a negative int (a ~4GB memcpy size). */
164+
if (proplen > (uint32_t)fdt_totalsize(fdt)) {
165+
return FDT_END; /* bad structure */
161166
}
167+
/* skip-name offset, length and value. Accumulate the next cursor in a
168+
* 64-bit unsigned so neither the addition nor the narrowing back to the
169+
* signed int offset can overflow, then re-validate it against the blob
170+
* size before continuing. */
171+
next_off = (uint64_t)offset
172+
+ (sizeof(struct fdt_property) - FDT_TAGSIZE) + proplen;
173+
if (fdt_version(fdt) < 0x10 && proplen >= 8 &&
174+
((next_off - proplen) % 8) != 0) {
175+
next_off += 4;
176+
}
177+
if (next_off > (uint64_t)fdt_totalsize(fdt)) {
178+
return FDT_END; /* bad structure */
179+
}
180+
offset = (int)next_off;
162181
break;
163182

164183
case FDT_END:

src/fwtpm_callable.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@
1717
#include "wolftpm/fwtpm/fwtpm_nv.h"
1818
#include "wolftpm/tpm2_types.h"
1919

20+
/* Validate that a buffer supplied by the non-secure caller lives in the
21+
* non-secure world before the secure side dereferences it. Without this check a
22+
* non-secure caller could pass a pointer into Secure SRAM and turn this veneer
23+
* into a confused-deputy primitive (forging TPM responses into Secure memory or
24+
* leaking Secure memory through the command path). The relevant property is the
25+
* Secure/Non-secure attribution (SAU/IDAU): only CMSE_NONSECURE is checked, not
26+
* the MPU read/write permission bits. The MPU bits depend on an enabled NS MPU
27+
* (they read back as 0 when NO_MPU is set) and do not constrain Secure accesses
28+
* to NS memory anyway, so requiring them would wrongly reject valid NS buffers.
29+
* Outside of a CMSE secure build there is no security boundary, so the checks
30+
* collapse to a simple non-NULL pass-through. */
31+
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
32+
#include <arm_cmse.h>
33+
#define WCS_FWTPM_NS_RW(p, sz) \
34+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
35+
#define WCS_FWTPM_NS_R(p, sz) \
36+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
37+
#else
38+
#define WCS_FWTPM_NS_RW(p, sz) ((void*)(p))
39+
#define WCS_FWTPM_NS_R(p, sz) ((void*)(p))
40+
#endif
41+
2042
static FWTPM_CTX fwtpm_ctx;
2143
static int fwtpm_ready;
2244

@@ -129,11 +151,18 @@ int CSME_NSE_API wcs_fwtpm_transmit(const uint8_t *cmd, uint32_t cmdSz,
129151
cmdSz > WCS_FWTPM_MAX_COMMAND_SIZE) {
130152
return BAD_FUNC_ARG;
131153
}
154+
if (WCS_FWTPM_NS_R(cmd, cmdSz) == NULL ||
155+
WCS_FWTPM_NS_RW(rspSz, sizeof(*rspSz)) == NULL) {
156+
return BAD_FUNC_ARG;
157+
}
132158

133159
rspCapacity = *rspSz;
134160
if (rspCapacity == 0U || rspCapacity > WCS_FWTPM_MAX_COMMAND_SIZE) {
135161
return BAD_FUNC_ARG;
136162
}
163+
if (WCS_FWTPM_NS_RW(rsp, rspCapacity) == NULL) {
164+
return BAD_FUNC_ARG;
165+
}
137166

138167
rspLen = (int)rspCapacity;
139168
rc = FWTPM_ProcessCommand(&fwtpm_ctx, cmd, (int)cmdSz, rsp, &rspLen, 0);

src/libwolfboot.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,6 +2419,25 @@ int wolfBoot_ram_decrypt(uint8_t *src, uint8_t *dst)
24192419
#endif /* EXT_ENCRYPTED */
24202420

24212421
#if defined(__WOLFBOOT) && defined(TZEN)
2422+
2423+
/* The wolfBoot_nsc_* functions are cmse_nonsecure_entry veneers callable by any
2424+
* non-secure code. Pointer arguments arrive directly from the NS caller and are
2425+
* used as the write target of the secure callee. Validate the whole range lives
2426+
* in the non-secure world before writing, otherwise an NS caller could aim the
2427+
* write at Secure SRAM (a confused-deputy write primitive). The check verifies
2428+
* only the Secure/Non-secure attribution (CMSE_NONSECURE); the MPU read/write
2429+
* permission bits are deliberately not required, as they read back as 0 when the
2430+
* NS MPU is disabled (NO_MPU) and do not constrain Secure accesses to NS memory
2431+
* anyway. Outside a CMSE secure build there is no security boundary, so the check
2432+
* collapses to a non-NULL pass-through. Same fix pattern as F-4416/F-4417/F-4644. */
2433+
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
2434+
#include <arm_cmse.h>
2435+
#define WOLFBOOT_NSC_NS_RW(p, sz) \
2436+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
2437+
#else
2438+
#define WOLFBOOT_NSC_NS_RW(p, sz) ((void*)(p))
2439+
#endif
2440+
24222441
CSME_NSE_API
24232442
void wolfBoot_nsc_success(void)
24242443
{
@@ -2440,6 +2459,8 @@ uint32_t wolfBoot_nsc_get_image_version(uint8_t part)
24402459
CSME_NSE_API
24412460
int wolfBoot_nsc_get_partition_state(uint8_t part, uint8_t *st)
24422461
{
2462+
if (WOLFBOOT_NSC_NS_RW(st, sizeof(uint8_t)) == NULL)
2463+
return -1;
24432464
return wolfBoot_get_partition_state(part, st);
24442465
}
24452466

src/pci.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,15 @@ static int pci_program_bar(uint8_t bus, uint8_t dev, uint8_t fun,
481481
(is_mmio ? "mm" : "io"), bus, dev, fun, bar_idx,
482482
(uint32_t)bar_value, *is_64bit ? "64bit" : "",
483483
is_prefetch ? "prefetch" : "");
484+
/* A BAR with no writable address bits (bar_align == 0) is unimplemented
485+
* or malformed: (~0) + 1 would wrap to length 0, leaving the allocator
486+
* cursor unchanged and colliding the next BAR onto the same address.
487+
* Treat it as unimplemented and skip it. Legitimate MMIO BARs always
488+
* have at least one writable address bit; IO BARs force the high bits
489+
* above, so bar_align is never 0 for them. */
490+
if (bar_align == 0)
491+
goto restore_bar;
492+
484493
align = length = (~bar_align) + 1;
485494
/* force pci address to be on page boundary */
486495
if (align < 0x1000)

src/pkcs11_store.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ static void bitmap_put(uint32_t pos, int val)
127127
uint32_t bit = pos % 8;
128128
uint8_t *bitmap = cached_sector + sizeof(uint32_t);
129129

130+
/* Reject out-of-range positions (e.g. a power-fault-corrupted hdr->pos
131+
* left as erased flash) to avoid an out-of-bounds write past the
132+
* bitmap, which lives within cached_sector. */
133+
if (pos >= KEYVAULT_MAX_ITEMS)
134+
return;
135+
130136
if (val != 0) {
131137
bitmap[octet] |= (1 << bit);
132138
} else {

src/tpm.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,14 +1230,41 @@ static int wolfRNG_GetSeedCB(OS_Seed* os, uint8_t* seed, uint32_t sz)
12301230

12311231

12321232
/* API's that are callable from non-secure code */
1233+
1234+
/* Validate that a buffer supplied by the non-secure caller lives in the
1235+
* non-secure world before the secure side dereferences it. Without this check a
1236+
* non-secure caller could pass a pointer into Secure SRAM and turn these veneers
1237+
* into a confused-deputy write primitive against Secure memory. The check
1238+
* verifies only the Secure/Non-secure attribution (CMSE_NONSECURE); the MPU
1239+
* read/write permission bits are deliberately not required, as they read back as
1240+
* 0 when the NS MPU is disabled (NO_MPU) and do not constrain Secure accesses to
1241+
* NS memory anyway. Outside of a CMSE secure build there is no security
1242+
* boundary, so the checks collapse to a simple non-NULL pass-through. */
1243+
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
1244+
#include <arm_cmse.h>
1245+
#define WOLFBOOT_TPM_NS_RW(p, sz) \
1246+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
1247+
#define WOLFBOOT_TPM_NS_R(p, sz) \
1248+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
1249+
#else
1250+
#define WOLFBOOT_TPM_NS_RW(p, sz) ((void*)(p))
1251+
#define WOLFBOOT_TPM_NS_R(p, sz) ((void*)(p))
1252+
#endif
1253+
12331254
int CSME_NSE_API wolfBoot_tpm2_caps(WOLFTPM2_CAPS* caps)
12341255
{
1256+
if (WOLFBOOT_TPM_NS_RW(caps, sizeof(*caps)) == NULL) {
1257+
return BAD_FUNC_ARG;
1258+
}
12351259
memset(caps, 0, sizeof(*caps));
12361260
return wolfTPM2_GetCapabilities(&wolftpm_dev, caps);
12371261
}
12381262

12391263
int CSME_NSE_API wolfBoot_tpm2_get_handles(TPM_HANDLE handle, TPML_HANDLE* handles)
12401264
{
1265+
if (WOLFBOOT_TPM_NS_RW(handles, sizeof(*handles)) == NULL) {
1266+
return BAD_FUNC_ARG;
1267+
}
12411268
memset(handles, 0, sizeof(*handles));
12421269
return wolfTPM2_GetHandles(handle, handles);
12431270
}
@@ -1249,6 +1276,9 @@ const char* CSME_NSE_API wolfBoot_tpm2_get_alg_name(TPM_ALG_ID alg,
12491276
if (name == NULL || name_sz <= 0) {
12501277
return NULL;
12511278
}
1279+
if (WOLFBOOT_TPM_NS_RW(name, name_sz) == NULL) {
1280+
return NULL;
1281+
}
12521282
s_name = TPM2_GetAlgName(alg);
12531283
if (s_name != NULL && name != NULL && name_sz > 0) {
12541284
strncpy(name, s_name, name_sz - 1);
@@ -1267,6 +1297,9 @@ const char* CSME_NSE_API wolfBoot_tpm2_get_rc_string(int rc, char* error, int er
12671297
if (error == NULL || error_sz <= 0) {
12681298
return NULL;
12691299
}
1300+
if (WOLFBOOT_TPM_NS_RW(error, error_sz) == NULL) {
1301+
return NULL;
1302+
}
12701303
s_error = TPM2_GetRCString(rc);
12711304
if (s_error != NULL && error != NULL && error_sz > 0) {
12721305
strncpy(error, s_error, error_sz - 1);
@@ -1281,17 +1314,32 @@ const char* CSME_NSE_API wolfBoot_tpm2_get_rc_string(int rc, char* error, int er
12811314

12821315
int CSME_NSE_API wolfBoot_tpm2_get_capability(GetCapability_In* in, GetCapability_Out* out)
12831316
{
1317+
if (WOLFBOOT_TPM_NS_R(in, sizeof(*in)) == NULL ||
1318+
WOLFBOOT_TPM_NS_RW(out, sizeof(*out)) == NULL) {
1319+
return BAD_FUNC_ARG;
1320+
}
12841321
return (int)TPM2_GetCapability(in, out);
12851322
}
12861323

12871324
int CSME_NSE_API wolfBoot_tpm2_read_pcr(uint8_t pcrIndex, uint8_t* digest, int* digestSz)
12881325
{
1326+
if (WOLFBOOT_TPM_NS_RW(digest,
1327+
TPM2_GetHashDigestSize(WOLFBOOT_TPM_PCR_ALG)) == NULL ||
1328+
WOLFBOOT_TPM_NS_RW(digestSz, sizeof(*digestSz)) == NULL) {
1329+
return BAD_FUNC_ARG;
1330+
}
12891331
return wolfTPM2_ReadPCR(&wolftpm_dev, pcrIndex, WOLFBOOT_TPM_PCR_ALG,
12901332
digest, digestSz);
12911333
}
12921334

12931335
int CSME_NSE_API wolfBoot_tpm2_read_cert(uint32_t handle, uint8_t* cert, uint32_t* certSz)
12941336
{
1337+
if (WOLFBOOT_TPM_NS_RW(certSz, sizeof(*certSz)) == NULL) {
1338+
return BAD_FUNC_ARG;
1339+
}
1340+
if (WOLFBOOT_TPM_NS_RW(cert, *certSz) == NULL) {
1341+
return BAD_FUNC_ARG;
1342+
}
12951343
wolfTPM2_SetAuthPassword(&wolftpm_dev, 0, NULL);
12961344
return wolfTPM2_NVReadCert(&wolftpm_dev, handle, cert, certSz);
12971345
}
@@ -1304,6 +1352,13 @@ int CSME_NSE_API wolfBoot_tpm2_get_aik(WOLFTPM2_KEY* aik,
13041352
if (aik == NULL) {
13051353
return BAD_FUNC_ARG;
13061354
}
1355+
if (WOLFBOOT_TPM_NS_RW(aik, sizeof(*aik)) == NULL) {
1356+
return BAD_FUNC_ARG;
1357+
}
1358+
if (masterPassword != NULL &&
1359+
WOLFBOOT_TPM_NS_R(masterPassword, masterPasswordSz) == NULL) {
1360+
return BAD_FUNC_ARG;
1361+
}
13071362

13081363
/* Load existing AIK and set auth */
13091364
rc = wolfTPM2_ReadPublicKey(&wolftpm_dev, aik, TPM2_IAK_KEY_HANDLE);
@@ -1330,6 +1385,10 @@ int CSME_NSE_API wolfBoot_tpm2_get_timestamp(WOLFTPM2_KEY* aik, GetTime_Out* get
13301385
if (aik == NULL || getTime == NULL) {
13311386
return BAD_FUNC_ARG;
13321387
}
1388+
if (WOLFBOOT_TPM_NS_RW(aik, sizeof(*aik)) == NULL ||
1389+
WOLFBOOT_TPM_NS_RW(getTime, sizeof(*getTime)) == NULL) {
1390+
return BAD_FUNC_ARG;
1391+
}
13331392

13341393
memset(getTime, 0, sizeof(*getTime));
13351394
memset(&eh_handle, 0, sizeof(eh_handle));
@@ -1358,6 +1417,10 @@ int CSME_NSE_API wolfBoot_tpm2_get_timestamp(WOLFTPM2_KEY* aik, GetTime_Out* get
13581417

13591418
int CSME_NSE_API wolfBoot_tpm2_parse_attest(const TPM2B_ATTEST* in, TPMS_ATTEST* out)
13601419
{
1420+
if (WOLFBOOT_TPM_NS_R(in, sizeof(*in)) == NULL ||
1421+
WOLFBOOT_TPM_NS_RW(out, sizeof(*out)) == NULL) {
1422+
return BAD_FUNC_ARG;
1423+
}
13611424
return TPM2_ParseAttest(in, out);
13621425
}
13631426

@@ -1372,6 +1435,11 @@ int CSME_NSE_API wolfBoot_tpm2_quote(WOLFTPM2_KEY* aik,
13721435
quoteResult == NULL) {
13731436
return BAD_FUNC_ARG;
13741437
}
1438+
if (WOLFBOOT_TPM_NS_RW(aik, sizeof(*aik)) == NULL ||
1439+
WOLFBOOT_TPM_NS_R(pcrArray, pcrArraySz) == NULL ||
1440+
WOLFBOOT_TPM_NS_RW(quoteResult, sizeof(*quoteResult)) == NULL) {
1441+
return BAD_FUNC_ARG;
1442+
}
13751443

13761444
/* set auth for using the AIK */
13771445
wolfTPM2_SetAuthHandle(&wolftpm_dev, 0, &aik->handle);

src/wc_callable.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,31 @@
3434
#include "wolfboot/wcs_fwtpm.h"
3535
#endif
3636

37+
/* wcs_get_random is a cmse_nonsecure_entry veneer: the rand pointer and size
38+
* arrive from the non-secure caller and are used as the write target of
39+
* wc_RNG_GenerateBlock. Validate the whole range lives in the non-secure world
40+
* before writing, otherwise an NS caller could aim the RNG output at Secure SRAM
41+
* (a confused-deputy write primitive). The check verifies only the
42+
* Secure/Non-secure attribution (CMSE_NONSECURE); the MPU read/write permission
43+
* bits are deliberately not required, as they read back as 0 when the NS MPU is
44+
* disabled (NO_MPU) and do not constrain Secure accesses to NS memory anyway.
45+
* Outside a CMSE secure build there is no security boundary, so the check
46+
* collapses to a non-NULL pass-through. */
47+
#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U)
48+
#include <arm_cmse.h>
49+
#define WOLFBOOT_WCS_NS_RW(p, sz) \
50+
cmse_check_address_range((void*)(p), (size_t)(sz), CMSE_NONSECURE)
51+
#else
52+
#define WOLFBOOT_WCS_NS_RW(p, sz) ((void*)(p))
53+
#endif
54+
3755
static WC_RNG wcs_rng;
3856

3957
int CSME_NSE_API wcs_get_random(uint8_t *rand, uint32_t size)
4058
{
59+
if (WOLFBOOT_WCS_NS_RW(rand, size) == NULL) {
60+
return BAD_FUNC_ARG;
61+
}
4162
return wc_RNG_GenerateBlock(&wcs_rng, rand, size);
4263
}
4364

0 commit comments

Comments
 (0)