Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add testing for concurrently loading/using/destroying the same key #8924

Merged
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0a271fd
Add key_destroyable parameter to mbedtls_test_psa_exercise_key
Ryan-Everett-arm Mar 12, 2024
f08a93f
Add key_destroyable parameter to check_key_attributes_sanity
Ryan-Everett-arm Mar 12, 2024
7763550
Add key_destroyable parameter to exercise_mac_key
Ryan-Everett-arm Mar 12, 2024
70691f3
Add key_destroyable parameter to psa_exercise_cipher_key
Ryan-Everett-arm Mar 12, 2024
fbe703d
Add key_destroyable parameter to exercise_aead_key
Ryan-Everett-arm Mar 12, 2024
6edd408
Add key_destroyable parameter to exercise_signature_key
Ryan-Everett-arm Mar 12, 2024
d48fc10
Add key_destroyable parameter to exercise_asymmetric_encryption_key
Ryan-Everett-arm Mar 12, 2024
c1cc668
Add key_destroyable parameter to key derivation smoke tests
Ryan-Everett-arm Mar 12, 2024
8163028
Add key_destroyable parameter to raw key agreement smoke tests
Ryan-Everett-arm Mar 12, 2024
73e4ea3
Add key_destroyable parameter to non-raw key agreement smoke tests
Ryan-Everett-arm Mar 12, 2024
fbf815d
Add key_destroyable parameter to key export smoke tests
Ryan-Everett-arm Mar 12, 2024
5061999
Add test function for concurrently using the same persistent key
Ryan-Everett-arm Mar 12, 2024
f111f35
Add test cases for concurrently_use_same_persistent_key
Ryan-Everett-arm Mar 12, 2024
6c48870
Fix typo in thread_import_key
Ryan-Everett-arm Mar 14, 2024
3de040f
Use TEST_FAIL in threaded tests
Ryan-Everett-arm Mar 14, 2024
6de38ac
Add missing PSA_ASSERT in mbedtls_test_psa_raw_key_agreement_with_self
Ryan-Everett-arm Mar 14, 2024
e1b50f3
Document unsupported concurrency scenario in psa_exercise_key
Ryan-Everett-arm Mar 14, 2024
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
193 changes: 193 additions & 0 deletions tests/suites/test_suite_psa_crypto.function
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,127 @@ exit:
}

#if defined(MBEDTLS_THREADING_PTHREAD)

typedef struct same_key_context {
data_t *data;
mbedtls_svc_key_id_t key;
psa_key_attributes_t *attributes;
int type;
int bits;
/* The following two parameters are used to ensure that when multiple
* threads attempt to load/destroy the key, exactly one thread succeeds. */
int key_loaded;
mbedtls_threading_mutex_t MBEDTLS_PRIVATE(key_loaded_mutex);
}
same_key_context;

