[PATCH v2 0/2] tpm: Add TPM2_GetTestResult command support

Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
Currently, the tpm2_self_test function is implemented and can be called directly. The get_test_result function enables interested parties to retrieve the test result provided by the self test.
--- Changes in v2: - Apply style changes suggested by Ilias Apalodimas ilias.apalodimas@linaro.org - Move test cases reliant on restart_uboot() to seperate patch in response to Simon Glass sjg@chromium.org - Change test result data to be read directly from the response - Fix minor style issues
Julia Daxenberger (2): tpm: Add TPM2_GetTestResult command support tpm: Add error scenarios to get_test_result test
cmd/tpm-v2.c | 60 +++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 51 +++++++++++++++++- include/tpm-v2.h | 23 ++++++++ lib/tpm-v2.c | 97 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 ++++++++++++++++ 5 files changed, 280 insertions(+), 1 deletion(-)

Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com --- Changes in v2: - Apply style changes suggested by Ilias Apalodimas ilias.apalodimas@linaro.org - Move test cases reliant on restart_uboot() to separate patch in response to Simon Glass sjg@chromium.org - Change test result data to be read directly from the response - Fix minor style issues
cmd/tpm-v2.c | 60 +++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 51 +++++++++++++++++- include/tpm-v2.h | 23 ++++++++ lib/tpm-v2.c | 97 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 22 ++++++++ 5 files changed, 252 insertions(+), 1 deletion(-)
diff --git a/cmd/tpm-v2.c b/cmd/tpm-v2.c index 7e479b9dfe..acbd7171f5 100644 --- a/cmd/tpm-v2.c +++ b/cmd/tpm-v2.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2018 Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> @@ -13,6 +14,8 @@ #include <tpm-v2.h> #include "tpm-user-utils.h"
+#define TEST_RESULT_DATA_BUFFER_SIZE 256 + static int do_tpm2_startup(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { @@ -356,6 +359,57 @@ static int do_tpm_pcr_setauthvalue(struct cmd_tbl *cmdtp, int flag, key, key_sz)); }
+static int do_tpm2_get_test_result(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct udevice *dev; + int ret; + u8 data[TEST_RESULT_DATA_BUFFER_SIZE]; + size_t data_size = sizeof(data); + u32 test_result; + u32 rc; + + if (argc != 1) + return CMD_RET_USAGE; + + ret = get_tpm(&dev); + if (ret) + return ret; + + rc = tpm2_get_test_result(dev, data, &data_size, &test_result); + if (rc) + goto out; + + printf("Test Result:\n0x%08X ", test_result); + switch (test_result) { + case TPM2_RC_SUCCESS: + printf("TPM2_RC_SUCCESS\n"); + break; + case TPM2_RC_FAILURE: + printf("TPM2_RC_FAILURE\n"); + break; + case TPM2_RC_NEEDS_TEST: + printf("TPM2_RC_NEEDS_TEST\n"); + break; + case TPM2_RC_TESTING: + printf("TPM2_RC_TESTING\n"); + break; + } + + if (!data_size) { + printf("No Test Result Data available\n"); + goto out; + } + + printf("Test Result Data of Self Test:\n0x"); + for (int i = 0; i < data_size; i++) + printf("%02X", data[i]); + printf("\n"); + +out: + return report_return_code(rc); +} + static struct cmd_tbl tpm2_commands[] = { U_BOOT_CMD_MKENT(device, 0, 1, do_tpm_device, "", ""), U_BOOT_CMD_MKENT(info, 0, 1, do_tpm_info, "", ""), @@ -375,6 +429,8 @@ static struct cmd_tbl tpm2_commands[] = { do_tpm_pcr_setauthpolicy, "", ""), U_BOOT_CMD_MKENT(pcr_setauthvalue, 0, 1, do_tpm_pcr_setauthvalue, "", ""), + U_BOOT_CMD_MKENT(get_test_result, 0, 1, + do_tpm2_get_test_result, "", ""), };
struct cmd_tbl *get_tpm2_commands(unsigned int *size) @@ -453,4 +509,8 @@ U_BOOT_CMD(tpm2, CONFIG_SYS_MAXARGS, 1, do_tpm, "Issue a TPMv2.x command", " <pcr>: index of the PCR\n" " <key>: secret to protect the access of PCR #<pcr>\n" " <password>: optional password of the PLATFORM hierarchy\n" +"get_test_result\n" +" Show manufacturer-specific information regarding the results of a\n" +" self-test and an indication of the test status.\n" + ); diff --git a/drivers/tpm/tpm2_tis_sandbox.c b/drivers/tpm/tpm2_tis_sandbox.c index e4004cfcca..420961944d 100644 --- a/drivers/tpm/tpm2_tis_sandbox.c +++ b/drivers/tpm/tpm2_tis_sandbox.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2018, Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> @@ -231,6 +232,7 @@ static int sandbox_tpm2_check_session(struct udevice *dev, u32 command, u16 tag, case TPM2_CC_SELF_TEST: case TPM2_CC_GET_CAPABILITY: case TPM2_CC_PCR_READ: + case TPM2_CC_GET_TEST_RESULT: if (tag != TPM2_ST_NO_SESSIONS) { printf("No session required for command 0x%x\n", command); @@ -364,6 +366,13 @@ static int sandbox_tpm2_check_readyness(struct udevice *dev, int command) if (!tpm->startup_done) return TPM2_RC_INITIALIZE;
+ break; + case TPM2_CC_GET_TEST_RESULT: + if (!tpm->init_done || !tpm->startup_done) + return TPM2_RC_INITIALIZE; + if (!tpm->tests_done) + return TPM2_RC_NEEDS_TEST; + break; default: /* Skip this, since the startup may have happened in SPL @@ -458,7 +467,7 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, command = get_unaligned_be32(sent); sent += sizeof(command); rc = sandbox_tpm2_check_readyness(dev, command); - if (rc) { + if (rc && rc != TPM2_RC_NEEDS_TEST) { sandbox_tpm2_fill_buf(recv, recv_len, tag, rc); return 0; } @@ -778,6 +787,46 @@ static int sandbox_tpm2_xfer(struct udevice *dev, const u8 *sendbuf, *recv_len = 12; memset(recvbuf, '\0', *recv_len); break; + + case TPM2_CC_GET_TEST_RESULT: { + u32 testresult = 0; + u8 data[13] = { 0 }; + + /* Check readiness */ + testresult = sandbox_tpm2_check_readyness(dev, command); + + /* Write tag */ + put_unaligned_be16(tag, recv); + recv += sizeof(tag); + + /* Ignore length for now */ + recv += sizeof(u32); + + /* Write return code */ + put_unaligned_be32(rc, recv); + recv += sizeof(rc); + + /* + * Write manufacturer-specific test result data. + * The first two bytes contain the length of the data + */ + data[1] = 13 - 2; + memcpy(recv, data, sizeof(data)); + recv += sizeof(data); + + /* Write test result */ + put_unaligned_be32(testresult, recv); + recv += sizeof(testresult); + + /* Add trailing \0 */ + *recv = '\0'; + + /* Write response length */ + *recv_len = recv - recvbuf; + put_unaligned_be32(*recv_len, recvbuf + sizeof(tag)); + + break; + } default: printf("TPM2 command %02x unknown in Sandbox\n", command); rc = TPM2_RC_COMMAND_CODE; diff --git a/include/tpm-v2.h b/include/tpm-v2.h index 2b6980e441..0df08e6aaa 100644 --- a/include/tpm-v2.h +++ b/include/tpm-v2.h @@ -3,6 +3,7 @@ * Defines APIs and structures that allow software to interact with a * TPM2 device * + * Copyright (C) 2023 Infineon Technologies AG * Copyright (c) 2020 Linaro * Copyright (c) 2018 Bootlin * @@ -276,6 +277,7 @@ enum tpm2_handles { * @TPM2_CC_DAM_PARAMETERS: TPM2_DictionaryAttackParameters(). * @TPM2_CC_GET_CAPABILITY: TPM2_GetCapibility(). * @TPM2_CC_GET_RANDOM: TPM2_GetRandom(). + * @TPM2_CC_GET_TEST_RESULT: TPM2_GetTestResult(). * @TPM2_CC_PCR_READ: TPM2_PCR_Read(). * @TPM2_CC_PCR_EXTEND: TPM2_PCR_Extend(). * @TPM2_CC_PCR_SETAUTHVAL: TPM2_PCR_SetAuthValue(). @@ -296,6 +298,7 @@ enum tpm2_command_codes { TPM2_CC_NV_READ = 0x014E, TPM2_CC_GET_CAPABILITY = 0x017A, TPM2_CC_GET_RANDOM = 0x017B, + TPM2_CC_GET_TEST_RESULT = 0x017C, TPM2_CC_PCR_READ = 0x017E, TPM2_CC_PCR_EXTEND = 0x0182, TPM2_CC_PCR_SETAUTHVAL = 0x0183, @@ -706,4 +709,24 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd, */ u32 tpm2_auto_start(struct udevice *dev);
+/** + * Issue a TPM2_GetTestResult command. + * + * @dev TPM device + * @data output buffer for manufacturer-specific test result data + * @data_size input/output parameter + * input: size of data buffer, output: size of test result data + * @test_result output parameter: test result response code: + * TPM2_RC_NEEDS_TEST, if TPM2 self-test has not been executed and + * a testable function has not been tested + * TPM2_RC_TESTING, if TPM2 self-test is in progress. + * TPM2_RC_SUCCESS, if testing of all functions is complete without + * functional failures. + * TPM2_RC_FAILURE, if any test failed. + * + * Return: code of the operation + */ +u32 tpm2_get_test_result(struct udevice *dev, void *data, size_t *data_size, + u32 *test_result); + #endif /* __TPM_V2_H */ diff --git a/lib/tpm-v2.c b/lib/tpm-v2.c index 9ab5b46df1..7689832213 100644 --- a/lib/tpm-v2.c +++ b/lib/tpm-v2.c @@ -2,12 +2,14 @@ /* * Copyright (c) 2018 Bootlin * Author: Miquel Raynal miquel.raynal@bootlin.com + * Copyright (C) 2023 Infineon Technologies AG */
#include <common.h> #include <dm.h> #include <tpm-common.h> #include <tpm-v2.h> +#include <asm/unaligned.h> #include <linux/bitops.h> #include "tpm-utils.h"
@@ -742,3 +744,98 @@ u32 tpm2_enable_nvcommits(struct udevice *dev, uint vendor_cmd,
return 0; } + +u32 tpm2_get_test_result(struct udevice *dev, void *data, + size_t *data_size, u32 *test_result) +{ + const u8 command_v2[COMMAND_BUFFER_SIZE] = { + /* header 10 bytes */ + tpm_u16(TPM2_ST_NO_SESSIONS), /* TAG */ + tpm_u32(10), /* Length */ + tpm_u32(TPM2_CC_GET_TEST_RESULT), /* Command code */ + }; + int ret; + u8 response[COMMAND_BUFFER_SIZE]; + size_t response_reserved_size = sizeof(response); + u32 response_size; /* Size of response bytestream */ + unsigned int data_size_offset; + unsigned int data_offset; + unsigned int test_result_offset; + size_t data_response_size; + u32 min_response_size; + + ret = tpm_sendrecv_command(dev, command_v2, response, + &response_reserved_size); + log_debug("ret=%s, %x\n", dev->name, ret); + if (ret) + return ret; + + /* + * Get responseSize from return bytestream, switch endianness: big->host + * endianness + * In the response buffer, the responseSize is located after: + * tag (u16) + * The minimal response contains: + * tag (u16), response size (u32), response code (u32) + */ + response_size = get_unaligned_be32(response + sizeof(u16)); + min_response_size = sizeof(u16) + sizeof(u32) + sizeof(u32); + if (response_size < min_response_size) { + log_debug + ("Response Size: %d\nMinimal Response Size: %d\n", + response_size, min_response_size); + return TPM_LIB_ERROR; + } + if (response_size > response_reserved_size) { + log_debug + ("Response Size: %d\nReserved Response Size: %ld\n", + response_size, response_reserved_size); + return TPM_LIB_ERROR; + } + + /* + * Copy the test result data to buffer + * In the response buffer, the test result data is located after: + * tag (u16), response size (u32), response code (u32) and the data + * buffer size (u16). + */ + data_size_offset = sizeof(u16) + sizeof(u32) + sizeof(u32); + data_response_size = get_unaligned_be16(response + data_size_offset); + data_offset = data_size_offset + sizeof(u16); + + /* + * Checks, if the data buffer size data_response_size corresponds with + * the actual data size + */ + if (data_response_size != response_size - data_offset - sizeof(u32)) { + log_debug + ("Data Size from response: %ld\nActual Response Size: %ld\n", + data_response_size, + (response_size - data_offset - sizeof(u32))); + return TPM_LIB_ERROR; + } + + /* Checks, if the reserved data buffer size is insufficient */ + if (data_response_size > *data_size) { + log_debug + ("Data Size from response: %ld\nReserved Buffer Length: %ld\n", + data_response_size, *data_size); + return TPM_LIB_ERROR; + } + + memcpy(data, &response[data_offset], data_response_size); + + /* + * Get testResult from return bytestream, switch endianness: big->host + * endianness + * In the response buffer, the test result is located after: + * test result data (*data_size). + */ + test_result_offset = data_offset + data_response_size; + *test_result = get_unaligned_be32(response + test_result_offset); + + /* Return data length */ + *data_size = data_response_size; + + return 0; +} diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index fce689cd99..96bed36c5f 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018, Bootlin # Author: Miquel Raynal miquel.raynal@bootlin.com +# Copyright (C) 2023 Infineon Technologies AG
import os.path import pytest @@ -313,3 +314,24 @@ def test_tpm2_cleanup(u_boot_console): """Ensure the TPM is cleared from password or test related configuration."""
force_init(u_boot_console, True) + +@pytest.mark.buildconfigspec('cmd_tpm_v2') +def test_tpm2_get_test_result(u_boot_console): + """Execute a TPM_GetTestResult command. + + Ask the TPM to get the test result of the self test. + Display the Test Result and Test Result Data. + + Expected default value for test_result: + - TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures. + + There is no expected default value for the test result data because it would depend on the chip + used. The test result data is therefore not tested. + """ + u_boot_console.run_command('tpm2 self_test full') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert output.endswith('0') + assert 'Test Result:\r\r\n0x00000000 TPM2_RC_SUCCESS' in read_res + """ Assert console output. Test Result Data can not be tested, as it is vendor specific""" + assert 'Test Result Data of Self Test:\r\r\n0x' in read_res -- 2.34.1

On Wed, 2 Aug 2023 at 02:35, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
The TPM2_GetTestResult command is performed after the TPM2_SelfTest command and returns manufacturer-specific information regarding the results of the self-test and an indication of the test status.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
Changes in v2:
- Apply style changes suggested by Ilias Apalodimas
- Move test cases reliant on restart_uboot() to separate patch in
response to Simon Glass sjg@chromium.org
- Change test result data to be read directly from the response
- Fix minor style issues
cmd/tpm-v2.c | 60 +++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 51 +++++++++++++++++- include/tpm-v2.h | 23 ++++++++ lib/tpm-v2.c | 97 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 22 ++++++++ 5 files changed, 252 insertions(+), 1 deletion(-)
Reviewed-by: Simon Glass sjg@chromium.org

Patch 1/2 of this series provides basic testing of the get_test_result functionality. This Patch adds further testing of possible error scenarios, but relies on rebooting sandbox at the beginning of the test.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com --- Changes in v2: - Add error test cases reliant on restart_uboot() in response to Simon Glass sjg@chromium.org
test/py/tests/test_tpm2.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index 96bed36c5f..8416ff5d0b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -322,12 +322,40 @@ def test_tpm2_get_test_result(u_boot_console): Ask the TPM to get the test result of the self test. Display the Test Result and Test Result Data.
+ Expected default return value of tpm2_get_test_result, if the TPM has not been initialized: + - TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100. + Expected default value for test_result: + - TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed. - TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
There is no expected default value for the test result data because it would depend on the chip used. The test result data is therefore not tested. """ + if is_sandbox(u_boot_console): + u_boot_console.restart_uboot() + + skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) + if skip_test: + pytest.skip('skip TPM device test') + + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert 'Error: 256' in read_res + + u_boot_console.run_command('tpm2 init') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + assert 'Error: 256' in read_res + + u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') + read_res = u_boot_console.run_command('tpm2 get_test_result') + output = u_boot_console.run_command('echo $?') + """assert output.endswith('0')""" + assert 'Test Result:\r\r\n0x00000153 TPM2_RC_NEEDS_TEST' in read_res + """ Assert console output. Test Result Data can not be tested, as it is vendor specific""" + assert 'Test Result Data of Self Test:\r\r\n0x' in read_res + u_boot_console.run_command('tpm2 self_test full') read_res = u_boot_console.run_command('tpm2 get_test_result') output = u_boot_console.run_command('echo $?') -- 2.34.1

Hi Julia,
On Wed, 2 Aug 2023 at 02:35, Julia Daxenberger julia.daxenberger@infineon.com wrote:
Patch 1/2 of this series provides basic testing of the get_test_result functionality. This Patch adds further testing of possible error scenarios, but relies on rebooting sandbox at the beginning of the test.
Signed-off-by: Julia Daxenberger julia.daxenberger@infineon.com
Changes in v2:
- Add error test cases reliant on restart_uboot() in response to Simon
Glass sjg@chromium.org
test/py/tests/test_tpm2.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/test/py/tests/test_tpm2.py b/test/py/tests/test_tpm2.py index 96bed36c5f..8416ff5d0b 100644 --- a/test/py/tests/test_tpm2.py +++ b/test/py/tests/test_tpm2.py @@ -322,12 +322,40 @@ def test_tpm2_get_test_result(u_boot_console): Ask the TPM to get the test result of the self test. Display the Test Result and Test Result Data.
Expected default return value of tpm2_get_test_result, if the TPM has not been initialized:
- TPM2_RC_INITIALIZE = TPM2_RC_VER1 + 0x0000 = 0x00000100.
Expected default value for test_result:
- TPM_RC_NEEDS_TEST = 0x00000153, if tpm2 self_test has not been executed.
- TPM_RC_SUCCESS = 0x00000000, if testing is complete without functional failures.
There is no expected default value for the test result data because it would depend on the chip used. The test result data is therefore not tested. """
if is_sandbox(u_boot_console):
u_boot_console.restart_uboot()
Actually sandbox should not need a restart. What is missing about the current state init in dm_test_pre_run()?
- skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False)
- if skip_test:
pytest.skip('skip TPM device test')
- read_res = u_boot_console.run_command('tpm2 get_test_result')
- output = u_boot_console.run_command('echo $?')
- assert 'Error: 256' in read_res
- u_boot_console.run_command('tpm2 init')
- read_res = u_boot_console.run_command('tpm2 get_test_result')
- output = u_boot_console.run_command('echo $?')
- assert 'Error: 256' in read_res
- u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR')
- read_res = u_boot_console.run_command('tpm2 get_test_result')
- output = u_boot_console.run_command('echo $?')
- """assert output.endswith('0')"""
- assert 'Test Result:\r\r\n0x00000153 TPM2_RC_NEEDS_TEST' in read_res
- """ Assert console output. Test Result Data can not be tested, as it is vendor specific"""
- assert 'Test Result Data of Self Test:\r\r\n0x' in read_res
- u_boot_console.run_command('tpm2 self_test full') read_res = u_boot_console.run_command('tpm2 get_test_result') output = u_boot_console.run_command('echo $?')
-- 2.34.1
Regards, Simon

Hi Julia,
On Wed, Aug 02, 2023 at 10:35:19AM +0200, Julia Daxenberger wrote:
Add TPM2_GetTestResult command support and change the command file and the help accordingly. Add Python tests and sandbox driver functionality.
Currently, the tpm2_self_test function is implemented and can be called directly. The get_test_result function enables interested parties to retrieve the test result provided by the self test.
Some of the TPM tests fail with these patches applied [0]. Can you please take a look and fix those?
[0] https://source.denx.de/u-boot/custodians/u-boot-tpm/-/pipelines/17277
Thanks /Ilias
Changes in v2:
- Apply style changes suggested by Ilias Apalodimas
- Move test cases reliant on restart_uboot() to seperate patch in
response to Simon Glass sjg@chromium.org
- Change test result data to be read directly from the response
- Fix minor style issues
Julia Daxenberger (2): tpm: Add TPM2_GetTestResult command support tpm: Add error scenarios to get_test_result test
cmd/tpm-v2.c | 60 +++++++++++++++++++++ drivers/tpm/tpm2_tis_sandbox.c | 51 +++++++++++++++++- include/tpm-v2.h | 23 ++++++++ lib/tpm-v2.c | 97 ++++++++++++++++++++++++++++++++++ test/py/tests/test_tpm2.py | 50 ++++++++++++++++ 5 files changed, 280 insertions(+), 1 deletion(-)
participants (3)
-
Ilias Apalodimas
-
Julia Daxenberger
-
Simon Glass