Commit ad910ae2 authored by Kevin Marshall's avatar Kevin Marshall Committed by Commit Bot

Fuchsia: Enable mounting handles as dirs on child processess.

This functionality will be used by the ContextProvider service to
bind an arbitrary, client-supplied directory handle to the "/data"
directory of the child Context processes.

* Adds a helper function for extracting file handles from File objects.
* Adds unit tests.
* Migrates some uses of ScopedZxHandle to zx::handle.

Bug: 850743, 852541
Change-Id: Iada1775485745124822e32b9ac81aefaff6b9059
Reviewed-on: https://chromium-review.googlesource.com/1091803
Commit-Queue: Kevin Marshall <kmarshall@chromium.org>
Reviewed-by: default avatarWez <wez@chromium.org>
Reviewed-by: default avatarSergey Ulanov <sergeyu@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567880}
parent f34bba7c
......@@ -1352,6 +1352,8 @@ jumbo_component("base") {
"fuchsia/default_job.h",
"fuchsia/fidl_interface_request.cc",
"fuchsia/fidl_interface_request.h",
"fuchsia/file_utils.cc",
"fuchsia/file_utils.h",
"fuchsia/fuchsia_logging.cc",
"fuchsia/fuchsia_logging.h",
"fuchsia/scoped_zx_handle.cc",
......
// Copyright 2018 The Chromium 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 "base/fuchsia/file_utils.h"
#include <lib/fdio/limits.h>
#include <lib/fdio/util.h>
#include <zircon/processargs.h>
#include <utility>
#include "base/files/file.h"
#include "base/fuchsia/fuchsia_logging.h"
namespace base {
namespace fuchsia {
zx::handle GetHandleFromFile(File file) {
// Unwrap the FD into |handles|. Negative result indicates failure.
zx_handle_t handles[FDIO_MAX_HANDLES] = {};
uint32_t types[FDIO_MAX_HANDLES] = {};
zx_status_t num_handles =
fdio_transfer_fd(file.GetPlatformFile(), 0, handles, types);
if (num_handles <= 0) {
DCHECK_LT(num_handles, 0);
ZX_DLOG(ERROR, num_handles) << "fdio_transfer_fd";
return zx::handle();
}
// fdio_transfer_fd() has torn-down the file-descriptor, on success.
ignore_result(file.TakePlatformFile());
// Wrap the returned handles, so they will be closed on error.
zx::handle owned_handles[FDIO_MAX_HANDLES];
for (int i = 0; i < FDIO_MAX_HANDLES; ++i)
owned_handles[i] = zx::handle(handles[i]);
// We expect a single handle, of type PA_FDIO_REMOTE.
if (num_handles != 1 || types[0] != PA_FDIO_REMOTE) {
DLOG(ERROR) << "Specified file has " << num_handles
<< " handles, and type: " << types[0];
return zx::handle();
}
return std::move(owned_handles[0]);
}
} // namespace fuchsia
} // namespace base
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_FUCHSIA_FILE_UTILS_H_
#define BASE_FUCHSIA_FILE_UTILS_H_
#include <lib/zx/handle.h>
#include "base/base_export.h"
namespace base {
class File;
namespace fuchsia {
// Gets a Zircon handle from a file or directory |path| in the process'
// namespace.
BASE_EXPORT zx::handle GetHandleFromFile(base::File file);
} // namespace fuchsia
} // namespace base
#endif // BASE_FUCHSIA_FILE_UTILS_H_
......@@ -39,6 +39,10 @@ class CommandLine;
#if defined(OS_WIN)
typedef std::vector<HANDLE> HandlesToInheritVector;
#elif defined(OS_FUCHSIA)
struct PathToTransfer {
base::FilePath path;
zx_handle_t handle;
};
struct HandleToTransfer {
uint32_t id;
zx_handle_t handle;
......@@ -202,10 +206,15 @@ struct BASE_EXPORT LaunchOptions {
FDIO_SPAWN_CLONE_JOB;
// Specifies paths to clone from the calling process' namespace into that of
// the child process. If |paths_to_map| is empty then the process will receive
// either a full copy of the parent's namespace, or an empty one, depending on
// whether FDIO_SPAWN_CLONE_NAMESPACE is set.
std::vector<FilePath> paths_to_map;
// the child process. If |paths_to_clone| is empty then the process will
// receive either a full copy of the parent's namespace, or an empty one,
// depending on whether FDIO_SPAWN_CLONE_NAMESPACE is set.
std::vector<FilePath> paths_to_clone;
// Specifies handles which will be installed as files or directories in the
// child process' namespace. Paths installed by |paths_to_clone| will be
// overridden by these entries.
std::vector<PathToTransfer> paths_to_transfer;
#endif // defined(OS_FUCHSIA)
#if defined(OS_POSIX)
......
......@@ -16,6 +16,7 @@
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/fuchsia/default_job.h"
#include "base/fuchsia/file_utils.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
......@@ -62,45 +63,6 @@ bool GetAppOutputInternal(const CommandLine& cmd_line,
return process.WaitForExit(exit_code);
}
ScopedZxHandle OpenHandleForPath(const FilePath& path) {
if (!PathExists(path)) {
DLOG(ERROR) << "Path does not exist: " << path;
return ScopedZxHandle();
}
// Open the specified |path|.
File dir(path, File::FLAG_OPEN | File::FLAG_READ);
ScopedPlatformFile scoped_fd(dir.TakePlatformFile());
// Unwrap |scoped_fd| into |handles|. Negative result indicates failure.
zx_handle_t handles[FDIO_MAX_HANDLES] = {};
uint32_t types[FDIO_MAX_HANDLES] = {};
zx_status_t num_handles =
fdio_transfer_fd(scoped_fd.get(), 0, handles, types);
if (num_handles <= 0) {
DCHECK_LT(num_handles, 0);
ZX_LOG(ERROR, num_handles) << "fdio_transfer_fd";
return ScopedZxHandle();
}
// fdio_transfer_fd() has torn-down the file-descriptor, on success.
ignore_result(scoped_fd.release());
// Wrap the returned handles, so they will be closed on error.
ScopedZxHandle owned_handles[FDIO_MAX_HANDLES];
for (int i = 0; i < FDIO_MAX_HANDLES; ++i)
owned_handles[i] = ScopedZxHandle(handles[i]);
// We expect a single handle, of type PA_FDIO_REMOTE.
if (num_handles != 1 || types[0] != PA_FDIO_REMOTE) {
LOG(ERROR) << "Path " << path.AsUTF8Unsafe() << " had " << num_handles
<< " handles, and type:" << types[0];
return ScopedZxHandle();
}
return std::move(owned_handles[0]);
}
fdio_spawn_action_t FdioSpawnAction(uint32_t action) {
fdio_spawn_action_t new_action = {};
new_action.action = action;
......@@ -147,7 +109,7 @@ Process LaunchProcess(const std::vector<std::string>& argv,
// Handles to be transferred to the child are owned by this vector, so that
// they they are closed on early-exit, and can be release()d otherwise.
std::vector<ScopedZxHandle> transferred_handles;
std::vector<zx::handle> transferred_handles;
// Add caller-supplied handles for transfer. We must do this first to ensure
// that the handles are consumed even if some later step fails.
......@@ -196,19 +158,34 @@ Process LaunchProcess(const std::vector<std::string>& argv,
// Add actions to clone handles for any specified paths into the new process'
// namespace.
std::vector<const char*> mapped_paths_cstr;
if (!options.paths_to_map.empty()) {
if (!options.paths_to_clone.empty() || !options.paths_to_transfer.empty()) {
DCHECK((options.spawn_flags & FDIO_SPAWN_CLONE_NAMESPACE) == 0);
mapped_paths_cstr.reserve(options.paths_to_map.size());
mapped_paths_cstr.reserve(options.paths_to_clone.size() +
options.paths_to_transfer.size());
transferred_handles.reserve(transferred_handles.size() +
options.paths_to_map.size());
options.paths_to_clone.size() +
options.paths_to_transfer.size());
for (const auto& path_to_transfer : options.paths_to_transfer) {
zx::handle handle(path_to_transfer.handle);
spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry(
path_to_transfer.path.value().c_str(), handle.get()));
mapped_paths_cstr.push_back(path_to_transfer.path.value().c_str());
transferred_handles.push_back(std::move(handle));
}
for (const auto& path_to_clone : options.paths_to_clone) {
zx::handle handle = fuchsia::GetHandleFromFile(
base::File(base::FilePath(path_to_clone),
base::File::FLAG_OPEN | base::File::FLAG_READ));
if (!handle) {
LOG(WARNING) << "Could not open handle for path: " << path_to_clone;
return base::Process();
}
for (auto& path_to_map : options.paths_to_map) {
ScopedZxHandle handle(OpenHandleForPath(path_to_map));
if (!handle)
return Process();
spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry(
path_to_map.value().c_str(), handle.get()));
mapped_paths_cstr.push_back(path_to_map.value().c_str());
path_to_clone.value().c_str(), handle.get()));
mapped_paths_cstr.push_back(path_to_clone.value().c_str());
transferred_handles.push_back(std::move(handle));
}
}
......
......@@ -72,6 +72,8 @@
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include "base/base_paths_fuchsia.h"
#include "base/files/scoped_temp_dir.h"
#include "base/fuchsia/file_utils.h"
#endif
namespace base {
......@@ -88,6 +90,9 @@ const char kSignalFileTerm[] = "TerminatedChildProcess.die";
#if defined(OS_FUCHSIA)
const char kSignalFileClone[] = "ClonedTmpDir.die";
const char kDataDirHasStaged[] = "DataDirHasStaged.die";
const char kFooDirHasStaged[] = "FooDirHasStaged.die";
const char kFooDirDoesNotHaveStaged[] = "FooDirDoesNotHaveStaged.die";
#endif
#if defined(OS_WIN)
......@@ -223,6 +228,100 @@ TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) {
#if defined(OS_FUCHSIA)
MULTIPROCESS_TEST_MAIN(CheckDataDirHasStaged) {
if (!PathExists(base::FilePath("/data/staged"))) {
return 1;
}
WaitToDie(ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged).c_str());
return kSuccess;
}
// Test transferred paths override cloned paths.
TEST_F(ProcessUtilTest, HandleTransfersOverrideClones) {
const std::string signal_file =
ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged);
remove(signal_file.c_str());
// Create a tempdir with "staged" as its contents.
ScopedTempDir tmpdir_with_staged;
ASSERT_TRUE(tmpdir_with_staged.CreateUniqueTempDir());
{
base::FilePath staged_file_path =
tmpdir_with_staged.GetPath().Append("staged");
base::File staged_file(staged_file_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(staged_file.created());
staged_file.Close();
}
base::LaunchOptions options;
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
// Attach the tempdir to "data", but also try to duplicate the existing "data"
// directory.
options.paths_to_clone.push_back(base::FilePath("/data"));
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.paths_to_transfer.push_back(
{FilePath("/data"),
fuchsia::GetHandleFromFile(
base::File(base::FilePath(tmpdir_with_staged.GetPath()),
base::File::FLAG_OPEN | base::File::FLAG_READ))
.release()});
// Verify from that "/data/staged" exists from the child process' perspective.
Process process(SpawnChildWithOptions("CheckDataDirHasStaged", options));
ASSERT_TRUE(process.IsValid());
SignalChildren(signal_file.c_str());
int exit_code = 42;
EXPECT_TRUE(process.WaitForExit(&exit_code));
EXPECT_EQ(kSuccess, exit_code);
}
MULTIPROCESS_TEST_MAIN(CheckMountedDir) {
if (!PathExists(base::FilePath("/foo/staged"))) {
return 1;
}
WaitToDie(ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged).c_str());
return kSuccess;
}
// Test that we can install an opaque handle in the child process' namespace.
TEST_F(ProcessUtilTest, TransferHandleToPath) {
const std::string signal_file =
ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged);
remove(signal_file.c_str());
// Create a tempdir with "staged" as its contents.
ScopedTempDir new_tmpdir;
ASSERT_TRUE(new_tmpdir.CreateUniqueTempDir());
base::FilePath staged_file_path = new_tmpdir.GetPath().Append("staged");
base::File staged_file(staged_file_path,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
ASSERT_TRUE(staged_file.created());
staged_file.Close();
// Mount the tempdir to "/foo".
zx::handle tmp_handle = fuchsia::GetHandleFromFile(
base::File(base::FilePath(new_tmpdir.GetPath()),
base::File::FLAG_OPEN | base::File::FLAG_READ));
ASSERT_TRUE(tmp_handle.is_valid());
LaunchOptions options;
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.paths_to_transfer.push_back(
{base::FilePath("/foo"), tmp_handle.release()});
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
// Verify from that "/foo/staged" exists from the child process' perspective.
Process process(SpawnChildWithOptions("CheckMountedDir", options));
ASSERT_TRUE(process.IsValid());
SignalChildren(signal_file.c_str());
int exit_code = 42;
EXPECT_TRUE(process.WaitForExit(&exit_code));
EXPECT_EQ(kSuccess, exit_code);
}
MULTIPROCESS_TEST_MAIN(CheckTmpFileExists) {
// Look through the filesystem to ensure that no other directories
// besides "tmp" are in the namespace.
......@@ -241,13 +340,13 @@ MULTIPROCESS_TEST_MAIN(CheckTmpFileExists) {
return kSuccess;
}
TEST_F(ProcessUtilTest, SelectivelyClonedDir) {
TEST_F(ProcessUtilTest, CloneTmp) {
const std::string signal_file =
ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
remove(signal_file.c_str());
LaunchOptions options;
options.paths_to_map.push_back(base::FilePath("/tmp"));
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
......@@ -260,6 +359,45 @@ TEST_F(ProcessUtilTest, SelectivelyClonedDir) {
EXPECT_EQ(kSuccess, exit_code);
}
MULTIPROCESS_TEST_MAIN(CheckMountedDirDoesNotExist) {
if (PathExists(base::FilePath("/foo"))) {
return 1;
}
WaitToDie(
ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged).c_str());
return kSuccess;
}
TEST_F(ProcessUtilTest, TransferInvalidHandleFails) {
LaunchOptions options;
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.paths_to_transfer.push_back(
{base::FilePath("/foo"), ZX_HANDLE_INVALID});
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
// Verify that the process is never constructed.
const std::string signal_file =
ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged);
remove(signal_file.c_str());
Process process(
SpawnChildWithOptions("CheckMountedDirDoesNotExist", options));
ASSERT_FALSE(process.IsValid());
}
TEST_F(ProcessUtilTest, CloneInvalidDirFails) {
const std::string signal_file =
ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
remove(signal_file.c_str());
LaunchOptions options;
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.paths_to_clone.push_back(base::FilePath("/definitely_not_a_dir"));
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
ASSERT_FALSE(process.IsValid());
}
// Test that we can clone other directories. CheckTmpFileExists will return an
// error code if it detects a directory other than "/tmp", so we can use that as
// a signal that it successfully detected another entry in the root namespace.
......@@ -269,8 +407,8 @@ TEST_F(ProcessUtilTest, CloneAlternateDir) {
remove(signal_file.c_str());
LaunchOptions options;
options.paths_to_map.push_back(base::FilePath("/tmp"));
options.paths_to_map.push_back(base::FilePath("/data"));
options.paths_to_clone.push_back(base::FilePath("/tmp"));
options.paths_to_clone.push_back(base::FilePath("/data"));
options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
......@@ -284,9 +422,9 @@ TEST_F(ProcessUtilTest, CloneAlternateDir) {
}
TEST_F(ProcessUtilTest, HandlesToTransferClosedOnSpawnFailure) {
ScopedZxHandle handles[2];
zx_status_t result =
zx_channel_create(0, handles[0].receive(), handles[1].receive());
zx::handle handles[2];
zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
handles[1].reset_and_get_address());
ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
LaunchOptions options;
......@@ -308,15 +446,16 @@ TEST_F(ProcessUtilTest, HandlesToTransferClosedOnSpawnFailure) {
}
TEST_F(ProcessUtilTest, HandlesToTransferClosedOnBadPathToMapFailure) {
ScopedZxHandle handles[2];
zx_status_t result =
zx_channel_create(0, handles[0].receive(), handles[1].receive());
zx::handle handles[2];
zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
handles[1].reset_and_get_address());
ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
LaunchOptions options;
options.handles_to_transfer.push_back({0, handles[0].get()});
options.spawn_flags = options.spawn_flags & ~FDIO_SPAWN_CLONE_NAMESPACE;
options.paths_to_map.emplace_back("💩magical_path_that_will_never_exist_ever");
options.paths_to_clone.emplace_back(
"💩magical_path_that_will_never_exist_ever");
// LaunchProces should fail to open() the path_to_map, and fail before
// fdio_spawn().
......
......@@ -27,10 +27,10 @@ void UpdateLaunchOptionsForSandbox(service_manager::SandboxType type,
if (type != service_manager::SANDBOX_TYPE_NO_SANDBOX) {
// Map /pkg (read-only files deployed from the package) and /tmp into the
// child's namespace.
options->paths_to_map.push_back(base::GetPackageRoot());
options->paths_to_clone.push_back(base::GetPackageRoot());
base::FilePath temp_dir;
base::GetTempDir(&temp_dir);
options->paths_to_map.push_back(temp_dir);
options->paths_to_clone.push_back(temp_dir);
// Clear environmental variables to better isolate the child from
// this process.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment