blob: 6d9bf8f07c769284c4b6479d14463a8835444a88 [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 <fidl/fuchsia.io/cpp/wire_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fidl/cpp/wire/vector_view.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <sys/mman.h>
#include <zircon/errors.h>
#include <zircon/limits.h>
#include <zircon/rights.h>
#include <zircon/syscalls/object.h>
#include <algorithm>
#include <cerrno>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
namespace {
struct Context {
zx::vmo vmo;
bool supports_read_at;
bool supports_seek;
bool supports_get_backing_memory;
size_t content_size; // Must be <= zx_system_get_page_size().
fuchsia_io::wire::VmoFlags last_flags;
};
class TestServer final : public fidl::testing::WireTestBase<fuchsia_io::File> {
public:
explicit TestServer(Context* context) : context(context) {}
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {
ADD_FAILURE("%s should not be called", name.c_str());
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Query(QueryCompleter::Sync& completer) final {
const std::string_view kProtocol = fuchsia_io::wire::kFileProtocolName;
// TODO(https://fxbug.dev/42052765): avoid the const cast.
uint8_t* data = reinterpret_cast<uint8_t*>(const_cast<char*>(kProtocol.data()));
completer.Reply(fidl::VectorView<uint8_t>::FromExternal(data, kProtocol.size()));
}
void Close(CloseCompleter::Sync& completer) override {
completer.ReplySuccess();
completer.Close(ZX_OK);
}
void Describe(DescribeCompleter::Sync& completer) override { completer.Reply({}); }
void GetAttr(GetAttrCompleter::Sync& completer) override {
completer.Reply(ZX_OK, {
.id = 5,
.content_size = context->content_size,
.storage_size = zx_system_get_page_size(),
.link_count = 1,
});
}
void ReadAt(ReadAtRequestView request, ReadAtCompleter::Sync& completer) override {
if (!context->supports_read_at) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
if (request->offset >= context->content_size) {
completer.ReplySuccess(fidl::VectorView<uint8_t>());
return;
}
size_t actual = std::min(request->count, context->content_size - request->offset);
std::vector<uint8_t> buffer(zx_system_get_page_size());
zx_status_t status = context->vmo.read(buffer.data(), request->offset, actual);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(buffer.data(), actual));
}
void Seek(SeekRequestView request, SeekCompleter::Sync& completer) override {
if (!context->supports_seek) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
completer.ReplySuccess(0);
}
void GetBackingMemory(GetBackingMemoryRequestView request,
GetBackingMemoryCompleter::Sync& completer) override {
context->last_flags = request->flags;
if (!context->supports_get_backing_memory) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY;
rights |= (request->flags & fuchsia_io::wire::VmoFlags::kRead) ? ZX_RIGHT_READ : 0;
rights |= (request->flags & fuchsia_io::wire::VmoFlags::kWrite) ? ZX_RIGHT_WRITE : 0;
rights |= (request->flags & fuchsia_io::wire::VmoFlags::kExecute) ? ZX_RIGHT_EXECUTE : 0;
zx_status_t status = ZX_OK;
zx::vmo result;
if (request->flags & fuchsia_io::wire::VmoFlags::kPrivateClone) {
rights |= ZX_RIGHT_SET_PROPERTY;
uint32_t options = ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE;
if (request->flags & fuchsia_io::wire::VmoFlags::kExecute) {
// Creating a SNAPSHOT_AT_LEAST_ON_WRITE child removes ZX_RIGHT_EXECUTE even if the parent
// VMO has it, but NO_WRITE changes this behavior so that the new handle doesn't have WRITE
// and preserves EXECUTE.
options |= ZX_VMO_CHILD_NO_WRITE;
}
status = context->vmo.create_child(options, 0, zx_system_get_page_size(), &result);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
status = result.replace(rights, &result);
} else {
status = context->vmo.duplicate(rights, &result);
}
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(std::move(result));
}
private:
Context* context;
};
zx_koid_t get_koid(const zx::object_base& handle) {
zx_info_handle_basic_t info;
zx_status_t status = handle.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
zx_rights_t get_rights(const zx::object_base& handle) {
zx_info_handle_basic_t info;
zx_status_t status = handle.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.rights : ZX_RIGHT_NONE;
}
bool vmo_starts_with(const zx::vmo& vmo, const char* string) {
size_t length = strlen(string);
if (length > zx_system_get_page_size()) {
return false;
}
std::vector<char> buffer(zx_system_get_page_size());
zx_status_t status = vmo.read(buffer.data(), 0, buffer.size());
if (status != ZX_OK) {
return false;
}
return strncmp(string, buffer.data(), length) == 0;
}
void create_context_vmo(size_t size, zx::vmo* out_vmo) {
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(size, 0, &vmo));
ASSERT_OK(
vmo.replace(ZX_RIGHTS_BASIC | ZX_RIGHTS_IO | ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY, &vmo));
ASSERT_OK(vmo.replace_as_executable(zx::resource(), out_vmo));
}
TEST(GetVMOTest, Remote) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread("fake-filesystem"));
async_dispatcher_t* dispatcher = loop.dispatcher();
auto endpoints = fidl::Endpoints<fuchsia_io::File>::Create();
Context context = {
.supports_get_backing_memory = true,
.content_size = 43,
};
create_context_vmo(zx_system_get_page_size(), &context.vmo);
ASSERT_OK(context.vmo.write("abcd", 0, 4));
fidl::BindServer(dispatcher, std::move(endpoints.server), std::make_unique<TestServer>(&context));
int raw_fd = -1;
ASSERT_OK(fdio_fd_create(endpoints.client.channel().release(), &raw_fd));
fbl::unique_fd fd(raw_fd);
zx_rights_t expected_rights =
ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_READ;
zx::vmo received;
EXPECT_OK(fdio_get_vmo_exact(fd.get(), received.reset_and_get_address()));
EXPECT_EQ(get_koid(context.vmo), get_koid(received));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_EQ(fuchsia_io::wire::VmoFlags::kRead | fuchsia_io::wire::VmoFlags::kSharedBuffer,
context.last_flags);
context.last_flags = {};
// The rest of these tests exercise methods which use VmoFlags::PRIVATE_CLONE, in which case the
// returned rights should also include ZX_RIGHT_SET_PROPERTY.
expected_rights |= ZX_RIGHT_SET_PROPERTY;
EXPECT_OK(fdio_get_vmo_clone(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_EQ(fuchsia_io::wire::VmoFlags::kRead | fuchsia_io::wire::VmoFlags::kPrivateClone,
context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = {};
EXPECT_OK(fdio_get_vmo_copy(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_EQ(fuchsia_io::wire::VmoFlags::kRead | fuchsia_io::wire::VmoFlags::kPrivateClone,
context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = {};
EXPECT_OK(fdio_get_vmo_exec(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_EQ(get_rights(received), expected_rights | ZX_RIGHT_EXECUTE);
EXPECT_EQ(fuchsia_io::wire::VmoFlags::kRead | fuchsia_io::wire::VmoFlags::kExecute |
fuchsia_io::wire::VmoFlags::kPrivateClone,
context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = {};
context.supports_get_backing_memory = false;
context.supports_read_at = true;
EXPECT_OK(fdio_get_vmo_copy(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_EQ(fuchsia_io::wire::VmoFlags::kRead | fuchsia_io::wire::VmoFlags::kPrivateClone,
context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = {};
}
// Verify that mmap works with PROT_EXEC. This test is here instead of fdio_mmap.cc since we need
// a file handle that supports execute rights, which the fake filesystem server above handles.
TEST(MmapFileTest, ProtExecWorks) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread("fake-filesystem"));
async_dispatcher_t* dispatcher = loop.dispatcher();
auto endpoints = fidl::Endpoints<fuchsia_io::File>::Create();
Context context = {
.supports_get_backing_memory = true,
.content_size = 43,
};
create_context_vmo(zx_system_get_page_size(), &context.vmo);
ASSERT_OK(context.vmo.write("abcd", 0, 4));
fidl::BindServer(dispatcher, std::move(endpoints.server), std::make_unique<TestServer>(&context));
int raw_fd = -1;
ASSERT_OK(fdio_fd_create(endpoints.client.channel().release(), &raw_fd));
fbl::unique_fd fd(raw_fd);
// Make sure we can obtain an executable VMO from the underlying fd otherwise the test is invalid.
{
zx::vmo received;
ASSERT_OK(
fdio_get_vmo_exec(fd.get(), received.reset_and_get_address()),
"File must support obtaining executable VMO to backing memory for test case to be valid!");
}
// Attempt to mmap some bytes from the fd using PROT_EXEC.
size_t len = 4;
off_t fd_off = 0;
ASSERT_NE(nullptr, mmap(nullptr, len, PROT_READ | PROT_EXEC, MAP_SHARED, fd.get(), fd_off),
"mmap failed: %s", strerror(errno));
}
} // namespace