Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/Security-Model.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ interrupted write. The live per-object counter also detects replay of a stale
ciphertext unless an attacker can coherently roll back the counter store; see
[Threat Model](Threat-Model.md).

Sealed replacement stages the authenticated prior object until the new
counter mapping commits, then destroys the stage. An interrupted replacement
restores that object
without rolling back the global nonce counter. Replacing sealed data with
unsealed data uses the same transaction and retires the old seal counter on
commit. If the recovery copy is missing or invalid, the live object is kept
only if it authenticates under the committed counter; otherwise that object
is discarded and its counter retired. Other objects remain available.
The vault requires wolfHSM's
`wh_NvmFlash` backend, including its capacity and compaction callbacks, and
rejects incompatible backends at initialization. The flash HAL may be supplied
by the target port or the host RAM simulator.

Vault storage rejects its reserved key-object type. Guest cryptographic keys
instead use the separate wolfHSM keystore behind `SERVICE_HSM`, where the
relay binds operations to the caller's namespace.
Expand Down
2 changes: 1 addition & 1 deletion docs/TF-M-Compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ algorithm or feature is enabled in every build.
| wolfTrust exposes the FF-M Non-secure client API through one five-function CMSE gateway. | Implementation detail | The gateway exports framework version, service version, connect, call, and close; Secure Partition entry points are a separate manifest concern. |
| Only connection-based IPC services are enabled. | Scoped | The production manifest requests only `WT_MANIFEST_FEATURE_IPC`. SFN, stateless services, and memory-mapped I/O vectors are rejected by the runtime. |
| IPC uses fixed copied buffers. | Scoped safety bound | Calls are limited to four vectors and a 1024-byte transfer budget. Services apply smaller bounds. Applications must chunk larger data. |
| A Non-secure connect, call, or close cannot remain pending after the scheduler reaches quiescence for that dispatch. | Scoped deviation | An incomplete message becomes an error. Shipped Non-secure-facing services reply within the dispatch; Secure Partition callers use the begin/finish path when scheduling another partition is required. |
| A Non-secure connect, call, or close cannot remain pending after the scheduler reaches quiescence for that dispatch. | Scoped deviation | An incomplete message becomes a client error. If already claimed, the service retains its message until reply or partition fault; late output is discarded. A late call reply releases the message, but the connection remains in error until the client requests close. If close was already requested, the late reply allows deferred disconnection to proceed. Shipped Non-secure-facing services reply within the dispatch; Secure Partition callers use the begin/finish path when scheduling another partition is required. |
| Secure services are linked in one image. | Scoped isolation difference | Service writable state is isolated by unprivileged threads and Secure MPU domains, but executable text is shared rather than separately linked. |
| All shipped service loops run as scheduled coroutines. | Implementation difference | Service code uses the standard Secure Partition API; privileged hardware access goes through identity-pinned SVC gates. |
| Abnormal Non-secure guest termination reclaims the guest's connections without delivering `PSA_IPC_DISCONNECT` to the affected services. | Scoped deviation | Fault-handler cleanup cannot dispatch a Secure service inline without re-entering the scheduler. Shipped services do not use `psa_set_rhandle()` for per-connection cleanup, but a ported service that depends on disconnect cleanup must account for this behavior. |
Expand Down
1 change: 1 addition & 0 deletions include/wolftrust/ffm.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ typedef struct wt_ffm_message_runtime {
uint8_t allocated;
uint8_t active;
uint8_t complete;
uint8_t abandoned; /* Client returned; retain ownership until service reply. */
Comment thread
aidangarske marked this conversation as resolved.
} wt_ffm_message_runtime_t;

