blob: 6e38ca53d59d5cfcfd15bafa6bc19b3b0b8becad [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 <fuchsia/io/llcpp/fidl.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-async/cpp/bind.h>
#include <lib/fidl/llcpp/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 <cstdlib>
#include <string>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
// We redeclare _mmap_file because it is implemented as part of fdio and we care
// about its behavior with respect to other things it calls within fdio. The
// canonical declaration of this function lives in
// zircon/third_party/ulib/musl/src/internal/stdio_impl.h, but including that
// header is fraught. The implementation in fdio just declares and exports the
// symbol inline, so I think it's reasonable for this test to declare it itself
// and depend on it the same way musl does.
extern "C" zx_status_t _mmap_file(size_t offset, size_t len, zx_vm_option_t zx_options, int flags,
int fd, off_t fd_off, uintptr_t* out);
namespace {
namespace fuchsia_io = ::llcpp::fuchsia::io;
struct Context {
zx::vmo vmo;
bool is_vmofile;
bool supports_read_at;
bool supports_seek;
bool supports_get_buffer;
size_t content_size; // Must be <= ZX_PAGE_SIZE.
uint32_t last_flags;
};
class TestServer final : public fuchsia_io::File::Interface {
public:
TestServer(Context* context) : context(context) {}
void Clone(uint32_t flags, zx::channel object, CloneCompleter::Sync& completer) override {}
void Close(CloseCompleter::Sync& completer) override { completer.Reply(ZX_OK); }
void Describe(DescribeCompleter::Sync& completer) override {
if (context->is_vmofile) {
zx::vmo vmo;
zx_status_t status = context->vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
return;
}
fuchsia_io::Vmofile vmofile;
vmofile.vmo = std::move(vmo);
vmofile.offset = 0;
vmofile.length = context->content_size;
completer.Reply(fuchsia_io::NodeInfo::WithVmofile(fidl::unowned_ptr(&vmofile)));
} else {
fuchsia_io::FileObject fo;
completer.Reply(fuchsia_io::NodeInfo::WithFile(fidl::unowned_ptr(&fo)));
}
}
void Sync(SyncCompleter::Sync& completer) override {}
void GetAttr(GetAttrCompleter::Sync& completer) override {
fuchsia_io::NodeAttributes attributes;
attributes.id = 5;
attributes.content_size = context->content_size;
attributes.storage_size = ZX_PAGE_SIZE;
attributes.link_count = 1;
completer.Reply(ZX_OK, std::move(attributes));
}
void SetAttr(uint32_t flags, fuchsia_io::NodeAttributes attribute,
SetAttrCompleter::Sync& completer) override {}
void Read(uint64_t count, ReadCompleter::Sync& completer) override {}
void ReadAt(uint64_t count, uint64_t offset, ReadAtCompleter::Sync& completer) override {
if (!context->supports_read_at) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, fidl::VectorView<uint8_t>());
return;
}
if (offset >= context->content_size) {
completer.Reply(ZX_OK, fidl::VectorView<uint8_t>());
return;
}
size_t actual = std::min(count, context->content_size - offset);
uint8_t buffer[ZX_PAGE_SIZE];
zx_status_t status = context->vmo.read(buffer, offset, actual);
if (status != ZX_OK) {
completer.Reply(status, fidl::VectorView<uint8_t>());
return;
}
completer.Reply(ZX_OK, fidl::VectorView(fidl::unowned_ptr(buffer), actual));
}
void Write(fidl::VectorView<uint8_t> data, WriteCompleter::Sync& completer) override {}
void WriteAt(fidl::VectorView<uint8_t> data, uint64_t offset,
WriteAtCompleter::Sync& completer) override {}
void Seek(int64_t offset, fuchsia_io::SeekOrigin start, SeekCompleter::Sync& completer) override {
if (!context->supports_seek) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, 0);
}
completer.Reply(ZX_OK, 0);
}
void Truncate(uint64_t length, TruncateCompleter::Sync& completer) override {}
void GetFlags(GetFlagsCompleter::Sync& completer) override {}
void SetFlags(uint32_t flags, SetFlagsCompleter::Sync& completer) override {}
void GetBuffer(uint32_t flags, GetBufferCompleter::Sync& completer) override {
context->last_flags = flags;
if (!context->supports_get_buffer) {
completer.Reply(ZX_ERR_NOT_SUPPORTED, nullptr);
return;
}
llcpp::fuchsia::mem::Buffer buffer = {};
buffer.size = context->content_size;
zx_rights_t rights = ZX_RIGHTS_BASIC | ZX_RIGHT_MAP | ZX_RIGHT_GET_PROPERTY;
rights |= (flags & fuchsia_io::VMO_FLAG_READ) ? ZX_RIGHT_READ : 0;
rights |= (flags & fuchsia_io::VMO_FLAG_WRITE) ? ZX_RIGHT_WRITE : 0;
rights |= (flags & fuchsia_io::VMO_FLAG_EXEC) ? ZX_RIGHT_EXECUTE : 0;
zx_status_t status = ZX_OK;
zx::vmo result;
if (flags & fuchsia_io::VMO_FLAG_PRIVATE) {
rights |= ZX_RIGHT_SET_PROPERTY;
uint32_t options = ZX_VMO_CHILD_COPY_ON_WRITE;
if (flags & fuchsia_io::VMO_FLAG_EXEC) {
// Creating a COPY_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_PAGE_SIZE, &result);
if (status != ZX_OK) {
completer.Reply(status, nullptr);
return;
}
status = result.replace(rights, &result);
} else {
status = context->vmo.duplicate(rights, &result);
}
if (status != ZX_OK) {
completer.Reply(status, nullptr);
return;
}
buffer.vmo = std::move(result);
completer.Reply(ZX_OK, fidl::unowned_ptr(&buffer));
}
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_PAGE_SIZE) {
return false;
}
char buffer[ZX_PAGE_SIZE];
zx_status_t status = vmo.read(buffer, 0, sizeof(buffer));
if (status != ZX_OK) {
return false;
}
return strncmp(string, buffer, 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();
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
Context context = {};
context.is_vmofile = false;
context.content_size = 43;
context.supports_get_buffer = true;
create_context_vmo(ZX_PAGE_SIZE, &context.vmo);
ASSERT_OK(context.vmo.write("abcd", 0, 4));
ASSERT_OK(fidl::BindSingleInFlightOnly(dispatcher, std::move(server),
std::make_unique<TestServer>(&context)));
int raw_fd = -1;
ASSERT_OK(fdio_fd_create(client.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::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_EXACT, context.last_flags);
context.last_flags = 0;
// The rest of these tests exercise methods which use VMO_FLAG_PRIVATE, in which case the returned
// rights should also include 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::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_PRIVATE, context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = 0;
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::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_PRIVATE, context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = 0;
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::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_EXEC | fuchsia_io::VMO_FLAG_PRIVATE,
context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = 0;
context.supports_get_buffer = 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::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_PRIVATE, context.last_flags);
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
context.last_flags = 0;
}
TEST(GetVMOTest, VMOFile) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread("fake-filesystem"));
async_dispatcher_t* dispatcher = loop.dispatcher();
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
Context context = {};
context.content_size = 43;
context.is_vmofile = true;
context.supports_seek = true;
create_context_vmo(ZX_PAGE_SIZE, &context.vmo);
ASSERT_OK(context.vmo.write("abcd", 0, 4));
ASSERT_OK(fidl::BindSingleInFlightOnly(dispatcher, std::move(server),
std::make_unique<TestServer>(&context)));
int raw_fd = -1;
ASSERT_OK(fdio_fd_create(client.release(), &raw_fd));
fbl::unique_fd fd(raw_fd);
context.supports_seek = false;
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);
// The rest of these tests exercise methods which use VMO_FLAG_PRIVATE, in which case the returned
// rights should also include 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_TRUE(vmo_starts_with(received, "abcd"));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_OK(fdio_get_vmo_copy(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
EXPECT_EQ(get_rights(received), expected_rights);
EXPECT_OK(fdio_get_vmo_exec(fd.get(), received.reset_and_get_address()));
EXPECT_NE(get_koid(context.vmo), get_koid(received));
EXPECT_TRUE(vmo_starts_with(received, "abcd"));
EXPECT_EQ(get_rights(received), expected_rights | ZX_RIGHT_EXECUTE);
}
// Verify that mmap (or rather the internal fdio function used to implement mmap, _mmap_file, works
// with PROT_EXEC).
TEST(MmapFileTest, ProtExecWorks) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread("fake-filesystem"));
async_dispatcher_t* dispatcher = loop.dispatcher();
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
Context context = {};
context.is_vmofile = false;
context.content_size = 43;
context.supports_get_buffer = true;
create_context_vmo(ZX_PAGE_SIZE, &context.vmo);
ASSERT_OK(context.vmo.write("abcd", 0, 4));
ASSERT_OK(fidl::BindSingleInFlightOnly(dispatcher, std::move(server),
std::make_unique<TestServer>(&context)));
int raw_fd = -1;
ASSERT_OK(fdio_fd_create(client.release(), &raw_fd));
fbl::unique_fd fd(raw_fd);
size_t offset = 0;
size_t len = 4;
off_t fd_off = 0;
zx_vm_option_t zx_options = PROT_READ | PROT_EXEC;
uintptr_t ptr;
ASSERT_OK(_mmap_file(offset, len, zx_options, MAP_SHARED, fd.get(), fd_off, &ptr));
EXPECT_EQ(context.last_flags, fuchsia_io::VMO_FLAG_READ | fuchsia_io::VMO_FLAG_EXEC);
}
} // namespace