blob: 9984369ec16877c5e33ea40ecfbac1cfc3885bd5 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fake-object/object.h>
#include <lib/zx/event.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/process.h>
#include <lib/zx/result.h>
#include <lib/zx/thread.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <zircon/limits.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <array>
#include <climits> // PAGE_SIZE
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
#include <zxtest/zxtest.h>
#include "src/devices/testing/fake-object/internal.h"
namespace fake_object {
class FakeObject : public zxtest::Test {
protected:
// Catch handles leaked through tests to ensure the library itself doesn't leak any.
void TearDown() final { ZX_ASSERT(fake_object::FakeHandleTable().size() == 0); }
};
// By default a base |Object| should return ZX_ERR_NOT_SUPPORTED for
// all intercepted object syscalls. This tests that the dispatch for
// the fake syscall routing works for syscalls other tests don't exercise.
// They are organized into individual tests to make it easier to tell if a
// specific syscall is broken.
TEST_F(FakeObject, ShimGetInfo) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_get_info(res.value(), 0, nullptr, 0, nullptr, nullptr),
ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimGetProperty) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_get_property(res.value(), 0, nullptr, 0), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimSetProfile) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_set_profile(res.value(), 0, 0), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimSetProperty) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_set_property(res.value(), 0, nullptr, 0), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimSignal) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_signal(res.value(), 0, 0), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimSignalPeer) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_signal_peer(res.value(), 0, 0), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimWaitOne) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_wait_one(res.value(), 0, 0, nullptr), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, ShimWaitAsync) {
zx::result res = fake_object_create();
ASSERT_OK(res.status_value());
ASSERT_STATUS(zx_object_wait_one(res.value(), 0, 0, nullptr), ZX_ERR_NOT_SUPPORTED);
EXPECT_OK(zx_handle_close(res.value()));
}
TEST_F(FakeObject, HandleValidityCheck) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(0, 0, &vmo));
ASSERT_FALSE(HandleTable::IsValidFakeHandle(vmo.get()));
auto result = fake_object_create();
ASSERT_TRUE(result.is_ok());
ASSERT_TRUE(HandleTable::IsValidFakeHandle(result.value()));
EXPECT_OK(zx_handle_close(result.value()));
}
TEST_F(FakeObject, Get) {
EXPECT_EQ(FakeHandleTable().size(), 0);
zx::result<zx_handle_t> obj = fake_object_create();
EXPECT_OK(obj.status_value());
zx::result<std::shared_ptr<Object>> getter = FakeHandleTable().Get(obj.value());
EXPECT_TRUE(getter.is_ok());
EXPECT_EQ(FakeHandleTable().size(), 1);
EXPECT_OK(zx_handle_close(obj.value()));
}
TEST_F(FakeObject, DuplicateHandle) {
// Setup, create a fake bti, make sure it is valid:
zx::result<zx_handle_t> obj = fake_object_create();
EXPECT_OK(obj.status_value());
// Duplicate the handle, make sure it is valid and the same object:
zx_handle_t obj_dup = ZX_HANDLE_INVALID;
EXPECT_OK(zx_handle_duplicate(obj.value(), 0, &obj_dup));
EXPECT_EQ(2, fake_object::FakeHandleTable().size());
zx::result obj_koid = fake_object_get_koid(obj.value());
zx::result obj_dup_koid = fake_object_get_koid(obj_dup);
EXPECT_OK(obj_koid.status_value());
EXPECT_OK(obj_dup_koid.status_value());
EXPECT_EQ(obj_koid.value(), obj_dup_koid.value());
EXPECT_OK(zx_handle_close(obj.value()));
EXPECT_OK(zx_handle_close(obj_dup));
EXPECT_EQ(0u, fake_object::FakeHandleTable().size());
}
TEST_F(FakeObject, DuplicateRealHandle) {
// Setup, create an event and duplicate it, to make sure that still works:
zx::event event, event_dup;
ASSERT_OK(zx::event::create(0u, &event), "Error during event create");
EXPECT_OK(event.duplicate(ZX_RIGHT_SAME_RIGHTS, &event_dup));
// The ZX_EVENT_SIGNALED bit is guaranteed to be 0 when we create the object.
// Now signal the original event:
ASSERT_OK(event.signal(0u, ZX_EVENT_SIGNALED));
zx_signals_t pending;
// Now wait for that signal on the duplicated version:
EXPECT_OK(event_dup.wait_one(ZX_EVENT_SIGNALED, zx::time(0), &pending));
EXPECT_EQ(pending & ZX_EVENT_SIGNALED, ZX_EVENT_SIGNALED, "Error during wait call");
}
TEST_F(FakeObject, ReplaceHandle) {
zx::result<zx_handle_t> obj = fake_object_create();
zx_handle_t obj_repl_hnd = ZX_HANDLE_INVALID;
EXPECT_OK(obj.status_value());
zx::result<zx_koid_t> original_koid = fake_object_get_koid(obj.value());
EXPECT_OK(original_koid.status_value());
EXPECT_OK(zx_handle_replace(obj.value(), 0, &obj_repl_hnd));
EXPECT_STATUS(fake_object::FakeHandleTable().Get(obj.value()).status_value(), ZX_ERR_NOT_FOUND);
EXPECT_EQ(original_koid, fake_object_get_koid(obj_repl_hnd));
EXPECT_OK(zx_handle_close(obj_repl_hnd));
EXPECT_EQ(0u, fake_object::FakeHandleTable().size());
}
TEST_F(FakeObject, ReplaceRealHandle) {
zx::event event, event_repl;
ASSERT_OK(zx::event::create(0u, &event), "Error during event create");
zx_handle_t old_hnd = event.get();
ASSERT_OK(event.replace(0, &event_repl));
ASSERT_EQ(event.get(), ZX_HANDLE_INVALID);
ASSERT_NE(old_hnd, event_repl.get());
}
TEST_F(FakeObject, HandleClose) {
zx::result<zx_handle_t> obj = fake_object_create();
EXPECT_OK(obj.status_value());
EXPECT_NE(obj.value(), ZX_HANDLE_INVALID);
EXPECT_EQ(1u, fake_object::FakeHandleTable().size());
EXPECT_OK(zx_handle_close(obj.value()));
EXPECT_EQ(0u, fake_object::FakeHandleTable().size());
}
TEST(FakeObject, HandleCloseMany) {
// Ensure other test state was cleaned up.
ASSERT_EQ(0, fake_object::FakeHandleTable().size());
std::array<zx_handle_t, 4> handles = {ZX_HANDLE_INVALID};
zx::result<zx_handle_t> obj_res = fake_object_create();
EXPECT_OK(obj_res.status_value());
handles[0] = obj_res.value();
EXPECT_OK(zx_event_create(0, &handles[1]));
// [2] will be ZX_HANDLE_INVALID
EXPECT_OK(zx_event_create(0, &handles[3]));
ASSERT_NO_DEATH([handles]() { EXPECT_OK(zx_handle_close_many(handles.data(), handles.size())); });
}
TEST_F(FakeObject, WaitMany) {
std::array<zx_wait_item_t, 3> items;
EXPECT_OK(zx_event_create(0, &items[0].handle));
EXPECT_OK(zx_event_create(0, &items[1].handle));
zx::result<zx_handle_t> obj_res = fake_object_create();
EXPECT_OK(obj_res.status_value());
items[2].handle = obj_res.value();
// This should assert due to a fake handle being in the list of wait items.
ASSERT_DEATH(
([&items] { ASSERT_OK(zx_object_wait_many(items.data(), items.size(), ZX_TIME_INFINITE)); }),
"");
// This should behave normally due to being real events and simply return the timeout error.
ASSERT_NO_DEATH(([&items] {
ASSERT_EQ(zx_object_wait_many(items.data(), items.size() - 1,
zx_deadline_after(ZX_MSEC(1))),
ZX_ERR_TIMED_OUT);
}),
"");
for (auto& item : items) {
EXPECT_OK(zx_handle_close(item.handle));
}
}
constexpr zx_handle_t kPotentialHandle = 1;
TEST_F(FakeObject, DuplicateInvalidHandle) {
zx_handle_t obj = ZX_HANDLE_INVALID;
zx_handle_t obj_dup = ZX_HANDLE_INVALID;
// Duplicating an invalid handle should return an error but not die.
ASSERT_NO_DEATH(([obj, &obj_dup]() { EXPECT_NOT_OK(zx_handle_duplicate(obj, 0, &obj_dup)); }));
// However, a real handle will just return an error:
obj = kPotentialHandle;
ASSERT_NO_DEATH(
([obj, &obj_dup]() { EXPECT_NOT_OK(REAL_SYSCALL(zx_handle_duplicate)(obj, 0, &obj_dup)); }));
}
// Ensure objects are walked in-order when ForEach is called.
TEST_F(FakeObject, ForEach) {
std::unordered_map<zx_koid_t, bool> fake_objects;
for (int i = 0; i < 16; i++) {
zx::result<zx_handle_t> obj_res = fake_object_create();
ASSERT_TRUE(obj_res.is_ok());
zx::result<zx_koid_t> koid_res = fake_object_get_koid(obj_res.value());
ASSERT_TRUE(koid_res.is_ok());
fake_objects[koid_res.value()] = false;
}
// Walk the objects ensuring that we've seen all the koids we inserted before.
FakeHandleTable().ForEach(ZX_OBJ_TYPE_NONE, [&fake_objects](Object* obj) -> bool {
fake_objects[obj->get_koid()] = true;
return true;
});
// Ensure every object was seen in the ForEach.
for (auto& pair : fake_objects) {
EXPECT_TRUE(pair.second);
}
// Clean up the 16 objects.
FakeHandleTable().Clear();
}
// Ensure fake objects can be transmitted over a channel.
TEST_F(FakeObject, Channel) {
zx::channel in, out;
ASSERT_OK(zx::channel::create(0, &in, &out));
auto result = fake_object_create();
ASSERT_TRUE(result.is_ok());
ASSERT_TRUE(HandleTable::IsValidFakeHandle(result.value()));
ASSERT_OK(in.write(0, nullptr, 0, &result.value(), 1));
zx::handle handle;
uint32_t actual_handles = 0;
ASSERT_OK(out.read(0, nullptr, handle.reset_and_get_address(), 0, 1, nullptr, &actual_handles));
ASSERT_EQ(actual_handles, 1);
ASSERT_TRUE(HandleTable::IsValidFakeHandle(handle.get()));
}
// Verify that we drop type testing for fake objects which is a requirement for
// working with FIDL bindings.
TEST_F(FakeObject, ChannelEtc) {
zx::channel in, out;
ASSERT_OK(zx::channel::create(0, &in, &out));
zx_obj_type_t test_type = ZX_OBJ_TYPE_BTI;
auto result = fake_object_create_typed(test_type);
ASSERT_TRUE(result.is_ok());
zx_handle_t fake_obj = result.value();
ASSERT_TRUE(HandleTable::IsValidFakeHandle(fake_obj));
// We need some real objects to toss into the channel to verify we don't break them.
zx::vmo vmo;
zx::event event;
zx::eventpair ep1, ep2;
ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), /*options=*/0, &vmo));
ASSERT_OK(zx::event::create(/*options=*/0, &event));
ASSERT_OK(zx::eventpair::create(/*options=*/0, &ep1, &ep2));
// The default operation is to move handles over the channel, but this test
// uses duplication so that we don't invalidate the test handles before the
// final run where we intend to be fully successful.
std::array<zx_handle_disposition_t, 5> wr_handles{
zx_handle_disposition_t{
.operation = ZX_HANDLE_OP_DUPLICATE,
.handle = vmo.get(),
.type = ZX_OBJ_TYPE_VMO,
.rights = ZX_RIGHT_SAME_RIGHTS,
},
{
.operation = ZX_HANDLE_OP_DUPLICATE,
.handle = event.get(),
.type = ZX_OBJ_TYPE_EVENT,
.rights = ZX_RIGHT_SAME_RIGHTS,
},
// This is the fake, which will attempt to masquerade as a handle to a BTI.
{
.operation = ZX_HANDLE_OP_DUPLICATE,
.handle = fake_obj,
.type = test_type,
.rights = ZX_RIGHT_SAME_RIGHTS,
},
// Intentionally set this eventpair to the wrong type so we know type
// checking still works generally.
{
.operation = ZX_HANDLE_OP_DUPLICATE,
.handle = ep1.get(),
.type = ZX_OBJ_TYPE_VMO,
.rights = ZX_RIGHT_SAME_RIGHTS,
},
{
.operation = ZX_HANDLE_OP_DUPLICATE,
.handle = ep2.get(),
.type = ZX_OBJ_TYPE_EVENTPAIR,
.rights = ZX_RIGHT_SAME_RIGHTS,
}};
ASSERT_STATUS(in.write_etc(/*flags=*/0, /*bytes=*/nullptr, /*num_bytes=*/0, wr_handles.data(),
wr_handles.size()),
ZX_ERR_WRONG_TYPE);
// The write_etc should fail due to ep1, but our fake should have not had a
// new value written to it since it was fine.
ASSERT_STATUS(wr_handles[2].result, ZX_OK);
ASSERT_STATUS(wr_handles[3].result, ZX_ERR_WRONG_TYPE);
// Fix up ep1 and try again.
wr_handles[3].type = ZX_OBJ_TYPE_EVENTPAIR;
wr_handles[3].result = ZX_OK;
// Testing MOVE this time.
for (auto& handle : wr_handles) {
handle.operation = ZX_HANDLE_OP_MOVE;
}
ASSERT_OK(in.write_etc(/*flags=*/0, /*bytes=*/nullptr, /*num_bytes=*/0, wr_handles.data(),
wr_handles.size()));
EXPECT_OK(wr_handles[0].result);
EXPECT_OK(wr_handles[1].result);
EXPECT_OK(wr_handles[2].result);
EXPECT_OK(wr_handles[3].result);
EXPECT_OK(wr_handles[4].result);
// Verify we fix the incoming handle types from VMO to their proper types.
std::array<zx_handle_info_t, 5> rd_handles;
uint32_t actual_handles;
ASSERT_OK(out.read_etc(/*flags=*/0, /*bytes=*/nullptr, rd_handles.data(), /*num_bytes=*/0,
/*num_handles=*/wr_handles.size(), /*actual_bytes=*/nullptr,
&actual_handles));
ASSERT_EQ(rd_handles.size(), actual_handles);
EXPECT_EQ(ZX_OBJ_TYPE_VMO, rd_handles[0].type);
EXPECT_EQ(ZX_OBJ_TYPE_EVENT, rd_handles[1].type);
EXPECT_EQ(test_type, rd_handles[2].type);
EXPECT_EQ(ZX_OBJ_TYPE_EVENTPAIR, rd_handles[3].type);
EXPECT_EQ(ZX_OBJ_TYPE_EVENTPAIR, rd_handles[4].type);
for (auto& handle : rd_handles) {
EXPECT_OK(zx_handle_close(handle.handle));
}
}
} // namespace fake_object