blob: 1fa1342421d975e5058b74e362f6ad62db1c3c87 [file] [log] [blame]
// Copyright 2020 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 "src/lib/loader_service/loader_service.h"
#include <fuchsia/ldsvc/llcpp/fidl.h>
#include <lib/fdio/unsafe.h>
#include <lib/zx/channel.h>
#include <lib/zx/status.h>
#include <zircon/errors.h>
#include <zircon/fidl.h>
#include <ldmsg/ldmsg.h>
#include "src/lib/loader_service/loader_service_test_fixture.h"
#define ASSERT_OK(expr) ASSERT_EQ(ZX_OK, expr)
#define EXPECT_OK(expr) EXPECT_EQ(ZX_OK, expr)
namespace loader {
namespace test {
namespace {
namespace fldsvc = ::llcpp::fuchsia::ldsvc;
TEST_F(LoaderServiceTest, ConnectBindDone) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("libfoo.so", "science", true);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
{
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("science")));
// Done should cleanly shutdown connection from server side.
ASSERT_TRUE(client.Done().ok());
ASSERT_EQ(client.LoadObject("libfoo.so").status(), ZX_ERR_PEER_CLOSED);
}
// Should be able to still make new connections.
{
zx::channel client_chan, server_chan;
ASSERT_OK(zx::channel::create(0, &client_chan, &server_chan));
auto status = loader->Bind(std::move(server_chan));
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(client_chan));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("science")));
}
}
TEST_F(LoaderServiceTest, OpenConnectionsKeepLoaderAlive) {
fbl::unique_fd root_fd;
std::vector<TestDirectoryEntry> config;
config.emplace_back("libfoo.so", "science", true);
ASSERT_NO_FATAL_FAILURE(CreateTestDirectory(std::move(config), &root_fd));
// Grab the raw zx_handle_t for the root_fd's channel for use below.
fdio_t* fdio = fdio_unsafe_fd_to_io(root_fd.get());
zx::unowned_channel fd_channel(fdio_unsafe_borrow_channel(fdio));
fdio_unsafe_release(fdio);
const ::testing::TestInfo* const test_info =
::testing::UnitTest::GetInstance()->current_test_info();
auto loader =
LoaderService::Create(loader_loop().dispatcher(), std::move(root_fd), test_info->name());
fldsvc::Loader::SyncClient client1, client2;
{
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
client1 = fldsvc::Loader::SyncClient(std::move(status.value()));
}
{
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
client2 = fldsvc::Loader::SyncClient(std::move(status.value()));
}
// Drop our copy of the LoaderService. Open connections should continue working.
loader.reset();
// Should still be able to Clone any open connection.
zx::channel client_chan, server_chan;
ASSERT_OK(zx::channel::create(0, &client_chan, &server_chan));
auto result = client2.Clone(std::move(server_chan));
ASSERT_TRUE(result.ok());
ASSERT_OK(result.Unwrap()->rv);
fldsvc::Loader::SyncClient client3(std::move(client_chan));
EXPECT_NO_FATAL_FAILURE(LoadObject(client1, "libfoo.so", zx::ok("science")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client2, "libfoo.so", zx::ok("science")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client3, "libfoo.so", zx::ok("science")));
// Note this closes the channels from the client side rather than using Done, which is exercised
// in another test, since this is closer to real Loader usage.
client1 = fldsvc::Loader::SyncClient();
EXPECT_NO_FATAL_FAILURE(LoadObject(client2, "libfoo.so", zx::ok("science")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client3, "libfoo.so", zx::ok("science")));
// Connection cloned from another should work the same as connections created from LoaderService.
client2 = fldsvc::Loader::SyncClient();
EXPECT_NO_FATAL_FAILURE(LoadObject(client3, "libfoo.so", zx::ok("science")));
// Verify that the directory fd used to create the loader is properly closed once all connections
// are closed.
ASSERT_OK(fd_channel->get_info(ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
client3 = fldsvc::Loader::SyncClient();
// Must shutdown the loader_loop (which joins its thread) to ensure this is not racy. Otherwise
// the server FIDL bindings may not have handled the client-side channel closure yet.
loader_loop().Shutdown();
ASSERT_EQ(ZX_ERR_BAD_HANDLE,
fd_channel->get_info(ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
}
TEST_F(LoaderServiceTest, LoadObject) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("libfoo.so", "science", true);
config.emplace_back("libnoexec.so", "rules", false);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("science")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libmissing.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libnoexec.so", zx::error(ZX_ERR_ACCESS_DENIED)));
}
TEST_F(LoaderServiceTest, Config) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("asan/libfoo.so", "black", true);
config.emplace_back("asan/libasan_only.so", "lives", true);
config.emplace_back("libfoo.so", "must", true);
config.emplace_back("libno_san.so", "matter", true);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("must")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
ASSERT_NO_FATAL_FAILURE(Config(client, "asan", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("black")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::ok("lives")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
ASSERT_NO_FATAL_FAILURE(Config(client, "asan!", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("black")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::ok("lives")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::error(ZX_ERR_NOT_FOUND)));
ASSERT_NO_FATAL_FAILURE(Config(client, "ubsan", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("must")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
// '!' mid-string should do nothing special, same as non-existing directory
ASSERT_NO_FATAL_FAILURE(Config(client, "ubsa!n", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("must")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
ASSERT_NO_FATAL_FAILURE(Config(client, "ubsan!", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::error(ZX_ERR_NOT_FOUND)));
// Config can be reset back to default.
ASSERT_NO_FATAL_FAILURE(Config(client, "", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("must")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
}
// Each new connection to the loader service should act as if Config has not yet been called, even
// if it had been called on the connection it was cloned from.
TEST_F(LoaderServiceTest, ClonedConnectionHasDefaultConfig) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("asan/libfoo.so", "black", true);
config.emplace_back("asan/libasan_only.so", "lives", true);
config.emplace_back("libno_san.so", "matter", true);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
ASSERT_NO_FATAL_FAILURE(Config(client, "asan", zx::ok(ZX_OK)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::ok("black")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::ok("lives")));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
zx::channel client_chan, server_chan;
ASSERT_OK(zx::channel::create(0, &client_chan, &server_chan));
auto result = client.Clone(std::move(server_chan));
ASSERT_TRUE(result.ok());
ASSERT_OK(result.Unwrap()->rv);
{
fldsvc::Loader::SyncClient client(std::move(client_chan));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libfoo.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libasan_only.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "libno_san.so", zx::ok("matter")));
}
}
TEST_F(LoaderServiceTest, InvalidLoadObject) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("libfoo.so", "science", true);
config.emplace_back("asan/libfoo.so", "rules", true);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "/", zx::error(ZX_ERR_NOT_FILE)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "..", zx::error(ZX_ERR_INVALID_ARGS)));
EXPECT_NO_FATAL_FAILURE(LoadObject(client, "asan", zx::error(ZX_ERR_NOT_FILE)));
}
TEST_F(LoaderServiceTest, InvalidConfig) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
fldsvc::Loader::SyncClient client(std::move(status.value()));
EXPECT_NO_FATAL_FAILURE(Config(client, "!", zx::ok(ZX_ERR_INVALID_ARGS)));
EXPECT_NO_FATAL_FAILURE(Config(client, "/", zx::ok(ZX_ERR_INVALID_ARGS)));
EXPECT_NO_FATAL_FAILURE(Config(client, "foo/", zx::ok(ZX_ERR_INVALID_ARGS)));
EXPECT_NO_FATAL_FAILURE(Config(client, "foo/bar", zx::ok(ZX_ERR_INVALID_ARGS)));
}
// fuchsia.ldsvc.Loader is manually implemented in //zircon/system/ulib/ldmsg, and this
// implementation is the one used by our musl-based ld.so dynamic linker/loader. In other words,
// that implementation is used to send most Loader client requests. Test interop with it.
void LoadObjectLdmsg(const zx::channel& client, const char* object_name, zx::status<> expected) {
size_t req_len;
ldmsg_req_t req = {};
req.header.ordinal = LDMSG_OP_LOAD_OBJECT;
req.header.magic_number = kFidlWireFormatMagicNumberInitial;
zx_status_t status = ldmsg_req_encode(&req, &req_len, object_name, strlen(object_name));
ASSERT_OK(status);
ldmsg_rsp_t rsp = {};
zx::vmo result;
zx_channel_call_args_t call = {
.wr_bytes = &req,
.wr_handles = nullptr,
.rd_bytes = &rsp,
.rd_handles = result.reset_and_get_address(),
.wr_num_bytes = static_cast<uint32_t>(req_len),
.wr_num_handles = 0,
.rd_num_bytes = sizeof(rsp),
.rd_num_handles = 1,
};
uint32_t actual_bytes, actual_handles;
status = client.call(0, zx::time::infinite(), &call, &actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_EQ(actual_bytes, ldmsg_rsp_get_size(&rsp));
ASSERT_EQ(rsp.header.ordinal, req.header.ordinal);
EXPECT_EQ(rsp.rv, expected.status_value());
EXPECT_EQ(result.is_valid(), expected.is_ok());
}
TEST_F(LoaderServiceTest, InteropWithLdmsg_LoadObject) {
std::shared_ptr<LoaderService> loader;
std::vector<TestDirectoryEntry> config;
config.emplace_back("libfoo.so", "science", true);
config.emplace_back("libnoexec.so", "rules", false);
ASSERT_NO_FATAL_FAILURE(CreateTestLoader(std::move(config), &loader));
auto status = loader->Connect();
ASSERT_TRUE(status.is_ok());
zx::channel client = std::move(status).value();
EXPECT_NO_FATAL_FAILURE(LoadObjectLdmsg(client, "libfoo.so", zx::ok()));
EXPECT_NO_FATAL_FAILURE(LoadObjectLdmsg(client, "libmissing.so", zx::error(ZX_ERR_NOT_FOUND)));
EXPECT_NO_FATAL_FAILURE(LoadObjectLdmsg(client, "libnoexec.so", zx::error(ZX_ERR_ACCESS_DENIED)));
}
} // namespace
} // namespace test
} // namespace loader