381 lines
14 KiB
C
381 lines
14 KiB
C
// Native capability-handle smoke test.
|
|
// Verifies monotonic identities, typed rights, derivation, aliasing, and revocation.
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define EVENT_AUTO_RESET 0u
|
|
#define HANDLE_STRESS_COUNT 512u
|
|
#define DERIVATION_SUCCESS_COUNT 15u
|
|
|
|
typedef struct TEST_QUEUE_MESSAGE {
|
|
uint32_t Type;
|
|
uint32_t Flags;
|
|
uint64_t Param[6];
|
|
} TEST_QUEUE_MESSAGE;
|
|
|
|
typedef char TEST_QUEUE_MESSAGE_SIZE_CHECK[
|
|
(sizeof(TEST_QUEUE_MESSAGE) == SYS_QUEUE_MESSAGE_SIZE) ? 1 : -1
|
|
];
|
|
|
|
static void print(const char* s) {
|
|
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
|
|
static void print_u64(uint64_t v) {
|
|
char buf[32];
|
|
int i = 0;
|
|
if (v == 0) {
|
|
print("0");
|
|
return;
|
|
}
|
|
while (v && i < (int)sizeof(buf)) {
|
|
buf[i++] = (char)('0' + (v % 10));
|
|
v /= 10;
|
|
}
|
|
while (i--) {
|
|
char c = buf[i];
|
|
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
}
|
|
|
|
static void exit_now(int code) {
|
|
while (true) SysExitProcess(code);
|
|
}
|
|
|
|
static void fail(const char* msg) {
|
|
print("handle_dup_smoke: ");
|
|
print(msg);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
static void fail_status(const char* what, KSTATUS status) {
|
|
print("handle_dup_smoke: ");
|
|
print(what);
|
|
print(" status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Requires one exact status value.
|
|
static void expect_status(const char* what, KSTATUS got, KSTATUS expected) {
|
|
if (got == expected) return;
|
|
print("handle_dup_smoke: ");
|
|
print(what);
|
|
print(" got=");
|
|
print_u64(got);
|
|
print(" expected=");
|
|
print_u64(expected);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Duplicates one handle into the current process.
|
|
static KSTATUS duplicate_handle(SYS_HANDLE source,
|
|
SYS_HANDLE process,
|
|
SYS_RIGHTS desiredRights,
|
|
uint32_t flags,
|
|
SYS_HANDLE* out) {
|
|
SYS_DUPLICATE_HANDLE_REQUEST req = {
|
|
sizeof(SYS_DUPLICATE_HANDLE_REQUEST),
|
|
source,
|
|
process,
|
|
desiredRights,
|
|
flags,
|
|
out,
|
|
};
|
|
return SysDuplicateHandle(&req);
|
|
}
|
|
|
|
// Derives one child capability in the current process.
|
|
static KSTATUS derive_handle(SYS_HANDLE source, SYS_RIGHTS desiredRights, SYS_HANDLE* out) {
|
|
SYS_DERIVE_HANDLE_REQUEST req = {
|
|
sizeof(SYS_DERIVE_HANDLE_REQUEST),
|
|
source,
|
|
desiredRights,
|
|
out,
|
|
};
|
|
return SysDeriveHandle(&req);
|
|
}
|
|
|
|
// Sends one queue message.
|
|
static KSTATUS send_message(SYS_HANDLE queue, uint32_t type, uint64_t value) {
|
|
TEST_QUEUE_MESSAGE msg = {0};
|
|
msg.Type = type;
|
|
msg.Param[0] = value;
|
|
SYS_QUEUE_SEND_REQUEST req = {
|
|
sizeof(req),
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
0,
|
|
&msg,
|
|
};
|
|
return SysSendQueue(&req);
|
|
}
|
|
|
|
// Receives one queue message.
|
|
static KSTATUS recv_message(SYS_HANDLE queue, TEST_QUEUE_MESSAGE* msg) {
|
|
SYS_QUEUE_RECV_REQUEST req = {
|
|
sizeof(req),
|
|
queue,
|
|
SYS_QUEUE_FLAG_NONE,
|
|
0,
|
|
msg,
|
|
};
|
|
return SysRecvQueue(&req);
|
|
}
|
|
|
|
// Verifies one received queue message.
|
|
static void check_message(const TEST_QUEUE_MESSAGE* msg, uint32_t type, uint64_t value) {
|
|
if (msg->Type != type || msg->Param[0] != value) fail("message payload mismatch");
|
|
}
|
|
|
|
// Closes one handle and requires success.
|
|
static void close_handle(const char* what, SYS_HANDLE handle) {
|
|
KSTATUS status = SysCloseHandle(handle);
|
|
if (!KSUCCESS(status)) fail_status(what, status);
|
|
}
|
|
|
|
// Verifies that closed identities are not reused and stale values stay invalid.
|
|
static void test_monotonic_identity(void) {
|
|
SYS_HANDLE first = SYS_HANDLE_INVALID;
|
|
KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &first);
|
|
if (!KSUCCESS(status)) fail_status("create first monotonic event", status);
|
|
close_handle("close first monotonic event", first);
|
|
|
|
SYS_HANDLE second = SYS_HANDLE_INVALID;
|
|
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &second);
|
|
if (!KSUCCESS(status)) fail_status("create second monotonic event", status);
|
|
if (second <= first) fail("handle identity was reused or moved backwards");
|
|
|
|
expect_status("close stale handle", SysCloseHandle(first), NTLSTATUS_INVALID_HANDLE);
|
|
close_handle("close second monotonic event", second);
|
|
}
|
|
|
|
// Verifies that live-handle capacity is no longer tied to the old 256-slot table.
|
|
static void test_dynamic_handle_capacity(void) {
|
|
SYS_HANDLE handles[HANDLE_STRESS_COUNT];
|
|
SYS_HANDLE previous = SYS_HANDLE_INVALID;
|
|
|
|
for (uint32_t i = 0; i < HANDLE_STRESS_COUNT; ++i) {
|
|
handles[i] = SYS_HANDLE_INVALID;
|
|
KSTATUS status = SysCreateEvent(EVENT_AUTO_RESET, 0, &handles[i]);
|
|
if (!KSUCCESS(status)) fail_status("create stress event", status);
|
|
if (previous != SYS_HANDLE_INVALID && handles[i] <= previous)
|
|
fail("stress handles were not strictly monotonic");
|
|
previous = handles[i];
|
|
}
|
|
|
|
for (uint32_t i = HANDLE_STRESS_COUNT; i != 0; --i)
|
|
close_handle("close stress event", handles[i - 1]);
|
|
}
|
|
|
|
// Verifies explicit rights attenuation on duplicate aliases.
|
|
static void test_duplicate_rights(SYS_HANDLE process, SYS_HANDLE queue,
|
|
SYS_HANDLE* sameRightsOut,
|
|
SYS_HANDLE* receiveOnlyOut,
|
|
SYS_HANDLE* sendOnlyOut) {
|
|
SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID;
|
|
KSTATUS status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE,
|
|
SYS_DUPLICATE_HANDLE_FLAG_NONE, &receiveOnly);
|
|
if (!KSUCCESS(status)) fail_status("duplicate receive-only", status);
|
|
|
|
SYS_HANDLE sendOnly = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_SEND,
|
|
SYS_DUPLICATE_HANDLE_FLAG_NONE, &sendOnly);
|
|
if (!KSUCCESS(status)) fail_status("duplicate send-only", status);
|
|
|
|
SYS_HANDLE sameRights = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(queue, process, 0,
|
|
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &sameRights);
|
|
if (!KSUCCESS(status)) fail_status("duplicate same rights", status);
|
|
|
|
status = send_message(receiveOnly, 10, 100);
|
|
expect_status("receive-only send denied", status, NTLSTATUS_ACCESS_DENIED);
|
|
|
|
TEST_QUEUE_MESSAGE msg;
|
|
status = recv_message(sendOnly, &msg);
|
|
expect_status("send-only receive denied", status, NTLSTATUS_ACCESS_DENIED);
|
|
|
|
status = send_message(queue, 11, 101);
|
|
if (!KSUCCESS(status)) fail_status("send original", status);
|
|
status = recv_message(receiveOnly, &msg);
|
|
if (!KSUCCESS(status)) fail_status("receive receive-only", status);
|
|
check_message(&msg, 11, 101);
|
|
|
|
status = send_message(sendOnly, 12, 102);
|
|
if (!KSUCCESS(status)) fail_status("send send-only", status);
|
|
status = recv_message(queue, &msg);
|
|
if (!KSUCCESS(status)) fail_status("receive original", status);
|
|
check_message(&msg, 12, 102);
|
|
|
|
status = send_message(sameRights, 13, 103);
|
|
if (!KSUCCESS(status)) fail_status("send same-rights", status);
|
|
status = recv_message(sameRights, &msg);
|
|
if (!KSUCCESS(status)) fail_status("receive same-rights", status);
|
|
check_message(&msg, 13, 103);
|
|
|
|
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(receiveOnly, process, SYS_QUEUE_RIGHT_RECEIVE,
|
|
SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
|
|
expect_status("duplicate non-duplicable handle", status, NTLSTATUS_ACCESS_DENIED);
|
|
|
|
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE | 0x80000000u,
|
|
SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
|
|
expect_status("duplicate unknown rights", status, NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
status = duplicate_handle(queue, process, SYS_QUEUE_RIGHT_RECEIVE,
|
|
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
|
|
expect_status("duplicate same-rights with desired rights", status, NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
status = duplicate_handle(queue, process, 0, SYS_DUPLICATE_HANDLE_FLAG_NONE, &ignored);
|
|
expect_status("duplicate zero rights", status, NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
SYS_DUPLICATE_HANDLE_REQUEST badOut = {
|
|
sizeof(SYS_DUPLICATE_HANDLE_REQUEST),
|
|
queue,
|
|
process,
|
|
SYS_QUEUE_RIGHT_RECEIVE,
|
|
SYS_DUPLICATE_HANDLE_FLAG_NONE,
|
|
0,
|
|
};
|
|
status = SysDuplicateHandle(&badOut);
|
|
expect_status("duplicate null output", status, NTLSTATUS_INVALID_PARAMETER);
|
|
|
|
*sameRightsOut = sameRights;
|
|
*receiveOnlyOut = receiveOnly;
|
|
*sendOnlyOut = sendOnly;
|
|
}
|
|
|
|
// Verifies child grants attenuate authority and can be revoked independently.
|
|
static void test_derived_revocation(SYS_HANDLE process, SYS_HANDLE queue) {
|
|
const SYS_RIGHTS childRights = SYS_QUEUE_RIGHT_RECEIVE |
|
|
SYS_RIGHT_DUPLICATE |
|
|
SYS_RIGHT_DERIVE |
|
|
SYS_RIGHT_REVOKE;
|
|
|
|
SYS_HANDLE child = SYS_HANDLE_INVALID;
|
|
KSTATUS status = derive_handle(queue, childRights, &child);
|
|
if (!KSUCCESS(status)) fail_status("derive child", status);
|
|
|
|
SYS_HANDLE childAlias = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(child, process, 0,
|
|
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &childAlias);
|
|
if (!KSUCCESS(status)) fail_status("duplicate child alias", status);
|
|
|
|
SYS_HANDLE grandchild = SYS_HANDLE_INVALID;
|
|
status = derive_handle(child, SYS_QUEUE_RIGHT_RECEIVE, &grandchild);
|
|
if (!KSUCCESS(status)) fail_status("derive grandchild", status);
|
|
|
|
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
|
|
status = derive_handle(child, SYS_QUEUE_RIGHT_SEND, &ignored);
|
|
expect_status("derive cannot amplify rights", status, NTLSTATUS_ACCESS_DENIED);
|
|
|
|
status = SysRevokeHandle(grandchild);
|
|
expect_status("revoke without revoke right", status, NTLSTATUS_ACCESS_DENIED);
|
|
|
|
TEST_QUEUE_MESSAGE msg;
|
|
status = send_message(queue, 20, 200);
|
|
if (!KSUCCESS(status)) fail_status("send for grandchild", status);
|
|
status = recv_message(grandchild, &msg);
|
|
if (!KSUCCESS(status)) fail_status("receive grandchild", status);
|
|
check_message(&msg, 20, 200);
|
|
|
|
status = SysRevokeHandle(child);
|
|
if (!KSUCCESS(status)) fail_status("revoke child", status);
|
|
|
|
ignored = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(childAlias, process, 0,
|
|
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
|
|
expect_status("duplicate revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED);
|
|
|
|
status = derive_handle(childAlias, SYS_QUEUE_RIGHT_RECEIVE, &ignored);
|
|
expect_status("derive revoked alias", status, NTLSTATUS_CAPABILITY_REVOKED);
|
|
|
|
status = recv_message(grandchild, &msg);
|
|
expect_status("revoked grandchild receive", status, NTLSTATUS_CAPABILITY_REVOKED);
|
|
|
|
// Revoking a derived grant must not revoke its parent grant.
|
|
status = send_message(queue, 21, 201);
|
|
if (!KSUCCESS(status)) fail_status("parent send after child revoke", status);
|
|
status = recv_message(queue, &msg);
|
|
if (!KSUCCESS(status)) fail_status("parent receive after child revoke", status);
|
|
check_message(&msg, 21, 201);
|
|
|
|
close_handle("close grandchild", grandchild);
|
|
close_handle("close child alias", childAlias);
|
|
close_handle("close child", child);
|
|
}
|
|
|
|
// Verifies the bounded derivation chain used for revocation traversal.
|
|
static void test_derivation_depth(SYS_HANDLE queue) {
|
|
const SYS_RIGHTS rights = SYS_QUEUE_RIGHT_RECEIVE |
|
|
SYS_RIGHT_DERIVE |
|
|
SYS_RIGHT_REVOKE;
|
|
SYS_HANDLE chain[DERIVATION_SUCCESS_COUNT];
|
|
SYS_HANDLE parent = queue;
|
|
|
|
for (uint32_t i = 0; i < DERIVATION_SUCCESS_COUNT; ++i) {
|
|
chain[i] = SYS_HANDLE_INVALID;
|
|
KSTATUS status = derive_handle(parent, rights, &chain[i]);
|
|
if (!KSUCCESS(status)) fail_status("derive bounded chain", status);
|
|
parent = chain[i];
|
|
}
|
|
|
|
SYS_HANDLE tooDeep = SYS_HANDLE_INVALID;
|
|
KSTATUS status = derive_handle(parent, rights, &tooDeep);
|
|
expect_status("derive beyond depth bound", status, NTLSTATUS_LIMIT_REACHED);
|
|
|
|
for (uint32_t i = DERIVATION_SUCCESS_COUNT; i != 0; --i)
|
|
close_handle("close bounded derivation", chain[i - 1]);
|
|
}
|
|
|
|
// Runs the native capability-handle smoke test.
|
|
int main(void) {
|
|
print("handle_dup_smoke: start\n");
|
|
|
|
test_monotonic_identity();
|
|
test_dynamic_handle_capacity();
|
|
|
|
uint32_t currentPid = SysGetCurrentPid();
|
|
SYS_HANDLE process = SYS_HANDLE_INVALID;
|
|
KSTATUS status = SysOpenProcess(currentPid, SYS_PROCESS_RIGHT_INSERT_HANDLE | SYS_RIGHT_QUERY, &process);
|
|
if (!KSUCCESS(status)) fail_status("open current process", status);
|
|
|
|
SYS_HANDLE queue = SYS_HANDLE_INVALID;
|
|
status = SysCreateQueue(&queue);
|
|
if (!KSUCCESS(status)) fail_status("create queue", status);
|
|
|
|
SYS_HANDLE sameRights = SYS_HANDLE_INVALID;
|
|
SYS_HANDLE receiveOnly = SYS_HANDLE_INVALID;
|
|
SYS_HANDLE sendOnly = SYS_HANDLE_INVALID;
|
|
test_duplicate_rights(process, queue, &sameRights, &receiveOnly, &sendOnly);
|
|
test_derived_revocation(process, queue);
|
|
test_derivation_depth(queue);
|
|
|
|
// A duplicate is an alias of the same grant, so revocation through either
|
|
// alias revokes the original grant as well.
|
|
status = SysRevokeHandle(sameRights);
|
|
if (!KSUCCESS(status)) fail_status("revoke duplicate alias", status);
|
|
|
|
SYS_HANDLE ignored = SYS_HANDLE_INVALID;
|
|
status = duplicate_handle(queue, process, 0,
|
|
SYS_DUPLICATE_HANDLE_FLAG_SAME_RIGHTS, &ignored);
|
|
expect_status("duplicate root after alias revoke", status, NTLSTATUS_CAPABILITY_REVOKED);
|
|
|
|
close_handle("close same-rights", sameRights);
|
|
close_handle("close send-only", sendOnly);
|
|
close_handle("close receive-only", receiveOnly);
|
|
close_handle("close revoked queue", queue);
|
|
close_handle("close process", process);
|
|
|
|
print("handle_dup_smoke: PASS\n");
|
|
exit_now(0);
|
|
return 0;
|
|
}
|