The length of a TPM reply comes from the device and cannot be trusted to be well-formed. Add DM tests for the malformed replies that the length checks reject.
For TPM2 GetCapability: one reply too short to hold its header, and one advertising more data than the caller's buffer holds. The sandbox emulator gains two test-only capability properties that produce them; the short reply is caught by the length check in tpm_sendrecv_command(), the over-long one by the caller's buffer bound. For the TPM1 OIAP helpers tpm1_load_key2_oiap() and tpm1_get_pub_key_oiap(): a reply too short to hold the auth trailer that their length arithmetic subtracts. The emulator answers OIAP with a zeroed session and both commands with a truncated reply, so the length check rejects them before the subtraction underflows. These paths need CONFIG_TPM_AUTH_SESSIONS, enabled here in the sandbox config. Signed-off-by: Shahriyar Jalayeri <[email protected]> --- configs/sandbox_defconfig | 1 + drivers/tpm/tpm2_tis_sandbox.c | 28 ++++++++++++++ drivers/tpm/tpm_tis_sandbox.c | 9 +++++ include/tpm-v2.h | 10 +++++ test/dm/tpm.c | 88 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 79f46317e45..db26b6813de 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -394,6 +394,7 @@ CONFIG_ECDSA=y CONFIG_ECDSA_VERIFY=y CONFIG_RSASSA_PSS=y CONFIG_TPM=y +CONFIG_TPM_AUTH_SESSIONS=y CONFIG_ERRNO_STR=y CONFIG_GETOPT=y CONFIG_TEST_FDTDEC=y diff --git a/drivers/tpm/tpm2_tis_sandbox.c b/drivers/tpm/tpm2_tis_sandbox.c index 50e308e7116..1fae0fc6ce1 100644 --- a/drivers/tpm/tpm2_tis_sandbox.c +++ b/drivers/tpm/tpm2_tis_sandbox.c @@ -542,6 +542,34 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, property_count = get_unaligned_be32(sent); sent += sizeof(property_count); + /* + * Test hook: reply with a truncated (header-only) success + * response so the response parser can be exercised against a + * reply too short to hold the data it promises. Honoured for any + * capability so the check can be tested on a path without a + * capability-specific length bound. + */ + if (property == TPM2_PT_SANDBOX_SHORT_RESPONSE) + return sandbox_tpm2_fill_buf(recv, recv_len, tag, + TPM2_RC_SUCCESS); + + /* + * Test hook: reply with a success response advertising more + * property data than a single-property request can hold, to + * exercise the caller-buffer bound in the parser. + */ + if (capability == TPM2_CAP_TPM_PROPERTIES && + property == TPM2_PT_SANDBOX_LONG_RESPONSE) { + *recv_len = TPM2_HDR_LEN + sizeof(u8) + sizeof(u32) + + sizeof(u32) + TPM2_PROPERTY_NB * + sizeof(struct tpms_tagged_property); + put_unaligned_be16(tag, recv); + put_unaligned_be32(*recv_len, recv + sizeof(tag)); + put_unaligned_be32(TPM2_RC_SUCCESS, + recv + sizeof(tag) + sizeof(u32)); + return 0; + } + switch (capability) { case TPM2_CAP_PCRS: break; diff --git a/drivers/tpm/tpm_tis_sandbox.c b/drivers/tpm/tpm_tis_sandbox.c index d7341062b31..1f63e60cbaa 100644 --- a/drivers/tpm/tpm_tis_sandbox.c +++ b/drivers/tpm/tpm_tis_sandbox.c @@ -226,6 +226,15 @@ static int sandbox_tpm_xfer(struct udevice *dev, const uint8_t *sendbuf, *recv_len = 12; memset(recvbuf, '\0', *recv_len); break; + case 0x0a: /* TPM_ORD_OIAP: hand out a zeroed auth session for tests */ + *recv_len = TPM_HDR_LEN + sizeof(uint32_t) + 20; + memset(recvbuf, '\0', *recv_len); + break; + case 0x21: /* TPM_ORD_GetPubKey: truncated reply, for the length check */ + case 0x41: /* TPM_ORD_LoadKey2: truncated reply, for the length check */ + *recv_len = TPM_HDR_LEN; + memset(recvbuf, '\0', *recv_len); + break; default: printf("Unknown tpm command %02x\n", code); return -ENOSYS; diff --git a/include/tpm-v2.h b/include/tpm-v2.h index 4f5082f2234..648dfa2aec8 100644 --- a/include/tpm-v2.h +++ b/include/tpm-v2.h @@ -43,6 +43,16 @@ struct udevice; #define TPM2_CAP_PCRS 0x00000005U #define TPM2_CAP_TPM_PROPERTIES 0x00000006U +/* + * Sandbox emulator test hooks: a TPM2_GetCapability for these properties makes + * the emulated TPM reply with, respectively, a header-only response and one + * advertising more property data than a single-property request can hold. They + * let tests drive the response parser with a reply shorter or longer than the + * data it should carry, as a tampered or faulty TPM on the bus could. + */ +#define TPM2_PT_SANDBOX_SHORT_RESPONSE 0x00ffffff +#define TPM2_PT_SANDBOX_LONG_RESPONSE 0x00fffffe + /* Definition of (UINT32) TPM2_PT Constants */ #define TPM2_PT_GROUP (u32)(0x00000100) #define TPM2_PT_FIXED (u32)(TPM2_PT_GROUP * 1) diff --git a/test/dm/tpm.c b/test/dm/tpm.c index 87c5c416daa..eb6e55c3ab0 100644 --- a/test/dm/tpm.c +++ b/test/dm/tpm.c @@ -6,6 +6,8 @@ #include <dm.h> #include <tpm_api.h> +#include <tpm-v1.h> +#include <tpm-v2.h> #include <dm/test.h> #include <test/test.h> #include <test/ut.h> @@ -197,3 +199,89 @@ static int dm_test_tpm_autostart_reinit(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_tpm_autostart_reinit, UTF_SCAN_FDT); + +/* + * A TPM sits on a bus a physical attacker can reach, so its responses cannot be + * trusted to be well-formed. Check that a reply too short to hold its header is + * rejected by tpm_sendrecv_command(), rather than parsed with an underflowed + * length. + */ +static int dm_test_tpm2_get_capability_short(struct unit_test_state *uts) +{ + struct udevice *dev; + u8 buf[64]; + + ut_assertok(get_tpm_version(TPM_V2, &dev)); + ut_assertok(tpm_auto_start(dev)); + + ut_assert(tpm2_get_capability(dev, TPM2_CAP_PCRS, + TPM2_PT_SANDBOX_SHORT_RESPONSE, buf, + sizeof(buf), 1)); + + return 0; +} +DM_TEST(dm_test_tpm2_get_capability_short, UTF_SCAN_FDT); + +/* + * Check that a GetCapability reply larger than the caller's buffer is rejected, + * rather than copied past the end of it. + */ +static int dm_test_tpm2_get_capability_long(struct unit_test_state *uts) +{ + struct udevice *dev; + u8 buf[16]; + + ut_assertok(get_tpm_version(TPM_V2, &dev)); + ut_assertok(tpm_auto_start(dev)); + + ut_assert(tpm2_get_capability(dev, TPM2_CAP_TPM_PROPERTIES, + TPM2_PT_SANDBOX_LONG_RESPONSE, buf, + sizeof(buf), 1)); + + return 0; +} +DM_TEST(dm_test_tpm2_get_capability_long, UTF_SCAN_FDT); + +#if CONFIG_IS_ENABLED(TPM_AUTH_SESSIONS) +/* + * A TPM1 OIAP reply carries an auth trailer that is subtracted from a + * device-controlled length. Check that a LoadKey2 reply too short to hold that + * trailer is rejected, rather than parsed with an underflowed length. + */ +static int dm_test_tpm1_load_key2_oiap_short(struct unit_test_state *uts) +{ + struct udevice *dev; + u8 key[8] = {0}; + u8 auth[20] = {0}; + u32 handle; + + ut_assertok(get_tpm_version(TPM_V1, &dev)); + ut_assertok(tpm_init(dev)); + + ut_assert(tpm1_load_key2_oiap(dev, 0, key, sizeof(key), auth, &handle)); + + return 0; +} +DM_TEST(dm_test_tpm1_load_key2_oiap_short, UTF_SCAN_FDT); + +/* + * The GetPubKey OIAP helper subtracts the same auth trailer from the same + * device-controlled length. Check that a reply too short to hold the trailer + * is rejected, rather than parsed with an underflowed length. + */ +static int dm_test_tpm1_get_pub_key_oiap_short(struct unit_test_state *uts) +{ + struct udevice *dev; + u8 auth[20] = {0}; + u8 pubkey[8] = {0}; + size_t pubkey_len = sizeof(pubkey); + + ut_assertok(get_tpm_version(TPM_V1, &dev)); + ut_assertok(tpm_init(dev)); + + ut_assert(tpm1_get_pub_key_oiap(dev, 0, auth, pubkey, &pubkey_len)); + + return 0; +} +DM_TEST(dm_test_tpm1_get_pub_key_oiap_short, UTF_SCAN_FDT); +#endif -- 2.43.0
