// Copyright 2018 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/host/file_transfer/ipc_file_operations.h" #include <cstdint> #include <memory> #include <utility> #include "base/files/file_path.h" #include "base/functional/bind.h" #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" #include "mojo/public/cpp/bindings/associated_remote.h" #include "remoting/host/mojom/desktop_session.mojom.h" #include "remoting/protocol/file_transfer_helpers.h" namespace remoting { // This is an overview of how IpcFileOperations is integrated and used in the // multi-process host architecture. Reasoning about the lifetime and ownership // of the various pieces currently requires digging through the code so this // comment block describes the relationships and pieces involved at a high-level // to help those looking to understand the code. // // The IpcFileOperations and related classes are all used in the low-privilege // network process. They handle network communication with the website client // over a WebRTC data channel and proxy those requests using Mojo to the // SessionFileOperationsHandler (and friends) which lives in the high-privilege // desktop process and handles the actual file reading and writing. // // When a new file transfer data channel is opened by the client, the // ClientSession instance on the host (running in the network process) will // create a FileTransferMessageHandler (FTMH) instance to service it. As part of // the FTMH creation, ClientSession will ask the IpcDesktopEnvironment to create // a new IpcFileOperations instance. This instance will be provided with a // WeakPtr<IpcFileOperations::RequestHandler> which is used to start a file read // or write operation in the desktop process over an existing IPC channel owned // by the DesktopSessionProxy. // // After the FTMH receives the initial message indicating the type of operation // to perform, it creates an IpcFileReader or an IpcFileWriter instance. The // IpcFile{Reader|Writer} begins an operation by calling the appropriate method // on the IpcFileOperations::RequestHandler interface. This interface is // implemented by the DesktopSessionProxy (DSP) which in turn calls the // DesktopSessionAgent (DSA) via its mojom::DesktopSessionControl remote. The // DSA passes the request to its SessionFileOperationsHandler instance which, if // successful, will create a new IPC channel for the transfer and return a // remote to the IpcFile{Reader|Writer} to allow it to proceed with the file // operation. The receiver is owned by a MojoFileReader or MojoFileWriter // instance whose lifetime is tied to the Mojo channel meaning the // MojoFile{Reader|Writer} will be destroyed when the channel is disconnected. // // The lifetime of an FTMH instance is tied to the WebRTC file transfer data // channel that it was created to service. Each data channel exists for one // transfer request, so once the operation completes, or encounters an error, // the IpcFileOperations instance and the IpcFile{Reader|Writer} it created will // be destroyed (this will also trigger destruction of a MojoFile{Reader|Writer} // in the desktop process). // // The lifetime of the DesktopSessionProxy is a bit harder to reason about as a // number of classes and callbacks hold a scoped_refptr reference to it. At the // very earliest, the DSP will be destroyed when the chromoting session is // terminated. When this occurs, the scoped_refptr in ClientSession is released // and the IpcDesktopEnvironment and IpcFileOperationsFactory are destroyed. // // Because of the objects involved, the two UaF concerns are: // - Calling into |request_handler_| after the DSP has been destroyed. // This is unlikely given that a DSP lasts for the entire session but it // could occur if the timing was just right near the end of a session. // Mitigation: |request_handler_| is wrapped in a WeakPtr and provided to each // IpcFile{Reader|Writer} instance. // - The DSP could invoke a disconnect_handler on the IpcFile{Reader|Writer} if // the file transfer request was canceled just after the operation started. // Mitigation: The disconnect_handler callback provided to the BeginFileRead // BeginFileWrite method is bound with a WeakPtr. class IpcFileOperations::IpcReader : public FileOperations::Reader { … }; class IpcFileOperations::IpcWriter : public FileOperations::Writer { … }; IpcFileOperations::IpcFileOperations( base::WeakPtr<RequestHandler> request_handler) : … { … } IpcFileOperations::~IpcFileOperations() = default; std::unique_ptr<FileOperations::Reader> IpcFileOperations::CreateReader() { … } std::unique_ptr<FileOperations::Writer> IpcFileOperations::CreateWriter() { … } IpcFileOperationsFactory::IpcFileOperationsFactory( IpcFileOperations::RequestHandler* request_handler) : … { … } IpcFileOperationsFactory::~IpcFileOperationsFactory() = default; std::unique_ptr<FileOperations> IpcFileOperationsFactory::CreateFileOperations() { … } IpcFileOperations::IpcReader::IpcReader( base::WeakPtr<RequestHandler> request_handler) : … { … } IpcFileOperations::IpcReader::~IpcReader() = default; void IpcFileOperations::IpcReader::Open(OpenCallback callback) { … } void IpcFileOperations::IpcReader::ReadChunk(std::size_t size, ReadCallback callback) { … } const base::FilePath& IpcFileOperations::IpcReader::filename() const { … } std::uint64_t IpcFileOperations::IpcReader::size() const { … } FileOperations::State IpcFileOperations::IpcReader::state() const { … } void IpcFileOperations::IpcReader::OnChannelDisconnected() { … } base::WeakPtr<IpcFileOperations::IpcReader> IpcFileOperations::IpcReader::GetWeakPtr() { … } void IpcFileOperations::IpcReader::OnOpenResult( mojom::BeginFileReadResultPtr result) { … } void IpcFileOperations::IpcReader::OnReadResult( const protocol::FileTransferResult<std::vector<std::uint8_t>>& result) { … } IpcFileOperations::IpcWriter::IpcWriter( base::WeakPtr<RequestHandler> request_handler) : … { … } IpcFileOperations::IpcWriter::~IpcWriter() = default; void IpcFileOperations::IpcWriter::Open(const base::FilePath& filename, Callback callback) { … } void IpcFileOperations::IpcWriter::WriteChunk(std::vector<std::uint8_t> data, Callback callback) { … } void IpcFileOperations::IpcWriter::Close(Callback callback) { … } FileOperations::State IpcFileOperations::IpcWriter::state() const { … } void IpcFileOperations::IpcWriter::OnChannelDisconnected() { … } base::WeakPtr<IpcFileOperations::IpcWriter> IpcFileOperations::IpcWriter::GetWeakPtr() { … } void IpcFileOperations::IpcWriter::OnOpenResult( mojom::BeginFileWriteResultPtr result) { … } void IpcFileOperations::IpcWriter::OnOperationResult( const std::optional<::remoting::protocol::FileTransfer_Error>& error) { … } void IpcFileOperations::IpcWriter::OnCloseResult( const std::optional<::remoting::protocol::FileTransfer_Error>& error) { … } } // namespace remoting