blob: 4abfe6c0504330f113b9a7ccb959521f2180cc59 [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#define TEST_NAME "Futex"
#include "tests.hh"
#include <cheri.hh>
#include <cheriot-atomic.hh>
#include <errno.h>
#include <futex.h>
#include <interrupt.h>
#include <thread.h>
#include <thread_pool.h>
using namespace CHERI;
using namespace thread_pool;
#ifdef SAIL
DECLARE_AND_DEFINE_INTERRUPT_CAPABILITY(interruptCapability,
FakeInterrupt,
true,
true);
#endif
int test_futex()
{
static uint32_t futex;
int ret;
// Make sure that waking a futex with no sleepers doesn't crash!
ret = futex_wake(&futex, 1);
TEST(ret == 0, "Waking a futex with no sleepers should return 0");
// Wake in another thread, ensure that we don't wake until the futex value
// has been set to 1.
async([]() {
futex = 1;
futex_wake(&futex, 1);
});
debug_log("Calling blocking futex_wait");
ret = futex_wait(&futex, 0);
TEST(ret == 0, "futex_wait returned {}, expected 0", ret);
TEST(futex == 1,
"Thread should not have woken up until the futex had been set to 1");
// Check that a futex wait with a mismatched value returns immediately.
debug_log("Calling futex_wait and expecting immediate return");
ret = futex_wait(&futex, 0);
TEST(ret == 0, "futex_wait returned {}, expected 0", ret);
debug_log("Calling futex with timeout");
{
Timeout t{3};
auto err = futex_timed_wait(&t, &futex, 1);
TEST(err == -ETIMEDOUT,
"futex_timed_wait returned {}, expected {}",
err,
-ETIMEDOUT);
TEST(t.elapsed >= 3,
"futex_timed_wait timed out but elapsed ticks {} too small",
t.elapsed);
}
Timeout t{3};
auto err = futex_timed_wait(&t, &futex, 0);
TEST(err == 0, "futex_timed_wait returned {}, expected {}", err, 0);
// Some tests for invalid API usage.
ret = futex_wait(nullptr, 0);
TEST(ret == -EINVAL,
"futex_wait returned {} when called with a null pointer, expected {}",
ret,
-EINVAL);
ret = futex_wake(nullptr, 0);
TEST(ret == -EINVAL,
"futex_wake returned {} when called with a null pointer, expected {}",
ret,
-EINVAL);
Capability fcap{&futex};
fcap.permissions() &= PermissionSet::omnipotent().without(Permission::Load);
ret = futex_wait(fcap, 0);
TEST(
ret == -EINVAL,
"futex_wait returned {} when called without load permission, expected {}",
ret,
-EINVAL);
fcap = &futex;
fcap.permissions() &=
PermissionSet::omnipotent().without(Permission::Store);
ret = futex_wake(fcap, 1);
TEST(ret == -EINVAL,
"futex_wake returned {} when called without store permission, "
"expected {}",
ret,
-EINVAL);
#ifdef SAIL
// If we're targeting Sail, also do some basic tests of the interrupt
// capability. We don't have an interrupt controller, so these tests are
// quite rudimentary.
Capability<const uint32_t> interruptFutex =
interrupt_futex_get(STATIC_SEALED_VALUE(interruptCapability));
TEST(interruptFutex.is_valid(),
"Interrupt futex {} is not valid",
interruptFutex);
TEST(!interruptFutex.permissions().contains(Permission::Store),
"Interrupt futex {} should not have store permission",
interruptFutex);
TEST(*interruptFutex == 0,
"Interrupt futex {} for fake interrupt should not have fired but "
"shows {} interrupts",
interruptFutex,
*interruptFutex);
TEST(interrupt_complete(STATIC_SEALED_VALUE(interruptCapability)) == 0,
"interrupt_complete returned an error unexpectedly");
TEST(interrupt_complete(nullptr) != 0,
"interrupt_complete returned success unexpectedly");
#endif
debug_log("Starting priority inheritance test");
futex = 0;
static cheriot::atomic<int> state;
auto priorityBug = []() {
if (thread_id_get() == 2)
{
debug_log("Medium-priority task starting");
// We are the high priority thread, wait for the futex to be
// acquired and then spin and never yield.
while (state != 1)
{
Timeout t{3};
thread_sleep(&t);
}
debug_log("Consuming all CPU on medium-priority thread");
state = 2;
while (state != 4) {}
debug_log("Finishing medium-priority task");
}
else
{
debug_log("Low-priority task starting");
futex = thread_id_get();
state = 1;
debug_log("Low-priority thread acquired futex, yielding");
// Now the high priority thread should run.
while (state != 3)
{
yield();
}
debug_log("Low-priority thread finished, unlocking");
state = 4;
futex = 0;
futex_wake(&futex, 1);
}
};
async(priorityBug);
async(priorityBug);
debug_log("Waiting for background threads to enter the right state");
while (state != 2)
{
Timeout t{3};
thread_sleep(&t);
}
debug_log("High-priority thread attempting to acquire futex owned by "
"low-priority thread without priority propagation");
state = 3;
t.remaining = 1;
futex_timed_wait(&t, &futex, futex, FutexNone);
TEST(futex != 0, "Made progress surprisingly!");
debug_log("High-priority thread attempting to acquire futex owned by "
"low-priority thread with priority propagation");
t.remaining = 4;
futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance);
TEST(futex == 0, "Failed to make progress!");
futex = 1234;
t.remaining = 1;
ret = futex_timed_wait(&t, &futex, futex, FutexPriorityInheritance);
TEST(ret == -EINVAL,
"PI futex with an invalid thread ID returned {}, should be {}",
ret,
-EINVAL);
futex = 0;
debug_log(
"Testing priority inheriting futex_timed_wait with zero thread ID");
// zero is not a valid thread ID for priority inheriting futex. Previously
// this caused a crash due to OOB access but better to return -EINVAL.
ret = futex_timed_wait(&t, &futex, 0, FutexPriorityInheritance);
TEST(ret == -EINVAL,
"PI futex with a zero thread ID returned {}, should be {}",
ret,
-EINVAL);
return 0;
}