diff options
Diffstat (limited to 'chromium/chrome/common/service_process_util_posix.cc')
-rw-r--r-- | chromium/chrome/common/service_process_util_posix.cc | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/chromium/chrome/common/service_process_util_posix.cc b/chromium/chrome/common/service_process_util_posix.cc new file mode 100644 index 00000000000..698cf49c19c --- /dev/null +++ b/chromium/chrome/common/service_process_util_posix.cc @@ -0,0 +1,360 @@ +// Copyright (c) 2012 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 "chrome/common/service_process_util_posix.h" + +#include <fcntl.h> + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/location.h" +#include "base/message_loop/message_loop_current.h" +#include "base/posix/eintr_wrapper.h" +#include "base/synchronization/waitable_event.h" +#include "build/branding_buildflags.h" +#include "chrome/common/multi_process_lock.h" + +#if defined(OS_ANDROID) +#error "Should not be built on android" +#endif + +namespace { +int g_signal_socket = -1; + +#if !defined(OS_MACOSX) + +bool FilePathForMemoryName(const std::string& mem_name, base::FilePath* path) { + // mem_name will be used for a filename; make sure it doesn't + // contain anything which will confuse us. + DCHECK_EQ(std::string::npos, mem_name.find('/')); + DCHECK_EQ(std::string::npos, mem_name.find('\0')); + + base::FilePath temp_dir; + if (!GetShmemTempDir(false, &temp_dir)) + return false; + +#if BUILDFLAG(GOOGLE_CHROME_BRANDING) + static const char kShmem[] = "com.google.Chrome.shmem."; +#else + static const char kShmem[] = "org.chromium.Chromium.shmem."; +#endif + *path = temp_dir.AppendASCII(kShmem + mem_name); + return true; +} + +#endif // !defined(OS_MACOSX) + +} // namespace + +#if !defined(OS_MACOSX) + +// static +base::WritableSharedMemoryRegion +ServiceProcessState::CreateServiceProcessDataRegion(size_t size) { + base::FilePath path; + + if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path)) + return {}; + + // Make sure that the file is opened without any permission + // to other users on the system. + const mode_t kOwnerOnly = S_IRUSR | S_IWUSR; + + bool fix_size = true; + + // First, try to create the file. + base::ScopedFD fd(HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly))); + if (!fd.is_valid()) { + // If this doesn't work, try and open an existing file in append mode. + // Opening an existing file in a world writable directory has two main + // security implications: + // - Attackers could plant a file under their control, so ownership of + // the file is checked below. + // - Attackers could plant a symbolic link so that an unexpected file + // is opened, so O_NOFOLLOW is passed to open(). +#if !defined(OS_AIX) + fd.reset(HANDLE_EINTR( + open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW))); +#else + // AIX has no 64-bit support for open flags such as - + // O_CLOEXEC, O_NOFOLLOW and O_TTY_INIT. + fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_APPEND))); +#endif + // Check that the current user owns the file. + // If uid != euid, then a more complex permission model is used and this + // API is not appropriate. + const uid_t real_uid = getuid(); + const uid_t effective_uid = geteuid(); + struct stat sb; + if (fd.is_valid() && (fstat(fd.get(), &sb) != 0 || sb.st_uid != real_uid || + sb.st_uid != effective_uid)) { + DLOG(ERROR) << "Invalid owner when opening existing shared memory file."; + return {}; + } + + // An existing file was opened, so its size should not be fixed. + fix_size = false; + } + + if (fd.is_valid() && fix_size) { + // Get current size. + struct stat stat; + if (fstat(fd.get(), &stat) != 0) + return {}; + const size_t current_size = stat.st_size; + if (current_size != size) { + if (HANDLE_EINTR(ftruncate(fd.get(), size)) != 0) + return {}; + } + } + + // Everything has worked out so far, so open a read-only handle to the region + // in order to be able to create a writable region (which needs a read-only + // handle in order to convert to a read-only region. + base::ScopedFD read_only_fd( + HANDLE_EINTR(open(path.value().c_str(), O_RDONLY, kOwnerOnly))); + if (!read_only_fd.is_valid()) { + DPLOG(ERROR) << "Could not reopen shared memory region as read-only"; + return {}; + } + + base::WritableSharedMemoryRegion writable_region = + base::WritableSharedMemoryRegion::Deserialize( + base::subtle::PlatformSharedMemoryRegion::Take( + base::subtle::ScopedFDPair(std::move(fd), + std::move(read_only_fd)), + base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, size, + base::UnguessableToken::Create())); + if (!writable_region.IsValid()) { + DLOG(ERROR) << "Could not deserialize named region"; + return {}; + } + return writable_region; +} + +// static +base::ReadOnlySharedMemoryMapping +ServiceProcessState::OpenServiceProcessDataMapping(size_t size) { + base::FilePath path; + if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path)) + return {}; + + base::ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY))); + if (!fd.is_valid()) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + return {}; + } + return base::ReadOnlySharedMemoryRegion::Deserialize( + base::subtle::PlatformSharedMemoryRegion::Take( + base::subtle::ScopedFDPair(std::move(fd), base::ScopedFD()), + base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly, + size, base::UnguessableToken::Create())) + .Map(); +} + +// static +bool ServiceProcessState::DeleteServiceProcessDataRegion() { + base::FilePath path; + if (!FilePathForMemoryName(GetServiceProcessSharedMemName(), &path)) + return false; + + if (PathExists(path)) + return DeleteFile(path, false); + + // Doesn't exist, so success. + return true; +} + +#endif // !defined(OS_MACOSX) + +// Attempts to take a lock named |name|. If |waiting| is true then this will +// make multiple attempts to acquire the lock. +// Caller is responsible for ownership of the MultiProcessLock. +MultiProcessLock* TakeNamedLock(const std::string& name, bool waiting) { + std::unique_ptr<MultiProcessLock> lock = MultiProcessLock::Create(name); + if (lock == NULL) return NULL; + bool got_lock = false; + for (int i = 0; i < 10; ++i) { + if (lock->TryLock()) { + got_lock = true; + break; + } + if (!waiting) break; + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100 * i)); + } + if (!got_lock) { + lock.reset(); + } + return lock.release(); +} + +ServiceProcessTerminateMonitor::ServiceProcessTerminateMonitor( + const base::Closure& terminate_task) + : terminate_task_(terminate_task) { +} + +ServiceProcessTerminateMonitor::~ServiceProcessTerminateMonitor() { +} + +void ServiceProcessTerminateMonitor::OnFileCanReadWithoutBlocking(int fd) { + if (!terminate_task_.is_null()) { + int buffer; + int length = read(fd, &buffer, sizeof(buffer)); + if ((length == sizeof(buffer)) && (buffer == kTerminateMessage)) { + terminate_task_.Run(); + terminate_task_.Reset(); + } else if (length > 0) { + DLOG(ERROR) << "Unexpected read: " << buffer; + } else if (length == 0) { + DLOG(ERROR) << "Unexpected fd close"; + } else if (length < 0) { + DPLOG(ERROR) << "read"; + } + } +} + +void ServiceProcessTerminateMonitor::OnFileCanWriteWithoutBlocking(int fd) { + NOTIMPLEMENTED(); +} + +// "Forced" Shutdowns on POSIX are done via signals. The magic signal for +// a shutdown is SIGTERM. "write" is a signal safe function. PLOG(ERROR) is +// not, but we don't ever expect it to be called. +static void SigTermHandler(int sig, siginfo_t* info, void* uap) { + // TODO(dmaclach): add security here to make sure that we are being shut + // down by an appropriate process. + int message = ServiceProcessTerminateMonitor::kTerminateMessage; + if (write(g_signal_socket, &message, sizeof(message)) < 0) { + DPLOG(ERROR) << "write"; + } +} + +ServiceProcessState::StateData::StateData() + : watcher(FROM_HERE), set_action(false) { + memset(sockets, -1, sizeof(sockets)); + memset(&old_action, 0, sizeof(old_action)); +} + +void ServiceProcessState::StateData::SignalReady(base::WaitableEvent* signal, + bool* success) { + DCHECK(task_runner->BelongsToCurrentThread()); + DCHECK_EQ(g_signal_socket, -1); + DCHECK(!signal->IsSignaled()); + *success = base::MessageLoopCurrentForIO::Get()->WatchFileDescriptor( + sockets[0], true, base::MessagePumpForIO::WATCH_READ, &watcher, + terminate_monitor.get()); + if (!*success) { + DLOG(ERROR) << "WatchFileDescriptor"; + signal->Signal(); + return; + } + g_signal_socket = sockets[1]; + + // Set up signal handler for SIGTERM. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_sigaction = SigTermHandler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_SIGINFO; + *success = sigaction(SIGTERM, &action, &old_action) == 0; + if (!*success) { + DPLOG(ERROR) << "sigaction"; + signal->Signal(); + return; + } + + // If the old_action is not default, somebody else has installed a + // a competing handler. Our handler is going to override it so it + // won't be called. If this occurs it needs to be fixed. + DCHECK_EQ(old_action.sa_handler, SIG_DFL); + set_action = true; + +#if defined(OS_MACOSX) + *success = WatchExecutable(); + if (!*success) { + DLOG(ERROR) << "WatchExecutable"; + signal->Signal(); + return; + } +#elif defined(OS_POSIX) + initializing_lock.reset(); +#endif // OS_POSIX + signal->Signal(); +} + +ServiceProcessState::StateData::~StateData() { + // StateData is destroyed on the thread that called SignalReady() (if any) to + // satisfy the requirement that base::FilePathWatcher is destroyed in sequence + // with base::FilePathWatcher::Watch(). + DCHECK(!task_runner || task_runner->BelongsToCurrentThread()); + + // Cancel any pending file-descriptor watch before closing the descriptor. + watcher.StopWatchingFileDescriptor(); + + if (sockets[0] != -1) { + if (IGNORE_EINTR(close(sockets[0]))) { + DPLOG(ERROR) << "close"; + } + } + if (sockets[1] != -1) { + if (IGNORE_EINTR(close(sockets[1]))) { + DPLOG(ERROR) << "close"; + } + } + if (set_action) { + if (sigaction(SIGTERM, &old_action, NULL) < 0) { + DPLOG(ERROR) << "sigaction"; + } + } + g_signal_socket = -1; +} + +void ServiceProcessState::CreateState() { + DCHECK(!state_); + state_ = new StateData(); +} + +bool ServiceProcessState::SignalReady( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + const base::Closure& terminate_task) { + DCHECK(task_runner); + DCHECK(state_); + +#if !defined(OS_MACOSX) + state_->running_lock.reset(TakeServiceRunningLock(true)); + if (state_->running_lock.get() == NULL) { + return false; + } +#endif + state_->terminate_monitor.reset( + new ServiceProcessTerminateMonitor(terminate_task)); + if (pipe(state_->sockets) < 0) { + DPLOG(ERROR) << "pipe"; + return false; + } + base::WaitableEvent signal_ready( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + bool success = false; + + state_->task_runner = std::move(task_runner); + state_->task_runner->PostTask( + FROM_HERE, + base::BindOnce(&ServiceProcessState::StateData::SignalReady, + base::Unretained(state_), &signal_ready, &success)); + signal_ready.Wait(); + return success; +} + +void ServiceProcessState::TearDownState() { + if (state_ && state_->task_runner) + state_->task_runner->DeleteSoon(FROM_HERE, state_); + else + delete state_; + state_ = nullptr; +} |