| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Permission is hereby granted, free of charge, to any person |
| * obtaining a copy of this software and associated documentation |
| * files (the "Software"), to deal in the Software without |
| * restriction, including without limitation the rights to use, copy, |
| * modify, merge, publish, distribute, sublicense, and/or sell copies |
| * of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <assert.h> |
| #include <interface/hwbcc/hwbcc.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <test-runner-arch.h> |
| #include <trusty/hwbcc.h> |
| #include <trusty/keymaster.h> |
| #include <trusty/rpmb.h> |
| #include <trusty/trusty_dev.h> |
| #include <trusty/trusty_ipc.h> |
| #include <utils.h> |
| #include <virtio-console.h> |
| #include <virtio-rpmb.h> |
| |
| enum test_message_header { |
| TEST_PASSED = 0, |
| TEST_FAILED = 1, |
| TEST_MESSAGE = 2, |
| }; |
| |
| bool starts_with(const char* str1, const char* str2, size_t str2_len) { |
| for (size_t i = 0; i < str2_len; i++) { |
| if (str1[i] == '\0') { |
| return true; |
| } |
| if (str1[i] != str2[i]) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| /* |
| * Any return from this function indicates an internal error. The caller is |
| * responsible for reporting the error. It currently returns to the host with |
| * 2 as the exit code. |
| */ |
| void boot(int cpu) { |
| int ret; |
| int chan; |
| int status; |
| char cmdline[256]; |
| size_t cmdline_len; |
| const char* port; |
| const char boottest_cmd[] = "boottest "; |
| char test_result[256]; |
| struct trusty_ipc_iovec iovec = { |
| .base = test_result, |
| .len = sizeof(test_result), |
| }; |
| static struct trusty_dev trusty_dev; |
| struct trusty_ipc_dev* ipc_dev; |
| struct trusty_ipc_chan test_chan; |
| struct virtio_console* console; |
| |
| if (cpu) { |
| while (true) { |
| ret = trusty_dev_nop(&trusty_dev); |
| if (ret >= 0) { |
| trusty_idle(&trusty_dev, ret); |
| } else { |
| abort_msg("Secondary cpu unexpected error code\n"); |
| } |
| } |
| } |
| |
| /* |
| * Initialize VirtIO console device, it contains rpmb0 port for VirtIO |
| * RPMB and testrunner0 port to log message and pass test result to host |
| */ |
| console = init_virtio_console(); |
| if (NULL == console) { |
| return; |
| } |
| |
| ret = init_log(console); |
| if (ret != 0) { |
| return; |
| } |
| |
| /* |
| * Read test arguments from host (port name of test server to connect to) |
| */ |
| cmdline_len = host_get_cmdline(cmdline, sizeof(cmdline)); |
| if (!starts_with(boottest_cmd, cmdline, cmdline_len)) { |
| /* No test was requested, boot next operating system */ |
| boot_next(); |
| return; |
| } |
| |
| port = cmdline + sizeof(boottest_cmd) - 1; |
| |
| /* Init Trusty device */ |
| ret = trusty_dev_init(&trusty_dev, NULL); |
| if (ret != 0) { |
| return; |
| } |
| |
| /* Create Trusty IPC device */ |
| ret = trusty_ipc_dev_create(&ipc_dev, &trusty_dev, PAGE_SIZE); |
| if (ret != 0) { |
| return; |
| } |
| |
| /* If we don't have a VirtIO RPMB device, skip storage proxy */ |
| if (!init_virtio_rpmb(console)) { |
| if (rpmb_storage_proxy_init(ipc_dev, NULL)) { |
| log_msg("Failed to initialize storage proxy\n"); |
| return; |
| } |
| } else { |
| log_msg("Could not find serial port rpmb0, skipping storage proxy.\n"); |
| } |
| |
| /* |
| * Check that keymaster can at least be connected to. |
| * TODO: Use in full boot path with typical calls. |
| */ |
| ret = km_tipc_init(ipc_dev); |
| if (ret != 0) { |
| log_msg("km_tipc_init failed\n"); |
| return; |
| } |
| ret = trusty_set_boot_params(0, 0, KM_VERIFIED_BOOT_UNVERIFIED, false, NULL, |
| 0, NULL, 0); |
| if (ret != 0) { |
| log_msg("trusty_set_boot_params failed\n"); |
| return; |
| } |
| km_tipc_shutdown(); |
| |
| /** |
| * Check that HWBCC can be connected to. |
| */ |
| ret = hwbcc_tipc_init(ipc_dev); |
| if (ret != 0) { |
| log_msg("hwbcc_tipc_init failed.\n"); |
| return; |
| } |
| uint8_t dice_artifacts[HWBCC_MAX_RESP_PAYLOAD_SIZE]; |
| size_t resp_payload_size = 0; |
| ret = hwbcc_get_dice_artifacts(0, dice_artifacts, sizeof(dice_artifacts), |
| &resp_payload_size); |
| if (ret != 0) { |
| log_msg("hwbcc_get_dice_artifacts failed.\n"); |
| } |
| |
| /** |
| * dice_artifacts expects the following CBOR encoded structure. |
| * Since the implementation of hwbcc_get_dice_artifacts serves only the |
| * non-secure world, Bcc is not present in the returned dice_artifacts. |
| * We calculate the expected size, including CBOR header sizes. |
| * BccHandover = { |
| * 1 : bstr .size 32, // CDI_Attest |
| * 2 : bstr .size 32, // CDI_Seal |
| * ? 3 : Bcc, // Cert_Chain |
| * } |
| * Bcc = [ |
| * PubKeyEd25519, // UDS (Unique Device Secret) |
| * + BccEntry, // Root -> leaf |
| * ] |
| */ |
| size_t DICE_CDI_SIZE = 32; |
| size_t bcc_handover_size = 2 * DICE_CDI_SIZE + 7 /*CBOR tags*/; |
| |
| if (resp_payload_size != bcc_handover_size) { |
| log_msg("hwbcc_get_dice_artifacts failed with incorrect response size.\n"); |
| } |
| |
| /** |
| * Note: In ABL, `hwbcc_ns_deprivilege` needs to be called |
| * after retrieving dice artifacts. |
| */ |
| ret = hwbcc_ns_deprivilege(); |
| |
| if (ret != 0) { |
| log_msg("hwbcc_ns_deprivilege failed.\n"); |
| } |
| |
| /* Close the firt connection and try to connect again, which should fail due |
| * to the deprivilege call. */ |
| hwbcc_tipc_shutdown(); |
| |
| ret = hwbcc_tipc_init(ipc_dev); |
| if (ret != 0) { |
| log_msg("hwbcc_tipc_init failed.\n"); |
| return; |
| } |
| memset(dice_artifacts, 0, sizeof(dice_artifacts)); |
| resp_payload_size = 0; |
| ret = hwbcc_get_dice_artifacts(0, dice_artifacts, sizeof(dice_artifacts), |
| &resp_payload_size); |
| |
| if (ret == 0) { |
| log_msg("hwbcc_ns_deprivilege is broken.\n"); |
| } |
| |
| hwbcc_tipc_shutdown(); |
| |
| ret = arch_start_secondary_cpus(); |
| if (ret) { |
| log_msg("Failed to start secondary CPUs\n"); |
| return; |
| } |
| |
| /* Create connection to test server */ |
| trusty_ipc_chan_init(&test_chan, ipc_dev); |
| chan = trusty_ipc_connect(&test_chan, port, true); |
| if (chan < 0) { |
| log_msg("Failed to connect to test server\n"); |
| return; |
| } |
| |
| /* Wait for tests to complete and read status */ |
| while (true) { |
| ret = trusty_ipc_recv(&test_chan, &iovec, 1, /* wait = */ true); |
| if (ret <= 0 || ret >= (int)sizeof(test_result)) { |
| return; |
| } |
| |
| if (test_result[0] == TEST_PASSED) { |
| break; |
| } else if (test_result[0] == TEST_FAILED) { |
| break; |
| } else if (test_result[0] == TEST_MESSAGE) { |
| log_buf(test_result + 1, ret - 1); |
| } else { |
| return; |
| } |
| } |
| status = test_result[0] != TEST_PASSED; |
| |
| /* Request another read to wait for the sever to close the connection */ |
| ret = trusty_ipc_recv(&test_chan, &iovec, 1, /* wait = */ true); |
| if (ret != TRUSTY_ERR_CHANNEL_CLOSED) { |
| return; |
| } |
| |
| /* Return test status to host, 0: test success, 1: test failed */ |
| host_exit(status); |
| } |