|  | // 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 |