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