diff --git a/protocol/host_cmd.c b/protocol/host_cmd.c index 1c38d41b..f3124ef7 100644 --- a/protocol/host_cmd.c +++ b/protocol/host_cmd.c @@ -169,58 +169,71 @@ libhoth_error libhoth_hostcmd_exec(struct libhoth_device* dev, uint16_t command, size_t req_payload_size, void* resp_buf, size_t resp_buf_size, size_t* out_resp_size) { - struct { - struct hoth_host_request hdr; - uint8_t - payload_buf[LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request)]; - } req; - if (req_payload_size > sizeof(req.payload_buf)) { - fprintf(stderr, "req_payload_size too large: %d > %d\n", - (int)req_payload_size, (int)sizeof(req.payload_buf)); + union { + struct { + struct hoth_host_request hdr; + uint8_t payload_buf[LIBHOTH_MAX_MAILBOX_SIZE - + sizeof(struct hoth_host_request)]; + } req; + struct { + struct hoth_host_response hdr; + uint8_t payload_buf[LIBHOTH_MAX_MAILBOX_SIZE - + sizeof(struct hoth_host_response)]; + } resp; + } buf; + if (req_payload_size > sizeof(buf.req.payload_buf)) { + fprintf(stderr, "req_payload_size too large: %zu > %zu\n", req_payload_size, + sizeof(buf.req.payload_buf)); + return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_OUT_UNDERFLOW); + } + if (resp_buf_size > sizeof(buf.resp.payload_buf)) { + fprintf(stderr, "resp_buf_size too large: %zu > %zu\n", resp_buf_size, + sizeof(buf.resp.payload_buf)); return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, LIBHOTH_ERR_OUT_UNDERFLOW); } if (req_payload) { - memcpy(req.payload_buf, req_payload, req_payload_size); + memcpy(buf.req.payload_buf, req_payload, req_payload_size); } libhoth_status status = populate_ec_request_header( - command, version, req.payload_buf, req_payload_size, &req.hdr); + command, version, buf.req.payload_buf, req_payload_size, &buf.req.hdr); if (status != LIBHOTH_OK) { fprintf(stderr, "populate_ec_request_header() failed: %d\n", status); return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, status); } - libhoth_error err = - libhoth_send_request(dev, &req, sizeof(req.hdr) + req_payload_size); + libhoth_error err = libhoth_send_request( + dev, &buf.req, sizeof(buf.req.hdr) + req_payload_size); if (err != HOTH_SUCCESS) { fprintf(stderr, "libhoth_send_request() failed: 0x%016llx\n", (unsigned long long)err); return err; } - struct { - struct hoth_host_response hdr; - uint8_t - payload_buf[LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_response)]; - } resp; + size_t max_resp_size = sizeof(struct hoth_host_response) + resp_buf_size; + if (max_resp_size < LIBHOTH_MAILBOX_SIZE) { + max_resp_size = LIBHOTH_MAILBOX_SIZE; + } size_t resp_size = 0; - err = libhoth_receive_response(dev, &resp, sizeof(resp), &resp_size, + err = libhoth_receive_response(dev, &buf.resp, max_resp_size, &resp_size, HOTH_CMD_TIMEOUT_MS_DEFAULT); if (err != HOTH_SUCCESS) { fprintf(stderr, "libhoth_receive_response() failed: 0x%016llx\n", (unsigned long long)err); return err; } - status = validate_ec_response_header(&resp.hdr, resp.payload_buf, resp_size); + status = validate_ec_response_header(&buf.resp.hdr, buf.resp.payload_buf, + resp_size); if (status != LIBHOTH_OK) { fprintf(stderr, "EC response header invalid: %d\n", status); return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, status); } - if (resp.hdr.result != HOTH_RES_SUCCESS) { - fprintf(stderr, "EC response contained error: %d", resp.hdr.result); - if (resp.hdr.data_len >= 4) { + if (buf.resp.hdr.result != HOTH_RES_SUCCESS) { + fprintf(stderr, "EC response contained error: %d", buf.resp.hdr.result); + if (buf.resp.hdr.data_len >= 4) { uint32_t error_code; - memcpy(&error_code, resp.payload_buf, sizeof(error_code)); + memcpy(&error_code, buf.resp.payload_buf, sizeof(error_code)); fprintf(stderr, " (extended: 0x%08x)\n", error_code); return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_PIEROT_ERR, error_code); @@ -228,7 +241,7 @@ libhoth_error libhoth_hostcmd_exec(struct libhoth_device* dev, uint16_t command, fprintf(stderr, "\n"); } return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_EC, - resp.hdr.result); + buf.resp.hdr.result); } size_t resp_payload_size = resp_size - sizeof(struct hoth_host_response); @@ -251,7 +264,7 @@ libhoth_error libhoth_hostcmd_exec(struct libhoth_device* dev, uint16_t command, } } if (resp_buf) { - memcpy(resp_buf, resp.payload_buf, resp_payload_size); + memcpy(resp_buf, buf.resp.payload_buf, resp_payload_size); } if (out_resp_size) { *out_resp_size = resp_payload_size; diff --git a/protocol/host_cmd_test.cc b/protocol/host_cmd_test.cc index 0fb65da3..9a2a93d1 100644 --- a/protocol/host_cmd_test.cc +++ b/protocol/host_cmd_test.cc @@ -17,7 +17,8 @@ #include #include -#include "protocol/host_cmd.h" +#include + #include "test/libhoth_device_mock.h" #include "transports/libhoth_device.h" @@ -72,3 +73,45 @@ TEST_F(LibHothTest, response_failure_extended) { EXPECT_EQ(LIBHOTH_ERR_GET_SPACE(err), HOTH_HOST_SPACE_PIEROT_ERR); EXPECT_EQ(LIBHOTH_ERR_GET_CODE(err), 0x89c70005); } + +TEST_F(LibHothTest, large_request_accepted_up_to_max_mailbox) { + std::vector payload(4096); + for (size_t i = 0; i < payload.size(); i++) { + payload[i] = static_cast(i); + } + + size_t sent_size = 0; + std::vector sent_payload; + EXPECT_CALL(mock_, send(_, UsesCommand(kCmd), _)) + .WillOnce([&](struct libhoth_device*, const void* request, + size_t request_size) { + const uint8_t* bytes = static_cast(request); + sent_size = request_size; + sent_payload.assign(bytes + sizeof(struct hoth_host_request), + bytes + request_size); + return LIBHOTH_OK; + }); + const uint8_t empty_resp = 0; + EXPECT_CALL(mock_, receive) + .WillOnce(DoAll(CopyResp(&empty_resp, 0), Return(LIBHOTH_OK))); + + EXPECT_EQ(libhoth_hostcmd_exec(&hoth_dev_, kCmd, 0, payload.data(), + payload.size(), nullptr, 0, nullptr), + HOTH_SUCCESS); + + EXPECT_EQ(sent_size, sizeof(struct hoth_host_request) + payload.size()); + EXPECT_EQ(sent_payload, payload); +} + +TEST_F(LibHothTest, oversized_request_rejected_above_max_mailbox) { + EXPECT_CALL(mock_, send).Times(0); + + const size_t max_payload = + LIBHOTH_MAX_MAILBOX_SIZE - sizeof(struct hoth_host_request); + std::vector payload(max_payload + 1, 0); + libhoth_error err = libhoth_hostcmd_exec(&hoth_dev_, kCmd, 0, payload.data(), + payload.size(), nullptr, 0, nullptr); + + EXPECT_NE(err, HOTH_SUCCESS); + EXPECT_EQ(LIBHOTH_ERR_GET_CODE(err), LIBHOTH_ERR_OUT_UNDERFLOW); +} diff --git a/protocol/key_rotation.c b/protocol/key_rotation.c index df53561e..b3cfc74f 100644 --- a/protocol/key_rotation.c +++ b/protocol/key_rotation.c @@ -173,12 +173,52 @@ libhoth_error libhoth_key_rotation_payload_status( return HOTH_SUCCESS; } +static libhoth_error key_rotation_write_commit(struct libhoth_device* dev, + const uint8_t* record, + size_t size) { + if (size < STRUCT_CHUNK_SIZE || (size % 4) != 0 || + size > KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE) { + fprintf(stderr, + "Invalid key rotation record size: %zu (must be 4-byte aligned in " + "[%d, %zu]).\n", + size, STRUCT_CHUNK_SIZE, + (size_t)KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE); + return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER); + } + + struct { + struct hoth_request_key_rotation_record hdr; + uint8_t data[KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE]; + } __hoth_align4 request; + + request.hdr.operation = KEY_ROTATION_RECORD_WRITE_COMMIT; + request.hdr.packet_offset = 0; + request.hdr.packet_size = (uint16_t)size; + request.hdr.reserved = 0; + memcpy(request.data, record, size); + + fprintf(stderr, "Writing and committing the key rotation record.\n"); + libhoth_error ret = libhoth_hostcmd_exec( + dev, HOTH_CMD_BOARD_SPECIFIC_BASE + HOTH_PRV_CMD_HAVEN_KEY_ROTATION_OP, 0, + &request, sizeof(request.hdr) + size, NULL, 0, NULL); + if (ret != HOTH_SUCCESS) { + fprintf(stderr, "HOTH_KEY_ROTATION_WRITE_COMMIT error code: 0x%016llx\n", + (unsigned long long)ret); + return ret; + } + return HOTH_SUCCESS; +} + libhoth_error libhoth_key_rotation_update(struct libhoth_device* dev, const uint8_t* image, size_t size) { if (dev == NULL || image == NULL) { return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, LIBHOTH_ERR_INVALID_PARAMETER); } + if (size >= 4 && memcmp(image, KEY_ROTATION_SIGNED_RECORD_MAGIC, 4) == 0) { + return key_rotation_write_commit(dev, image, size); + } if (size <= KEY_ROTATION_RECORD_SIGNATURE_SIZE || size > KEY_ROTATION_FLASH_AREA_SIZE) { fprintf(stderr, "Data chunk size invalid.\n"); diff --git a/protocol/key_rotation.h b/protocol/key_rotation.h index 63a5a6e1..31381197 100644 --- a/protocol/key_rotation.h +++ b/protocol/key_rotation.h @@ -35,6 +35,14 @@ extern "C" { #define KEY_ROTATION_RECORD_WRITE_MAX_SIZE \ (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ sizeof(struct hoth_request_key_rotation_record)) +// Maximum signed key rotation record container ("KRSD") size accepted over the +// 6 KiB OpenTitan mailbox. Note that after stripping the hybrid signature +// container, the inner sealed descriptor stored in OpenTitan flash must fit in +// KEY_ROTATION_FLASH_AREA_SIZE - 56 = 1,992 bytes. +#define KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE \ + (LIBHOTH_MAX_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_request_key_rotation_record)) +#define KEY_ROTATION_SIGNED_RECORD_MAGIC "KRSD" #define KEY_ROTATION_RECORD_READ_MAX_SIZE \ (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_response) - \ sizeof(struct hoth_request_key_rotation_record) - \ @@ -78,6 +86,8 @@ enum key_rotation_record_op { // allows KEY_ROTATION_RECORD_SET_MAUV = 10, // Set Key Rotation Record MAUV KEY_ROTATION_RECORD_GET_MAUV = 11, // Get Key Rotation Record MAUV + KEY_ROTATION_RECORD_WRITE_COMMIT = 12, // Write, validate and commit a whole + // record. OpenTitan only. }; #define KEY_ROTATION_CHUNK_TYPE_CODE_PKEY (0x59454B50) diff --git a/protocol/key_rotation_test.cc b/protocol/key_rotation_test.cc index b4d0dd96..7950d41f 100644 --- a/protocol/key_rotation_test.cc +++ b/protocol/key_rotation_test.cc @@ -21,6 +21,7 @@ #include #include #include +#include #include "protocol/host_cmd.h" #include "protocol/status.h" @@ -353,7 +354,7 @@ TEST_F(LibHothTest, key_rotation_update_failure) { } TEST_F(LibHothTest, key_rotation_update_initiate_failure) { - uint8_t data[KEY_ROTATION_RECORD_WRITE_MAX_SIZE]; + uint8_t data[KEY_ROTATION_RECORD_WRITE_MAX_SIZE] = {0}; EXPECT_CALL(mock_, send(_, UsesCommand(kCmd), _)) .WillRepeatedly(Return(LIBHOTH_OK)); @@ -600,3 +601,82 @@ TEST_F(LibHothTest, key_rotation_get_mauv_failure_invalid_response_size) { LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_EC, HOTH_RES_INVALID_RESPONSE_SIZE)); } + +TEST_F(LibHothTest, update_krsd_sends_whole_record_in_one_write_commit) { + std::vector record(4096); + for (size_t i = 0; i < record.size(); i++) { + record[i] = static_cast(i * 7); + } + std::memcpy(record.data(), KEY_ROTATION_SIGNED_RECORD_MAGIC, 4); + + struct hoth_request_key_rotation_record hdr = {}; + std::vector sent_record; + EXPECT_CALL(mock_, send(_, UsesCommand(kCmd), _)) + .Times(1) + .WillOnce([&](struct libhoth_device*, const void* request, + size_t request_size) { + const uint8_t* payload = static_cast(request) + + sizeof(struct hoth_host_request); + const size_t payload_size = + request_size - sizeof(struct hoth_host_request); + std::memcpy(&hdr, payload, sizeof(hdr)); + sent_record.assign(payload + sizeof(hdr), payload + payload_size); + return LIBHOTH_OK; + }); + EXPECT_CALL(mock_, receive) + .Times(1) + .WillOnce(DoAll(CopyResp(record.data(), 0), Return(LIBHOTH_OK))); + + EXPECT_EQ( + libhoth_key_rotation_update(&hoth_dev_, record.data(), record.size()), + HOTH_SUCCESS); + + EXPECT_EQ(hdr.operation, KEY_ROTATION_RECORD_WRITE_COMMIT); + EXPECT_EQ(hdr.packet_offset, 0); + EXPECT_EQ(hdr.packet_size, record.size()); + EXPECT_EQ(sent_record, record); +} + +TEST_F(LibHothTest, update_krsd_accepts_record_at_max_capacity) { + std::vector record(KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE, 0xa5); + std::memcpy(record.data(), KEY_ROTATION_SIGNED_RECORD_MAGIC, 4); + + EXPECT_CALL(mock_, send(_, UsesCommand(kCmd), _)) + .WillOnce(Return(LIBHOTH_OK)); + EXPECT_CALL(mock_, receive) + .WillOnce(DoAll(CopyResp(record.data(), 0), Return(LIBHOTH_OK))); + + EXPECT_EQ( + libhoth_key_rotation_update(&hoth_dev_, record.data(), record.size()), + HOTH_SUCCESS); +} + +TEST_F(LibHothTest, update_krsd_rejects_invalid_sizes_and_alignment) { + EXPECT_CALL(mock_, send).Times(0); + + const libhoth_error expected = + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER); + + std::vector record(KEY_ROTATION_RECORD_WRITE_COMMIT_MAX_SIZE + 4, 0); + std::memcpy(record.data(), KEY_ROTATION_SIGNED_RECORD_MAGIC, 4); + EXPECT_EQ( + libhoth_key_rotation_update(&hoth_dev_, record.data(), record.size()), + expected); + EXPECT_EQ(libhoth_key_rotation_update(&hoth_dev_, record.data(), 4), + expected); + EXPECT_EQ(libhoth_key_rotation_update(&hoth_dev_, record.data(), 10), + expected); +} + +TEST_F(LibHothTest, update_krsd_reports_device_failure) { + uint8_t record[8] = {0}; + std::memcpy(record, KEY_ROTATION_SIGNED_RECORD_MAGIC, 4); + const libhoth_error dev_err = LIBHOTH_ERR_CONSTRUCT( + HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, LIBHOTH_ERR_FAIL); + + EXPECT_CALL(mock_, send(_, UsesCommand(kCmd), _)).WillOnce(Return(dev_err)); + + EXPECT_EQ(libhoth_key_rotation_update(&hoth_dev_, record, sizeof(record)), + dev_err); +} diff --git a/protocol/test/libhoth_device_mock.h b/protocol/test/libhoth_device_mock.h index 45ac4e50..efdbf1d8 100644 --- a/protocol/test/libhoth_device_mock.h +++ b/protocol/test/libhoth_device_mock.h @@ -72,8 +72,8 @@ ACTION_P(CopyResp, response, resp_size) { struct { struct hoth_host_response hdr; - uint8_t - payload_buf[LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_response)]; + uint8_t payload_buf[LIBHOTH_MAX_MAILBOX_SIZE - + sizeof(struct hoth_host_response)]; } resp; ASSERT_LE(full_resp_size, sizeof(resp)); diff --git a/tools/htool/htool_raw_host_command.c b/tools/htool/htool_raw_host_command.c index 3258a32b..6a3d025c 100644 --- a/tools/htool/htool_raw_host_command.c +++ b/tools/htool/htool_raw_host_command.c @@ -94,7 +94,7 @@ int command_raw_host_command(const struct htool_invocation* inv) { return 0; } - uint8_t req_payload[LIBHOTH_MAILBOX_SIZE - + uint8_t req_payload[LIBHOTH_MAX_MAILBOX_SIZE - sizeof(struct hoth_host_request)] = {0}; if (req.data_len > sizeof(req_payload)) { diff --git a/transports/BUILD b/transports/BUILD index 1e583210..640ed350 100644 --- a/transports/BUILD +++ b/transports/BUILD @@ -1,4 +1,5 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") package(default_visibility = ["//visibility:public"]) @@ -77,6 +78,35 @@ cc_library( ], ) +cc_test( + name = "libhoth_usb_fifo_test", + srcs = ["libhoth_usb_fifo_test.cc"], + deps = [ + ":libhoth_device", + ":libhoth_usb", + ":libhoth_usb_device", + "//protocol:libhoth_status", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@libusb", + ], +) + +cc_test( + name = "libhoth_spi_test", + srcs = ["libhoth_spi_test.cc"], + linkopts = ["-Wl,--wrap=ioctl"], + linkstatic = True, + deps = [ + ":libhoth_device", + ":libhoth_ec", + ":libhoth_spi", + "//protocol:libhoth_status", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + filegroup( name = "headers", srcs = glob(["*.h"]), diff --git a/transports/libhoth_device.h b/transports/libhoth_device.h index bb58ea14..c147a3c5 100644 --- a/transports/libhoth_device.h +++ b/transports/libhoth_device.h @@ -24,7 +24,10 @@ extern "C" { #endif +// Legacy host command buffer size, used for chunked protocols. #define LIBHOTH_MAILBOX_SIZE 1024 +// Maximum host command buffer size supported by any target (e.g. OpenTitan). +#define LIBHOTH_MAX_MAILBOX_SIZE 6144 #define LIBHOTH_REBOOT_DELAY_MS 1000 struct libhoth_device { diff --git a/transports/libhoth_spi.c b/transports/libhoth_spi.c index ac3c535a..01b48221 100644 --- a/transports/libhoth_spi.c +++ b/transports/libhoth_spi.c @@ -373,7 +373,8 @@ libhoth_error libhoth_spi_open( libhoth_error libhoth_spi_send_request(struct libhoth_device* dev, const void* request, size_t request_size) { - if (dev == NULL) { + if (dev == NULL || dev->user_ctx == NULL || request == NULL || + request_size == 0 || request_size > LIBHOTH_MAX_MAILBOX_SIZE) { return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); } @@ -391,7 +392,8 @@ libhoth_error libhoth_spi_receive_response(struct libhoth_device* dev, size_t max_response_size, size_t* actual_size, int timeout_ms) { - if (dev == NULL) { + (void)timeout_ms; + if (dev == NULL || dev->user_ctx == NULL || response == NULL) { return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); } @@ -419,7 +421,8 @@ libhoth_error libhoth_spi_receive_response(struct libhoth_device* dev, *actual_size = total_bytes; } - if (max_response_size < (total_bytes + host_response.data_len)) { + if (max_response_size < (total_bytes + host_response.data_len) || + LIBHOTH_MAILBOX_SIZE < (total_bytes + host_response.data_len)) { return spi_err_libhoth(LIBHOTH_ERR_RESPONSE_BUFFER_OVERFLOW); } @@ -444,7 +447,8 @@ libhoth_error libhoth_spi_receive_response(struct libhoth_device* dev, libhoth_error libhoth_spi_buffer_request(struct libhoth_device* dev, const void* request, size_t request_size) { - if (dev == NULL) { + if (dev == NULL || dev->user_ctx == NULL || request == NULL || + request_size == 0 || request_size > SPI_NOR_FLASH_PAGE_SIZE) { return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); } @@ -470,11 +474,8 @@ libhoth_error libhoth_spi_send_and_receive_response(struct libhoth_device* dev, size_t max_response_size, size_t* actual_size, int timeout_ms) { - if (dev == NULL) { - return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); - } - - if (max_response_size < sizeof(struct hoth_host_response)) { + (void)timeout_ms; + if (dev == NULL || dev->user_ctx == NULL) { return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); } @@ -485,6 +486,16 @@ libhoth_error libhoth_spi_send_and_receive_response(struct libhoth_device* dev, return spi_err_libhoth(LIBHOTH_ERR_INTERFACE_BUSY); } + if (response == NULL || + max_response_size < sizeof(struct hoth_host_response) || + spi_dev->buffered_request_size == 0 || + spi_dev->buffered_request_size > SPI_NOR_FLASH_PAGE_SIZE) { + free(spi_dev->buffered_request); + spi_dev->buffered_request = NULL; + spi_dev->buffered_request_size = 0; + return spi_err_libhoth(LIBHOTH_ERR_INVALID_PARAMETER); + } + uint32_t address = spi_dev->mailbox_address; bool address_mode_4b = spi_dev->address_mode_4b; @@ -530,10 +541,11 @@ libhoth_error libhoth_spi_send_and_receive_response(struct libhoth_device* dev, .tx_nbits = SPI_NBITS_SINGLE, }; - // Read entire expected response buffer + // Read expected response buffer, bounded by the 1 KiB hardware SPI mailbox. + const size_t rx_len = MIN(max_response_size, (size_t)LIBHOTH_MAILBOX_SIZE); xfer[4] = (struct spi_ioc_transfer){ .rx_buf = (unsigned long)response, - .len = max_response_size, + .len = rx_len, .rx_nbits = SPI_NBITS_SINGLE, }; @@ -545,7 +557,7 @@ libhoth_error libhoth_spi_send_and_receive_response(struct libhoth_device* dev, struct hoth_host_response* host_response = (struct hoth_host_response*)response; if (host_response->data_len > - (max_response_size - sizeof(struct hoth_host_response))) { + (rx_len - sizeof(struct hoth_host_response))) { rc = spi_err_libhoth(LIBHOTH_ERR_RESPONSE_BUFFER_OVERFLOW); } else if (actual_size) { *actual_size = @@ -608,7 +620,11 @@ libhoth_error libhoth_spi_close(struct libhoth_device* dev) { struct libhoth_spi_device* spi_dev = (struct libhoth_spi_device*)dev->user_ctx; - close(spi_dev->fd); - free(dev->user_ctx); + if (spi_dev != NULL) { + free(spi_dev->buffered_request); + close(spi_dev->fd); + free(spi_dev); + dev->user_ctx = NULL; + } return HOTH_SUCCESS; } diff --git a/transports/libhoth_spi_test.cc b/transports/libhoth_spi_test.cc new file mode 100644 index 00000000..fcd12cfb --- /dev/null +++ b/transports/libhoth_spi_test.cc @@ -0,0 +1,170 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "transports/libhoth_spi.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "protocol/status.h" +#include "transports/libhoth_device.h" +#include "transports/libhoth_ec.h" + +namespace { + +struct CapturedSpiIoctl { + int call_count = 0; + unsigned long last_request = 0; + std::vector xfers; + std::vector> tx_payloads; + struct hoth_host_response canned_response = { + .struct_version = 3, + .checksum = 0xfd, + .result = 0, + .data_len = 0, + .reserved = 0, + }; +}; + +CapturedSpiIoctl* g_captured = nullptr; + +extern "C" int __wrap_ioctl(int fd, unsigned long request, ...) { + (void)fd; + va_list ap; + va_start(ap, request); + auto* xfers = va_arg(ap, struct spi_ioc_transfer*); + va_end(ap); + + if (!g_captured) { + return -1; + } + g_captured->call_count++; + g_captured->last_request = request; + + const size_t msg_bytes = _IOC_SIZE(request); + const size_t num_xfers = msg_bytes / sizeof(struct spi_ioc_transfer); + g_captured->xfers.assign(xfers, xfers + num_xfers); + g_captured->tx_payloads.clear(); + for (size_t i = 0; i < num_xfers; ++i) { + if (xfers[i].tx_buf != 0) { + const auto* ptr = reinterpret_cast(xfers[i].tx_buf); + g_captured->tx_payloads.emplace_back(ptr, ptr + xfers[i].len); + } else { + g_captured->tx_payloads.emplace_back(); + } + if (xfers[i].rx_buf != 0) { + if (xfers[i].len >= sizeof(struct hoth_host_response)) { + std::memcpy(reinterpret_cast(xfers[i].rx_buf), + &g_captured->canned_response, + sizeof(g_captured->canned_response)); + } else { + std::memset(reinterpret_cast(xfers[i].rx_buf), 0, xfers[i].len); + } + } + } + return 0; +} + +class LibHothSpiAtomicTest : public ::testing::Test { + protected: + void SetUp() override { + g_captured = &captured_; + spi_dev_.fd = 42; + spi_dev_.mailbox_address = 0x00800000; + spi_dev_.address_mode_4b = true; + dev_.user_ctx = &spi_dev_; + } + + void TearDown() override { g_captured = nullptr; } + + CapturedSpiIoctl captured_; + struct libhoth_spi_device spi_dev_{}; + struct libhoth_device dev_{}; +}; + +TEST_F(LibHothSpiAtomicTest, + NonAtomicSendRequestSplitsMultiPageAndPollsBusyWait) { + spi_dev_.device_busy_wait_timeout = 10000; + spi_dev_.device_busy_wait_check_interval = 10; + + // 4,072 bytes spans 15 full 256B pages + 1 partial 232B page = 16 pages. + std::vector req(4072, 0x5a); + ASSERT_EQ(libhoth_spi_send_request(&dev_, req.data(), req.size()), + HOTH_SUCCESS); + // 16 pages * (1 WRITE_ENABLE + 1 PAGE_PROGRAM + 1 READ_STATUS) = 48 ioctls. + EXPECT_EQ(captured_.call_count, 48); + + std::vector oversized(LIBHOTH_MAX_MAILBOX_SIZE + 1, 0x5a); + EXPECT_EQ(libhoth_spi_send_request(&dev_, oversized.data(), oversized.size()), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_SPI, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER)); +} + +TEST_F(LibHothSpiAtomicTest, NonAtomicReceiveRejectsResponseAbove1KiBMailbox) { + captured_.canned_response.data_len = + LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_response) + 1; + + std::vector resp_buf(LIBHOTH_MAX_MAILBOX_SIZE, 0); + size_t actual_size = 0; + EXPECT_EQ(libhoth_spi_receive_response(&dev_, resp_buf.data(), + resp_buf.size(), &actual_size, 1000), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_SPI, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_RESPONSE_BUFFER_OVERFLOW)); +} + +TEST_F(LibHothSpiAtomicTest, AtomicSinglePageSendAndReceiveClampsRxTo1KiB) { + std::vector req(SPI_NOR_FLASH_PAGE_SIZE, 0x3c); + ASSERT_EQ(libhoth_spi_buffer_request(&dev_, req.data(), req.size()), + HOTH_SUCCESS); + + std::vector resp_buf(LIBHOTH_MAX_MAILBOX_SIZE, 0); + size_t actual_size = 0; + ASSERT_EQ(libhoth_spi_send_and_receive_response( + &dev_, resp_buf.data(), resp_buf.size(), &actual_size, 1000), + HOTH_SUCCESS); + EXPECT_EQ(actual_size, sizeof(struct hoth_host_response)); + EXPECT_EQ(captured_.call_count, 1); + ASSERT_EQ(captured_.xfers.size(), 5u); + EXPECT_EQ(captured_.xfers[4].len, static_cast(LIBHOTH_MAILBOX_SIZE)); + EXPECT_EQ(spi_dev_.buffered_request, nullptr); +} + +TEST_F(LibHothSpiAtomicTest, + AtomicBufferRequestRejectsMultiPageAndFreesOnInvalidReceive) { + std::vector multi_page(SPI_NOR_FLASH_PAGE_SIZE + 1, 0xaa); + EXPECT_EQ( + libhoth_spi_buffer_request(&dev_, multi_page.data(), multi_page.size()), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_SPI, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER)); + EXPECT_EQ(spi_dev_.buffered_request, nullptr); + + uint8_t small_req[8] = {0}; + ASSERT_EQ(libhoth_spi_buffer_request(&dev_, small_req, sizeof(small_req)), + HOTH_SUCCESS); + uint8_t tiny_resp[4] = {0}; + size_t actual_size = 0; + EXPECT_EQ(libhoth_spi_send_and_receive_response( + &dev_, tiny_resp, sizeof(tiny_resp), &actual_size, 1000), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_SPI, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER)); + EXPECT_EQ(spi_dev_.buffered_request, nullptr); +} + +} // namespace diff --git a/transports/libhoth_usb_fifo.c b/transports/libhoth_usb_fifo.c index 2793ad31..2d01d9d4 100644 --- a/transports/libhoth_usb_fifo.c +++ b/transports/libhoth_usb_fifo.c @@ -23,7 +23,7 @@ #include "transports/libhoth_usb_device.h" #define LIBHOTH_USB_FIFO_REQUEST_ID_SIZE 16 -#define LIBHOTH_USB_FIFO_MAX_REQUEST_SIZE 1024 +#define LIBHOTH_USB_FIFO_MAX_REQUEST_SIZE LIBHOTH_MAX_MAILBOX_SIZE #define LIBHOTH_USB_FIFO_MTU \ (LIBHOTH_USB_FIFO_REQUEST_ID_SIZE + LIBHOTH_USB_FIFO_MAX_REQUEST_SIZE) diff --git a/transports/libhoth_usb_fifo_test.cc b/transports/libhoth_usb_fifo_test.cc new file mode 100644 index 00000000..25df0295 --- /dev/null +++ b/transports/libhoth_usb_fifo_test.cc @@ -0,0 +1,101 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include + +#include "protocol/status.h" +#include "transports/libhoth_device.h" +#include "transports/libhoth_usb_device.h" + +namespace { + +constexpr size_t kRequestIdSize = 16; +constexpr size_t kFifoMtu = kRequestIdSize + LIBHOTH_MAX_MAILBOX_SIZE; + +class LibHothUsbFifoTest : public ::testing::Test { + protected: + void SetUp() override { + endpoints_[0].bEndpointAddress = LIBUSB_ENDPOINT_IN | 0x01; + endpoints_[0].bmAttributes = LIBUSB_TRANSFER_TYPE_BULK; + endpoints_[0].wMaxPacketSize = 64; + endpoints_[1].bEndpointAddress = LIBUSB_ENDPOINT_OUT | 0x02; + endpoints_[1].bmAttributes = LIBUSB_TRANSFER_TYPE_BULK; + endpoints_[1].wMaxPacketSize = 64; + + altsetting_.bNumEndpoints = 2; + altsetting_.endpoint = endpoints_; + + iface_.altsetting = &altsetting_; + iface_.num_altsetting = 1; + + config_.bNumInterfaces = 1; + config_.interface = &iface_; + + dev_.info.type = LIBHOTH_USB_INTERFACE_TYPE_FIFO; + dev_.info.interface_id = 0; + dev_.info.interface_altsetting = 0; + + ASSERT_EQ(libhoth_usb_fifo_open(&dev_, &config_, /*prng_seed=*/0x12345678), + LIBHOTH_OK); + } + + void TearDown() override { + EXPECT_EQ(libhoth_usb_fifo_close(&dev_), LIBHOTH_OK); + } + + struct libusb_endpoint_descriptor endpoints_[2]{}; + struct libusb_interface_descriptor altsetting_{}; + struct libusb_interface iface_{}; + struct libusb_config_descriptor config_{}; + struct libhoth_usb_device dev_{}; +}; + +TEST_F(LibHothUsbFifoTest, AcceptsKrrSizedRequestUpToMaxMailbox) { + std::vector req(4096); + for (size_t i = 0; i < req.size(); ++i) { + req[i] = static_cast(i * 3 + 1); + } + + EXPECT_EQ(libhoth_usb_fifo_send_request(&dev_, req.data(), req.size()), + LIBHOTH_OK); + EXPECT_EQ(static_cast(dev_.driver_data.fifo.out_transfer->length), + kRequestIdSize + req.size()); + EXPECT_NE(dev_.driver_data.fifo.out_transfer->flags & + LIBUSB_TRANSFER_ADD_ZERO_PACKET, + 0); + EXPECT_EQ(std::memcmp(dev_.driver_data.fifo.out_buffer + kRequestIdSize, + req.data(), req.size()), + 0); + + std::vector max_req(LIBHOTH_MAX_MAILBOX_SIZE, 0x5a); + EXPECT_EQ( + libhoth_usb_fifo_send_request(&dev_, max_req.data(), max_req.size()), + LIBHOTH_OK); + EXPECT_EQ(static_cast(dev_.driver_data.fifo.out_transfer->length), + kFifoMtu); +} + +TEST_F(LibHothUsbFifoTest, RejectsOversizedRequestAboveMaxMailbox) { + std::vector oversized(LIBHOTH_MAX_MAILBOX_SIZE + 1, 0); + EXPECT_EQ( + libhoth_usb_fifo_send_request(&dev_, oversized.data(), oversized.size()), + LIBUSB_ERROR_INVALID_PARAM); +} + +} // namespace