/* Attempt to import the key in ctx. This handles any valid error codes
* and reports an error for any invalid codes. This function also insures
* that once imported by some thread, all threads can use the key. */
void *thread_import_key(void *ctx)
{
mbedtls_svc_key_id_t returned_key_id;
same_key_context *skc = (struct same_key_context *) ctx;
psa_key_attributes_t got_attributes = PSA_KEY_ATTRIBUTES_INIT;

/* Import the key, exactly one thread must succceed. */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: succeed

psa_status_t status = psa_import_key(skc->attributes, skc->data->x,
skc->data->len, &returned_key_id);
switch (status) {
case PSA_SUCCESS:
if (mbedtls_mutex_lock(&skc->key_loaded_mutex) == 0) {
if (skc->key_loaded) {
mbedtls_mutex_unlock(&skc->key_loaded_mutex);
/* More than one thread has succeeded, report a failure. */
TEST_EQUAL(skc->key_loaded, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not directly TEST_FAIL() in this case? I think it would be more explicit, but I might be missing something.
If you accept this change, then there is a very similar case also in thread_use_and_destroy_key() below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware of TEST_FAIL(), good suggestion!

}
skc->key_loaded = 1;
mbedtls_mutex_unlock(&skc->key_loaded_mutex);
}
break;
case PSA_ERROR_INSUFFICIENT_MEMORY:
/* If all of the key slots are reserved when a thread
* locks the mutex to reserve a new slot, it will return
* PSA_ERROR_INSUFFICIENT_MEMORY; this is correct behaviour.
* There is a chance for this to occur here when the number of
* threads running this function is larger than the number of
* free key slots. Each thread reserves an empty key slot,
* unlocks the mutex, then relocks it to finalize key creation.
* It is at that point where the thread sees that the key
* already exists, releases the reserved slot,
* and returns PSA_ERROR_ALREADY_EXISTS.
* There is no guarantee that the key is loaded upon this return
* code, so we can't test the key information. Just stop this
* thread from executing, note that this is not an error. */
goto exit;
break;
case PSA_ERROR_ALREADY_EXISTS:
/* The key has been loaded by a different thread. */
break;
default:
PSA_ASSERT(status);
}
/* At this point the key must exist, test the key information. */
status = psa_get_key_attributes(skc->key, &got_attributes);
if (status == PSA_ERROR_INSUFFICIENT_MEMORY) {
/* This is not a test failure. The following sequence of events
* causes this to occur:
* 1: This thread successfuly imports a persistent key skc->key.
* 2: N threads reserve an empty key slot in psa_import_key,
* where N is equal to the number of free key slots.
* 3: A final thread attempts to reserve an empty key slot, kicking
* skc->key (which has no registered readers) out of its slot.
* 4: This thread calls psa_get_key_attributes(skc->key,...):
* it sees that skc->key is not in a slot, attempts to load it and
* finds that there are no free slots.
* This thread returns PSA_ERROR_INSUFFICIENT_MEMORY.
*
* The PSA spec allows this behaviour, it is an unavoidable consequence
* of allowing persistent keys to be kicked out of the key store while
* they are still valid. */
goto exit;
}
PSA_ASSERT(status);
TEST_EQUAL(psa_get_key_type(&got_attributes), skc->type);
TEST_EQUAL(psa_get_key_bits(&got_attributes), skc->bits);

exit:
/* Key attributes may have been returned by psa_get_key_attributes(),
* reset them as required. */
psa_reset_key_attributes(&got_attributes);
return NULL;
}

void *thread_use_and_destroy_key(void *ctx)
{
same_key_context *skc = (struct same_key_context *) ctx;

/* Do something with the key according
* to its type and permitted usage. */
TEST_ASSERT(mbedtls_test_psa_exercise_key(skc->key,
skc->attributes->policy.usage,
skc->attributes->policy.alg, 1));

psa_status_t status = psa_destroy_key(skc->key);
if (status == PSA_SUCCESS) {
if (mbedtls_mutex_lock(&skc->key_loaded_mutex) == 0) {
/* Ensure that we are the only thread to succeed. */
if (skc->key_loaded != 1) {
mbedtls_mutex_unlock(&skc->key_loaded_mutex);
//Will always fail
TEST_EQUAL(skc->key_loaded, 1);
}
skc->key_loaded = 0;
mbedtls_mutex_unlock(&skc->key_loaded_mutex);
}
} else {
TEST_EQUAL(status, PSA_ERROR_INVALID_HANDLE);
}

exit:
return NULL;
}

typedef struct generate_key_context {
psa_key_type_t type;
psa_key_usage_t usage;
Expand Down Expand Up @@ -1824,6 +1945,78 @@ exit:
}
/* END_CASE */


#if defined(MBEDTLS_THREADING_PTHREAD)
/* BEGIN_CASE depends_on:MBEDTLS_THREADING_PTHREAD:MBEDTLS_PSA_CRYPTO_STORAGE_C */
void concurrently_use_same_persistent_key(data_t *data,
int type_arg,
int bits_arg,
int alg_arg,
int thread_count_arg)
{
size_t thread_count = (size_t) thread_count_arg;
mbedtls_test_thread_t *threads = NULL;
mbedtls_svc_key_id_t key_id = mbedtls_svc_key_id_make(1, 1);
same_key_context skc;
skc.data = data;
skc.key = key_id;
skc.type = type_arg;
skc.bits = bits_arg;
skc.key_loaded = 0;
mbedtls_mutex_init(&skc.key_loaded_mutex);
psa_key_usage_t usage = mbedtls_test_psa_usage_to_exercise(skc.type, alg_arg);
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;

PSA_ASSERT(psa_crypto_init());

psa_set_key_id(&attributes, key_id);
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_usage_flags(&attributes, usage);
psa_set_key_algorithm(&attributes, alg_arg);
psa_set_key_type(&attributes, type_arg);
psa_set_key_bits(&attributes, bits_arg);
skc.attributes = &attributes;

TEST_CALLOC(threads, sizeof(mbedtls_test_thread_t) * thread_count);

/* Test that when multiple threads import the same key,
* exactly one thread succeeds and the rest fail with valid errors.
* Also test that all threads can use the key as soon as it has been
* imported. */
for (size_t i = 0; i < thread_count; i++) {
TEST_EQUAL(
mbedtls_test_thread_create(&threads[i], thread_import_key,
(void *) &skc), 0);
}

/* Join threads. */
for (size_t i = 0; i < thread_count; i++) {
TEST_EQUAL(mbedtls_test_thread_join(&threads[i]), 0);
}

/* Test that when multiple threads use and destroy a key no corruption
* occurs, and exactly one thread succeeds when destroying the key. */
for (size_t i = 0; i < thread_count; i++) {
TEST_EQUAL(
mbedtls_test_thread_create(&threads[i], thread_use_and_destroy_key,
(void *) &skc), 0);
}

/* Join threads. */
for (size_t i = 0; i < thread_count; i++) {
TEST_EQUAL(mbedtls_test_thread_join(&threads[i]), 0);
}
/* Ensure that one thread succeeded in destroying the key. */
TEST_ASSERT(!skc.key_loaded);
exit:
psa_reset_key_attributes(&attributes);
mbedtls_mutex_free(&skc.key_loaded_mutex);
mbedtls_free(threads);
PSA_DONE();
}
/* END_CASE */
#endif

/* BEGIN_CASE */
void import_and_exercise_key(data_t *data,
int type_arg,
Expand Down