struct wt_ffm_runtime {
Expand Down
3 changes: 2 additions & 1 deletion include/wolftrust/services/hsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ int wt_hsm_attest_public_key(uint8_t* publicKey, size_t publicKeyCapacity,

/* Gated vault backing (WT-FFM-0047): bind the shared NVM context, then
* install wt_hsm_vault_backend into SERVICE_VAULT. wt_hsm_init does both;
* host tests may bind their own (e.g. ramsim-backed) context directly. */
* requires wh_NvmFlash capacity/compaction callbacks. Host tests may use
* that backend over ramsim. Other NVM backends are rejected with -1. */
struct whNvmContext_t;
int wt_hsm_vault_init(struct whNvmContext_t* nvm);
struct wt_vault_backend;
Expand Down
80 changes: 80 additions & 0 deletions src/ffm.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#define WT_FFM_HANDLE_GEN_MASK 0x00FFFFFFU
#define WT_FFM_HANDLE_CONNECTION 1U
#define WT_FFM_HANDLE_MESSAGE 2U
#define WT_FFM_ABANDONED_REPLY 1U
#define WT_FFM_ABANDONED_CLOSE 2U

static uint32_t wt_ffm_next_generation(uint32_t generation)
{
Expand Down Expand Up @@ -277,6 +279,25 @@ static int wt_ffm_message_from_handle(wt_ffm_runtime_t* runtime,
return WT_FFM_SUCCESS;
}

static int wt_ffm_close_abandoned(wt_ffm_runtime_t* runtime,
uint16_t connection_index)
{
size_t i;

for (i = 0U; i < WT_FFM_MAX_MESSAGES; i++) {
wt_ffm_message_runtime_t* message = &runtime->messages[i];

if (message->allocated != 0U && message->abandoned != 0U &&
message->connection_index == connection_index) {
message->abandoned = WT_FFM_ABANDONED_CLOSE;
runtime->connections[connection_index].state =
WT_IPC_CONNECTION_DISCONNECTING;
return 1;
}
}
return 0;
}

static void wt_ffm_update_service_signal(wt_ffm_runtime_t* runtime,
uint16_t service_index)
{
Expand Down Expand Up @@ -651,6 +672,12 @@ psa_handle_t wt_ffm_connect(wt_ffm_runtime_t* runtime,
wt_ffm_enqueue(runtime, service_index, message_index);
ret = wt_ffm_dispatch_message(runtime, message_index);
status = message->reply_status;
if (ret != WT_FFM_SUCCESS && message->active != 0U) {
message->abandoned = WT_FFM_ABANDONED_CLOSE;
return ret == WT_FFM_ERROR_RESOURCE ?
(psa_handle_t)PSA_ERROR_CONNECTION_BUSY :
(psa_handle_t)PSA_ERROR_GENERIC_ERROR;
}
if (ret != WT_FFM_SUCCESS) {
/* A refused CONNECT dispatch must unlink the message before its slot
* is released, or the slot aliases the next queued request. */
Expand Down Expand Up @@ -747,6 +774,13 @@ psa_status_t wt_ffm_call(wt_ffm_runtime_t* runtime,
ret = wt_ffm_dispatch_message(runtime, message_index);
if (ret != WT_FFM_SUCCESS) {
connection->state = WT_IPC_CONNECTION_ERROR;
if (message->active != 0U) {
connection->error_latch = 1U;
message->abandoned = WT_FFM_ABANDONED_REPLY;
(void)memset(message->client_output, 0,
sizeof(message->client_output));
return PSA_ERROR_GENERIC_ERROR;
}
wt_ffm_dequeue_message(runtime, connection->service_index,
message_index);
wt_ffm_release_message(runtime, message_index);
Expand Down Expand Up @@ -823,6 +857,8 @@ int wt_ffm_close(wt_ffm_runtime_t* runtime, psa_client_id_t caller,
if (connection->state != WT_IPC_CONNECTION_IDLE &&
connection->state != WT_IPC_CONNECTION_ERROR)
return WT_FFM_ERROR_STATE;
if (wt_ffm_close_abandoned(runtime, connection_index) != 0)
return WT_FFM_SUCCESS;
if (wt_ffm_alloc_message(runtime, &message_index) != WT_FFM_SUCCESS)
return WT_FFM_ERROR_RESOURCE;

Expand All @@ -834,6 +870,10 @@ int wt_ffm_close(wt_ffm_runtime_t* runtime, psa_client_id_t caller,
message->type = PSA_IPC_DISCONNECT;
wt_ffm_enqueue(runtime, connection->service_index, message_index);
ret = wt_ffm_dispatch_message(runtime, message_index);
if (ret != WT_FFM_SUCCESS && message->active != 0U) {
message->abandoned = WT_FFM_ABANDONED_CLOSE;
return ret;
}
if (ret != WT_FFM_SUCCESS) {
wt_ffm_dequeue_message(runtime, connection->service_index,
message_index);
Expand Down Expand Up @@ -988,6 +1028,10 @@ int wt_ffm_close_begin(wt_ffm_runtime_t* runtime, psa_client_id_t caller,
if (connection->state != WT_IPC_CONNECTION_IDLE &&
connection->state != WT_IPC_CONNECTION_ERROR)
return WT_FFM_ERROR_STATE;
if (wt_ffm_close_abandoned(runtime, connection_index) != 0) {
*msg_index = WT_FFM_QUEUE_NONE;
return WT_FFM_SUCCESS;
}
if (wt_ffm_alloc_message(runtime, &message_index) != WT_FFM_SUCCESS)
return WT_FFM_ERROR_RESOURCE;

Expand Down Expand Up @@ -1048,6 +1092,11 @@ int wt_ffm_fail_partition_messages(wt_ffm_runtime_t* runtime,
message->complete = 1U;
runtime->connections[message->connection_index].state =
WT_IPC_CONNECTION_ERROR;
if (message->abandoned != 0U) {
if (message->abandoned == WT_FFM_ABANDONED_CLOSE)
wt_ffm_release_connection(runtime, message->connection_index);
wt_ffm_release_message(runtime, (uint16_t)i);
}
failed++;
}

Expand Down Expand Up @@ -1575,6 +1624,35 @@ int wt_ffm_write(wt_ffm_runtime_t* runtime, int32_t partition_id,
return WT_FFM_SUCCESS;
}

static void wt_ffm_finish_abandoned(wt_ffm_runtime_t* runtime,
uint16_t message_index)
{
wt_ffm_message_runtime_t* message = &runtime->messages[message_index];
uint16_t connection_index = message->connection_index;
wt_ffm_connection_runtime_t* connection =
&runtime->connections[connection_index];
int disconnect;

disconnect = (message->type == PSA_IPC_CONNECT &&
message->reply_status == PSA_SUCCESS) ||
(message->type >= PSA_IPC_CALL &&
message->abandoned == WT_FFM_ABANDONED_CLOSE);
if (!disconnect && message->type < PSA_IPC_CALL)
wt_ffm_release_connection(runtime, connection_index);
wt_ffm_release_message(runtime, message_index);
if (disconnect) {
/* Reuse the replied slot so cleanup cannot fail on pool exhaustion. */
message->allocated = 1U;
message->abandoned = WT_FFM_ABANDONED_CLOSE;
message->caller = connection->caller;
message->connection_index = connection_index;
message->service_index = connection->service_index;
message->type = PSA_IPC_DISCONNECT;
connection->state = WT_IPC_CONNECTION_DISCONNECTING;
wt_ffm_enqueue(runtime, message->service_index, message_index);
}
}

int wt_ffm_reply(wt_ffm_runtime_t* runtime, int32_t partition_id,
psa_handle_t msg_handle, psa_status_t status)
{
Expand Down Expand Up @@ -1622,5 +1700,7 @@ int wt_ffm_reply(wt_ffm_runtime_t* runtime, int32_t partition_id,
message->reply_status = status;
message->active = 0U;
message->complete = 1U;
if (message->abandoned != 0U)
wt_ffm_finish_abandoned(runtime, message_index);
return WT_FFM_SUCCESS;
}
26 changes: 13 additions & 13 deletions src/monitor.c
Original file line number Diff line number Diff line change
Expand Up @@ -256,19 +256,19 @@ static void wt_tick_restart_backoff(void)

if (runtime->remaining_delay_ticks > 0U) {
runtime->remaining_delay_ticks--;
if (runtime->remaining_delay_ticks == 0U &&
runtime->state == WT_GUEST_RESTARTING) {
/* A relaunch is a launch: the image must still match its pin
* before the domain is re-entered. */
if (wt_verify_guest_launch((wt_guest_id_t)i) ==
WT_GUEST_VERIFY_OK) {
wt_partition_reset_runtime(&g_scheduler.configs[i],
runtime);
}
else {
runtime->state = WT_GUEST_FAULTED;
g_wt_quarantine_events++;
}
}
if (runtime->remaining_delay_ticks == 0U &&
runtime->state == WT_GUEST_RESTARTING) {
/* A relaunch is a launch: the image must still match its pin
* before the domain is re-entered. */
if (wt_verify_guest_launch((wt_guest_id_t)i) ==
WT_GUEST_VERIFY_OK) {
wt_partition_reset_runtime(&g_scheduler.configs[i],
runtime);
}
else {
runtime->state = WT_GUEST_FAULTED;
g_wt_quarantine_events++;
}
}
}
Expand Down
Loading
Loading