From 9b443b455a856b418b919b33b9c4a6ef2031ae2c Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 11 Sep 2026 12:35:58 -0400 Subject: [PATCH 1/7] build-sys: Build libtpms v0.7.13 Signed-off-by: Stefan Berger --- configure.ac | 2 +- dist/libtpms.spec | 2 +- include/libtpms/tpm_library.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac b/configure.ac index 9f96bb641..b2caa8acb 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ # # See the LICENSE file for the license associated with this file. -AC_INIT([libtpms],[0.7.11]) +AC_INIT([libtpms],[0.7.13]) AC_PREREQ([2.69]) AC_CONFIG_SRCDIR(Makefile.am) AC_CONFIG_AUX_DIR([.]) diff --git a/dist/libtpms.spec b/dist/libtpms.spec index d914d7dd6..5de7ed333 100644 --- a/dist/libtpms.spec +++ b/dist/libtpms.spec @@ -1,7 +1,7 @@ # --- libtpm rpm-spec --- %define name libtpms -%define version 0.7.11 +%define version 0.7.13 %define release 0 # Valid crypto subsystems are 'freebl' and 'openssl' diff --git a/include/libtpms/tpm_library.h b/include/libtpms/tpm_library.h index 14c55febe..e775a2365 100644 --- a/include/libtpms/tpm_library.h +++ b/include/libtpms/tpm_library.h @@ -50,7 +50,7 @@ extern "C" { #define TPM_LIBRARY_VER_MAJOR 0 #define TPM_LIBRARY_VER_MINOR 7 -#define TPM_LIBRARY_VER_MICRO 11 +#define TPM_LIBRARY_VER_MICRO 13 #define TPM_LIBRARY_VERSION_GEN(MAJ, MIN, MICRO) \ (( MAJ << 16 ) | ( MIN << 8 ) | ( MICRO )) From a970a52d6d35aa8d18da3278b7e6841f6c38fde6 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Fri, 2 Jan 2026 11:37:31 -0500 Subject: [PATCH 2/7] Fix a compilation error in TPMLIB_GetPlaintext Fix a compilation error that newer gcc versions may complain about: tpm_library.c: In function 'TPMLIB_GetPlaintext': tpm_library.c:441:11: error: assignment discards 'const' qualifier from pointer target type [-Werror=discarded-qualifiers] 441 | start = strstr(stream, starttag); | ^ At top level: cc1: note: unrecognized command-line option '-Wno-self-assign' may have been intended to silence earlier diagnostics cc1: all warnings being treated as errors Signed-off-by: Stefan Berger --- src/tpm_library.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpm_library.c b/src/tpm_library.c index de4ed1469..6d5c76514 100644 --- a/src/tpm_library.c +++ b/src/tpm_library.c @@ -415,7 +415,7 @@ static unsigned char *TPMLIB_GetPlaintext(const char *stream, const char *endtag, size_t *length) { - char *start, *end; + const char *start, *end; unsigned char *plaintext = NULL; start = strstr(stream, starttag); From a16bf486e467d33c0cd96a50facd583173f89e6b Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 11 Aug 2026 14:54:11 -0400 Subject: [PATCH 3/7] Sync: Clear OBJECT on ObjectFlush (CVE-2026-6726) This patch applies a fix for CVE-2026-6726 by clearing the OBJECT on ObjectFlush. libtpms does NOT seem to be affected by the vulnerability since a previous commit already resolved this issue: https://github.com/stefanberger/libtpms/commit/33a03986e0a09dde439985e0312d1c8fb3743aab Signed-off-by: Stefan Berger --- src/tpm2/Object.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tpm2/Object.c b/src/tpm2/Object.c index bd98fd9d1..115ba908d 100644 --- a/src/tpm2/Object.c +++ b/src/tpm2/Object.c @@ -75,6 +75,7 @@ ObjectFlush( OBJECT *object ) { + MemorySet(object, 0, sizeof(*object)); object->attributes.occupied = CLEAR; } /* 8.6.3.2 ObjectSetInUse() */ From 714791f6b3ace36430d77523634b2ea11606b8ba Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Mon, 16 Jun 2025 10:32:01 -0400 Subject: [PATCH 4/7] tpm2: Improve OaepDecode constant-timeness (CVE-2026-6727) This patch fixes CVE-2026-6727. Libtpms is typically not affected unless it was configured with --disable-use-openssl-functions or if it was built with '-DUSE_OPENSSL_FUNCTIONS_RSA=0'. Neither would be the 'typical' case. OaepDecode exposes a potential timing side-channel. Fix the timing side-channel of this function by accumulating a bad result in a variable while still executing most of the code. This at least causes the test case to not report the issue anymore. Note: This issue only appears when using libtpms with --disable-use-openssl-functions, which circumvents many OpenSSL functions for RSA, ECDSA, and others, that likely handle constant-time issues better than the TPM 2 reference code does. Signed-off-by: Stefan Berger --- src/tpm2/crypto/openssl/CryptRsa.c | 62 ++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/tpm2/crypto/openssl/CryptRsa.c b/src/tpm2/crypto/openssl/CryptRsa.c index d250c775d..21d39cb7c 100644 --- a/src/tpm2/crypto/openssl/CryptRsa.c +++ b/src/tpm2/crypto/openssl/CryptRsa.c @@ -67,9 +67,10 @@ /* Need this define to get the private defines for this function */ #define CRYPT_RSA_C #include "Tpm.h" -#include "Helpers_fp.h" // libtpms added +#include "Helpers_fp.h" // libtpms added begin -#include // libtpms added +#include +#include // libtpms added end #if ALG_RSA /* 10.2.19.3 Obligatory Initialization Functions */ @@ -364,17 +365,21 @@ OaepDecode( TPM2B *padded // IN: the padded data ) { - UINT32 i; + UINT32 i, j, k, l; BYTE seedMask[MAX_DIGEST_SIZE]; UINT32 hLen = CryptHashGetDigestSize(hashAlg); BYTE mask[MAX_RSA_KEY_BYTES]; BYTE *pp; BYTE *pm; TPM_RC retVal = TPM_RC_SUCCESS; + BOOL bad = FALSE; + NUMBYTES sizemask; // Strange size (anything smaller can't be an OAEP padded block) // Also check for no leading 0 - if((padded->size < (unsigned)((2 * hLen) + 2)) || (padded->buffer[0] != 0)) + // padded->size should be the size of an RSA public key (128/256/384) + if(padded->size < (unsigned)((2 * hLen) + 2)) ERROR_RETURN(TPM_RC_VALUE); + bad |= (padded->buffer[0] != 0); // Use the hash size to determine what to put through MGF1 in order // to recover the seedMask CryptMGF1(hLen, seedMask, hashAlg, padded->size - hLen - 1, @@ -397,29 +402,46 @@ OaepDecode( if((CryptHashBlock(hashAlg, label->size, (BYTE *)label->buffer, hLen, seedMask)) != hLen) FAIL(FATAL_ERROR_INTERNAL); - if(memcmp(seedMask, mask, hLen) != 0) - ERROR_RETURN(TPM_RC_VALUE); - // find the start of the data + bad |= (CRYPTO_memcmp(seedMask, mask, hLen) != 0); + // find the start of the data in constant-time (continue loop once found) pm = &mask[hLen]; - for(i = (UINT32)padded->size - (2 * hLen) - 1; i > 0; i--) - { - if(*pm++ != 0) - break; - } + i = 0; + for(l = (UINT32)padded->size - (2 * hLen) - 1; l > 0; l--) + { + UINT32 is_nonzero = (*pm != 0); // 0 or 1 + UINT32 not_found_yet = (i == 0); // 1 if we haven't found a non-zero yet + // Latch position on first non-zero: if is_nonzero AND not_found_yet, set j = i + i |= (l & (0 - (is_nonzero & not_found_yet))); + pm++; + } + // adjust pm according to 'i'; we would have left the loop once (*pm++ != 0) + pm = &mask[(UINT32)padded->size - hLen - i]; // If we ran out of data or didn't end with 0x01, then return an error - if(i == 0 || pm[-1] != 0x01) - ERROR_RETURN(TPM_RC_VALUE); + bad |= i == 0; + // pm is at least at offset of hLen + 1 into mask => pm[-1] will not be out-of-bounds + bad |= pm[-1] != 0x01; + // pm should be pointing at the first part of the data // and i is one greater than the number of bytes to move - i--; - if(i > dataOut->size) - // Special exit to preserve the size of the output buffer - return TPM_RC_VALUE; + i -= (i > 0); + bad |= i > dataOut->size; + + // Limit i to dataOut->size; + j = i; + k = dataOut->size; + if(i > k) + i = k; + else + i = j; + memcpy(dataOut->buffer, pm, i); dataOut->size = (UINT16)i; + retVal = bad ? TPM_RC_VALUE : TPM_RC_SUCCESS; + Exit: - if(retVal != TPM_RC_SUCCESS) - dataOut->size = 0; + sizemask = retVal != TPM_RC_SUCCESS ? 0 : ~0; + dataOut->size &= sizemask; + return retVal; } /* 10.2.19.4.7 PKCS1v1_5Encode() */ From fbaada329889489dad49af06f366eb902db8d6d0 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 3 Sep 2026 14:47:01 -0400 Subject: [PATCH 5/7] tpm2: Add checks for *size < 0 before casting it to UINT32 Signed-off-by: Stefan Berger --- src/tpm2/Unmarshal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tpm2/Unmarshal.c b/src/tpm2/Unmarshal.c index c839a6875..a83d8ff7f 100644 --- a/src/tpm2/Unmarshal.c +++ b/src/tpm2/Unmarshal.c @@ -48,7 +48,7 @@ TPM_RC UINT8_Unmarshal(UINT8 *target, BYTE **buffer, INT32 *size) { - if ((UINT32)*size < sizeof(UINT8)) { + if (*size < 0 || (UINT32)*size < sizeof(UINT8)) { return TPM_RC_INSUFFICIENT; } *target = (*buffer)[0]; @@ -66,7 +66,7 @@ INT8_Unmarshal(INT8 *target, BYTE **buffer, INT32 *size) TPM_RC UINT16_Unmarshal(UINT16 *target, BYTE **buffer, INT32 *size) { - if ((UINT32)*size < sizeof(UINT16)) { + if (*size < 0 || (UINT32)*size < sizeof(UINT16)) { return TPM_RC_INSUFFICIENT; } *target = ((UINT16)((*buffer)[0]) << 8) | @@ -79,7 +79,7 @@ UINT16_Unmarshal(UINT16 *target, BYTE **buffer, INT32 *size) TPM_RC UINT32_Unmarshal(UINT32 *target, BYTE **buffer, INT32 *size) { - if ((UINT32)*size < sizeof(UINT32)) { + if (*size < 0 || (UINT32)*size < sizeof(UINT32)) { return TPM_RC_INSUFFICIENT; } *target = ((UINT32)((*buffer)[0]) << 24) | @@ -94,7 +94,7 @@ UINT32_Unmarshal(UINT32 *target, BYTE **buffer, INT32 *size) TPM_RC UINT64_Unmarshal(UINT64 *target, BYTE **buffer, INT32 *size) { - if ((UINT32)*size < sizeof(UINT64)) { + if (*size < 0 || (UINT32)*size < sizeof(UINT64)) { return TPM_RC_INSUFFICIENT; } *target = ((UINT64)((*buffer)[0]) << 56) | @@ -115,7 +115,7 @@ Array_Unmarshal(BYTE *targetBuffer, UINT16 targetSize, BYTE **buffer, INT32 *siz { TPM_RC rc = TPM_RC_SUCCESS; - if (targetSize > *size) { + if (*size < 0 || targetSize > (UINT32)*size) { rc = TPM_RC_INSUFFICIENT; } else { From 675ec2612d46c44a7f06f18c236b2080d4f97839 Mon Sep 17 00:00:00 2001 From: sameera Date: Fri, 16 Oct 2026 21:08:24 +0530 Subject: [PATCH 6/7] tpm12: reject non-block-aligned CBC input in TPM_SymmetricKeyData_Decrypt TPM_SymmetricKeyData_Decrypt (AES path in src/tpm12/tpm_crypto.c, OpenSSL backend; and the NSS/FreeBL backend in src/tpm12/tpm_crypto_freebl.c) only checked encrypt_length < TPM_AES_BLOCK_SIZE before calling AES_cbc_encrypt(..., AES_DECRYPT) on a buffer allocated to exactly encrypt_length bytes. CBC decryption always consumes a whole final block, so a length that is not a multiple of 16 makes OpenSSL read up to 15 bytes past the end of the input buffer. That length is TPM_CONTEXT_BLOB.sensitiveData.size from the command stream. TPM_Process_LoadKeyContext and TPM_Process_LoadAuthContext run under TPM_CHECK_ALLOW_NO_OWNER and decrypt the blob with the permanent contextKey before the integrity HMAC is checked, so the over-read is reachable with a single unauthenticated command. Reproduced with a TPM_LoadKeyContext command whose sensitiveData.size is 17; AddressSanitizer reports a heap over-read: READ of size 4 ... #0 AES_decrypt aes_core.c #1 CRYPTO_cbc128_decrypt cbc128.c #2 TPM_SymmetricKeyData_Decrypt tpm_crypto.c #3 TPM_Process_LoadKeyContext tpm_session.c The fix rejects a length that is not a whole number of blocks, the same requirement the DES backend already enforces. The command then returns TPM_DECRYPT_ERROR. A zero length is still rejected by TPM_Malloc(). Signed-off-by: sameera --- src/tpm12/tpm_crypto.c | 5 +++-- src/tpm12/tpm_crypto_freebl.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tpm12/tpm_crypto.c b/src/tpm12/tpm_crypto.c index 4d6ea6896..4d198980a 100644 --- a/src/tpm12/tpm_crypto.c +++ b/src/tpm12/tpm_crypto.c @@ -2864,9 +2864,10 @@ TPM_RESULT TPM_SymmetricKeyData_Decrypt(unsigned char **decrypt_data, /* outpu (TPM_SYMMETRIC_KEY_DATA *)tpm_symmetric_key_token; printf(" TPM_SymmetricKeyData_Decrypt: Length %u\n", encrypt_length); - /* sanity check encrypted length */ + /* sanity check encrypted length; CBC requires a whole number of blocks, + otherwise AES_cbc_encrypt() reads past the end of the input buffer */ if (rc == 0) { - if (encrypt_length < TPM_AES_BLOCK_SIZE) { + if ((encrypt_length % TPM_AES_BLOCK_SIZE) != 0) { printf("TPM_SymmetricKeyData_Decrypt: Error, bad length\n"); rc = TPM_DECRYPT_ERROR; } diff --git a/src/tpm12/tpm_crypto_freebl.c b/src/tpm12/tpm_crypto_freebl.c index 15dffa506..ac873e48a 100644 --- a/src/tpm12/tpm_crypto_freebl.c +++ b/src/tpm12/tpm_crypto_freebl.c @@ -2323,9 +2323,9 @@ TPM_RESULT TPM_SymmetricKeyData_Decrypt(unsigned char **decrypt_data, /* outpu printf(" TPM_SymmetricKeyData_Decrypt: Length %u\n", encrypt_length); cx = NULL; /* freed @1 */ - /* sanity check encrypted length */ + /* sanity check encrypted length; CBC requires a whole number of blocks */ if (rc == 0) { - if (encrypt_length < TPM_AES_BLOCK_SIZE) { + if ((encrypt_length % TPM_AES_BLOCK_SIZE) != 0) { printf("TPM_SymmetricKeyData_Decrypt: Error, bad length\n"); rc = TPM_DECRYPT_ERROR; } From b1221ca826bdfa284b851a9ef9ca05602a39c1cd Mon Sep 17 00:00:00 2001 From: sameera Date: Sat, 24 Oct 2026 14:59:22 +0530 Subject: [PATCH 7/7] tpm12: validate SHA1 context num on load in TPM_Sha1Context_Load TPM_Sha1Context_Load() (OpenSSL backend, src/tpm12/tpm_crypto.c) deserializes the OpenSSL SHA_CTX from a TPMLIB_SetState(VOLATILE) blob and restores sha_ctx->num with TPM_Load32() without any range check. num is the number of bytes buffered in sha_ctx->data and OpenSSL uses it as the write offset in SHA1_Update()/SHA1_Final(): the update path runs memcpy(p + num, data, SHA_CBLOCK - num). A restored num >= SHA_CBLOCK makes SHA_CBLOCK - num wrap to a huge size_t, so the next update writes far past the heap-allocated context, and SHA1_Final() writes the 0x80 pad byte at p[num] well outside it. The restored sha1_context / sha1_context_tis are later driven by the TPM_SHA1Update command and by the host-side TPM_IO_Hash_Data(), both of which reach TPM_SHA1UpdateCmd() -> SHA1_Update(). The volatile-state blob is only protected by an unkeyed SHA1 integrity digest computed after parsing, so num is attacker-controlled across a state restore. Reproduced by loading a SHA1 context with num = 256 and then issuing one update; AddressSanitizer reports a wrapped copy: ERROR: AddressSanitizer: negative-size-param: (size=-192) #0 memcpy #1 SHA1_Update #2 TPM_SHA1UpdateCmd tpm_crypto.c:2028 Reject num >= SHA_CBLOCK with TPM_BAD_PARAMETER and free the partially loaded context, the same num bound the TPM 2.0 hash-state unmarshalers already enforce. The FreeBL backend loads into a fixed-layout struct guarded by a flatten-size check and does not restore a write offset, so only the OpenSSL path is affected. Signed-off-by: sameera --- src/tpm12/tpm_crypto.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tpm12/tpm_crypto.c b/src/tpm12/tpm_crypto.c index 4d198980a..a5f5dc4e1 100644 --- a/src/tpm12/tpm_crypto.c +++ b/src/tpm12/tpm_crypto.c @@ -2138,6 +2138,20 @@ TPM_RESULT TPM_Sha1Context_Load(void **context, if ((rc== 0) && contextPresent) { rc = TPM_Load32(&(sha_ctx->num), stream, stream_size); } + /* 'num' is the number of bytes buffered in sha_ctx->data and is used by + OpenSSL as the write offset in SHA1_Update()/SHA1_Final(); it must be + less than the block size, else a later update walks off the context. */ + if ((rc== 0) && contextPresent) { + if (sha_ctx->num >= SHA_CBLOCK) { + printf("TPM_Sha1Context_Load: Error, num %u out of range\n", + sha_ctx->num); + rc = TPM_BAD_PARAMETER; + } + } + if ((rc != 0) && contextPresent && (*context != NULL)) { + free(*context); + *context = NULL; + } return rc; }