| // Copyright 2019 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 "services/service_manager/service_instance.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/logging.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "services/service_manager/public/cpp/constants.h" |
| #include "services/service_manager/public/mojom/constants.mojom.h" |
| #include "services/service_manager/service_manager.h" |
| #include "services/service_manager/service_process_host.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(OS_IOS) |
| #include "sandbox/policy/mojom/sandbox.mojom.h" |
| #include "services/service_manager/service_process_launcher.h" |
| #endif // !defined(OS_IOS) |
| |
| namespace service_manager { |
| |
| namespace { |
| |
| // Returns the set of capabilities required from the target by the source. |
| std::set<std::string> GetRequiredCapabilities( |
| const Manifest::RequiredCapabilityMap& source_requirements, |
| const std::string& target_service) { |
| std::set<std::string> capabilities; |
| |
| // Start by looking for requirements specific to the target identity. |
| auto it = source_requirements.find(target_service); |
| if (it != source_requirements.end()) { |
| std::copy(it->second.begin(), it->second.end(), |
| std::inserter(capabilities, capabilities.begin())); |
| } |
| |
| // Apply wild card rules too. |
| it = source_requirements.find("*"); |
| if (it != source_requirements.end()) { |
| std::copy(it->second.begin(), it->second.end(), |
| std::inserter(capabilities, capabilities.begin())); |
| } |
| return capabilities; |
| } |
| |
| void ReportBlockedInterface(const Manifest::ServiceName& source_service_name, |
| const Manifest::ServiceName& target_service_name, |
| const std::string& target_interface_name) { |
| #if DCHECK_IS_ON() |
| // While it would not be correct to assert that this never happens (e.g. a |
| // compromised process may request invalid interfaces), we do want to |
| // effectively treat all occurrences of this branch in production code as |
| // bugs that must be fixed. This crash allows such bugs to be caught in |
| // testing rather than relying on easily overlooked log messages. |
| NOTREACHED() |
| #else |
| LOG(ERROR) |
| #endif |
| << "The Service Manager prevented service \"" << source_service_name |
| << "\" from binding interface \"" << target_interface_name << "\"" |
| << " in target service \"" << target_service_name << "\". You probably " |
| << "need to update one or more service manifests to ensure that \"" |
| << target_service_name << "\" exposes \"" << target_interface_name |
| << "\" through a capability and that \"" << source_service_name |
| << "\" requires that capability from the \"" << target_service_name |
| << "\" service."; |
| } |
| |
| void ReportBlockedStartService(const std::string& source_service_name, |
| const std::string& target_service_name) { |
| #if DCHECK_IS_ON() |
| // See the note in ReportBlockedInterface above. |
| NOTREACHED() |
| #else |
| LOG(ERROR) |
| #endif |
| << "Service \"" << source_service_name << "\" has attempted to manually " |
| << "start service \"" << target_service_name << "\", but it is not " |
| << "sufficiently privileged to do so. You probably need to update one or " |
| << "services' manifests in order to remedy this situation."; |
| } |
| |
| bool AllowsInterface(const Manifest::RequiredCapabilityMap& source_requirements, |
| const std::string& target_name, |
| const Manifest::ExposedCapabilityMap& target_capabilities, |
| const std::string& interface_name) { |
| std::set<std::string> allowed_interfaces; |
| std::set<std::string> required_capabilities = |
| GetRequiredCapabilities(source_requirements, target_name); |
| for (const auto& capability : required_capabilities) { |
| auto it = target_capabilities.find(capability); |
| if (it != target_capabilities.end()) { |
| for (const auto& interface : it->second) |
| allowed_interfaces.insert(interface); |
| } |
| } |
| |
| bool allowed = |
| allowed_interfaces.count("*") || allowed_interfaces.count(interface_name); |
| return allowed; |
| } |
| |
| } // namespace |
| |
| ServiceInstance::ServiceInstance( |
| service_manager::ServiceManager* service_manager, |
| const Identity& identity, |
| const Manifest& manifest) |
| : service_manager_(service_manager), |
| identity_(identity), |
| manifest_(manifest), |
| can_contact_all_services_(manifest_.required_capabilities.count("*") == |
| 1) { |
| DCHECK(identity_.IsValid()); |
| } |
| |
| ServiceInstance::~ServiceInstance() { |
| // The instance may have already been stopped prior to destruction if the |
| // ServiceManager itself is being torn down. |
| if (!stopped_) |
| Stop(); |
| } |
| |
| void ServiceInstance::SetPID(base::ProcessId pid) { |
| #if !defined(OS_IOS) |
| // iOS does not support base::Process and simply passes 0 here, so elide |
| // this check on that platform. |
| if (pid == base::kNullProcessId) { |
| // Destroys |this|. |
| service_manager_->DestroyInstance(this); |
| return; |
| } |
| #endif |
| pid_ = pid; |
| MaybeNotifyPidAvailable(); |
| } |
| |
| void ServiceInstance::StartWithRemote( |
| mojo::PendingRemote<mojom::Service> remote) { |
| DCHECK(!service_remote_); |
| service_remote_.Bind(std::move(remote)); |
| service_remote_.set_disconnect_handler(base::BindOnce( |
| &ServiceInstance::OnServiceDisconnected, base::Unretained(this))); |
| service_remote_->OnStart(identity_, |
| base::BindOnce(&ServiceInstance::OnStartCompleted, |
| base::Unretained(this))); |
| service_manager_->NotifyServiceCreated(*this); |
| } |
| |
| #if !defined(OS_IOS) |
| bool ServiceInstance::StartWithProcessHost( |
| std::unique_ptr<ServiceProcessHost> host, |
| sandbox::mojom::Sandbox sandbox_type) { |
| DCHECK(!service_remote_); |
| DCHECK(!process_host_); |
| |
| std::u16string display_name; |
| switch (manifest_.display_name.type) { |
| case Manifest::DisplayName::Type::kDefault: |
| display_name = |
| base::ASCIIToUTF16(base::StrCat({identity_.name(), "service"})); |
| break; |
| case Manifest::DisplayName::Type::kRawString: |
| display_name = base::ASCIIToUTF16(manifest_.display_name.raw_string); |
| break; |
| case Manifest::DisplayName::Type::kResourceId: |
| display_name = |
| l10n_util::GetStringUTF16(manifest_.display_name.resource_id); |
| break; |
| } |
| auto remote = host->Launch( |
| identity_, sandbox_type, display_name, |
| base::BindOnce(&ServiceInstance::SetPID, weak_ptr_factory_.GetWeakPtr())); |
| if (!remote) |
| return false; |
| |
| process_host_ = std::move(host); |
| StartWithRemote(std::move(remote)); |
| return true; |
| } |
| #endif // !defined(OS_IOS) |
| |
| void ServiceInstance::BindProcessMetadataReceiver( |
| mojo::PendingReceiver<mojom::ProcessMetadata> receiver) { |
| process_metadata_receiver_.Bind(std::move(receiver)); |
| } |
| |
| bool ServiceInstance::MaybeAcceptConnectionRequest( |
| const ServiceInstance& source_instance, |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle receiving_pipe, |
| mojom::BindInterfacePriority priority) { |
| if (state_ == mojom::InstanceState::kUnreachable) |
| return false; |
| |
| const Manifest& source_manifest = source_instance.manifest(); |
| const bool bindable_on_any_service = base::Contains( |
| source_manifest.interfaces_bindable_on_any_service, interface_name); |
| const bool allowed_by_capabilities = |
| AllowsInterface(source_manifest.required_capabilities, identity_.name(), |
| manifest_.exposed_capabilities, interface_name); |
| if (!bindable_on_any_service && !allowed_by_capabilities) { |
| ReportBlockedInterface(source_instance.identity().name(), identity_.name(), |
| interface_name); |
| return false; |
| } |
| |
| base::OnceClosure on_bind_interface_complete; |
| if (priority == mojom::BindInterfacePriority::kImportant) { |
| pending_service_connections_++; |
| on_bind_interface_complete = base::BindOnce( |
| &ServiceInstance::OnConnectRequestAcknowledged, base::Unretained(this)); |
| } |
| |
| service_remote_->OnBindInterface( |
| BindSourceInfo( |
| source_instance.identity(), |
| GetRequiredCapabilities(source_manifest.required_capabilities, |
| identity_.name())), |
| interface_name, std::move(receiving_pipe), |
| std::move(on_bind_interface_complete)); |
| |
| return true; |
| } |
| |
| bool ServiceInstance::CreatePackagedServiceInstance( |
| const Identity& packaged_instance_identity, |
| mojo::PendingReceiver<mojom::Service> receiver, |
| mojo::PendingRemote<mojom::ProcessMetadata> metadata) { |
| if (!service_remote_) |
| return false; |
| service_remote_->CreatePackagedServiceInstance( |
| packaged_instance_identity, std::move(receiver), std::move(metadata)); |
| return true; |
| } |
| |
| void ServiceInstance::Stop() { |
| DCHECK(!stopped_); |
| |
| // Shut down all receivers as well as the Service remote. The service should |
| // observe disconnection of its corresponding Service receiver and react by |
| // self-terminating ASAP. |
| service_remote_.reset(); |
| process_metadata_receiver_.reset(); |
| connector_receivers_.Clear(); |
| service_manager_receivers_.Clear(); |
| MarkUnreachable(); |
| |
| if (state_ == mojom::InstanceState::kCreated) |
| service_manager_->NotifyServiceFailedToStart(identity_); |
| else |
| service_manager_->OnInstanceStopped(identity_); |
| |
| stopped_ = true; |
| } |
| |
| mojom::RunningServiceInfoPtr ServiceInstance::CreateRunningServiceInfo() const { |
| return mojom::RunningServiceInfo::New(identity_, pid_, state_); |
| } |
| |
| void ServiceInstance::BindServiceManagerReceiver( |
| mojo::PendingReceiver<mojom::ServiceManager> receiver) { |
| service_manager_receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void ServiceInstance::OnStartCompleted( |
| mojo::PendingReceiver<mojom::Connector> connector_receiver, |
| mojo::PendingAssociatedReceiver<mojom::ServiceControl> control_receiver) { |
| state_ = mojom::InstanceState::kStarted; |
| if (connector_receiver.is_valid()) { |
| connector_receivers_.Add(this, std::move(connector_receiver)); |
| connector_receivers_.set_disconnect_handler(base::BindRepeating( |
| &ServiceInstance::OnConnectorDisconnected, base::Unretained(this))); |
| } |
| if (control_receiver.is_valid()) |
| control_receiver_.Bind(std::move(control_receiver)); |
| service_manager_->NotifyServiceStarted(identity_, pid_); |
| MaybeNotifyPidAvailable(); |
| } |
| |
| void ServiceInstance::OnConnectRequestAcknowledged() { |
| DCHECK_GT(pending_service_connections_, 0); |
| pending_service_connections_--; |
| } |
| |
| void ServiceInstance::MarkUnreachable() { |
| state_ = mojom::InstanceState::kUnreachable; |
| service_manager_->MakeInstanceUnreachable(this); |
| } |
| |
| void ServiceInstance::MaybeNotifyPidAvailable() { |
| // Ensure that we only notify listeners of the PID after notifying them of |
| // instance start to ensure consistent ordering of ServiceManagerListener |
| // messages pertaining to this instance. |
| if (state_ == mojom::InstanceState::kStarted && |
| pid_ != base::kNullProcessId) { |
| service_manager_->NotifyServicePIDReceived(identity_, pid_); |
| } |
| } |
| |
| void ServiceInstance::OnServiceDisconnected() { |
| service_remote_.reset(); |
| HandleServiceOrConnectorDisconnection(); |
| } |
| |
| void ServiceInstance::OnConnectorDisconnected() { |
| HandleServiceOrConnectorDisconnection(); |
| } |
| |
| void ServiceInstance::HandleServiceOrConnectorDisconnection() { |
| // As long as the Service remote is still connected, the instance remains |
| // alive and reachable. |
| if (service_remote_) |
| return; |
| |
| if (connector_receivers_.empty()) { |
| // No more connections of any kind. Destroys |this|. |
| service_manager_->DestroyInstance(this); |
| } else { |
| // The instance is no longer reachable since it has no Service remote, but |
| // we still have active Connectors which may send requests to the Service |
| // Manager. |
| MarkUnreachable(); |
| } |
| } |
| |
| bool ServiceInstance::CanConnectToOtherInstance( |
| const ServiceFilter& target_filter, |
| const absl::optional<std::string>& target_interface_name) { |
| if (target_filter.service_name().empty()) { |
| DLOG(ERROR) << "ServiceFilter has no service name."; |
| return false; |
| } |
| |
| bool skip_instance_group_check = |
| manifest_.options.instance_sharing_policy == |
| Manifest::InstanceSharingPolicy::kSingleton || |
| manifest_.options.instance_sharing_policy == |
| Manifest::InstanceSharingPolicy::kSharedAcrossGroups || |
| manifest_.options.can_connect_to_instances_in_any_group; |
| |
| if (!skip_instance_group_check && target_filter.instance_group() && |
| target_filter.instance_group() != identity_.instance_group() && |
| target_filter.instance_group() != kSystemInstanceGroup) { |
| LOG(ERROR) << "Instance " << identity_.ToString() << " attempting to " |
| << "connect to " << target_filter.service_name() << " in " |
| << "group " << target_filter.instance_group()->ToString() |
| << " without |can_connect_to_instances_in_any_group| set to " |
| << "|true|."; |
| return false; |
| } |
| if (target_filter.instance_id() && !target_filter.instance_id()->is_zero() && |
| !manifest_.options.can_connect_to_instances_with_any_id) { |
| LOG(ERROR) << "Instance " << identity_.ToString() |
| << " attempting to connect to " << target_filter.service_name() |
| << " with instance ID " |
| << target_filter.instance_id()->ToString() << " without " |
| << "|can_connect_to_instances_with_any_id| set to |true|."; |
| return false; |
| } |
| |
| if (can_contact_all_services_ || |
| !manifest_.interfaces_bindable_on_any_service.empty() || |
| manifest_.required_capabilities.find(target_filter.service_name()) != |
| manifest_.required_capabilities.end()) { |
| return true; |
| } |
| |
| if (target_interface_name) { |
| ReportBlockedInterface(identity_.name(), target_filter.service_name(), |
| *target_interface_name); |
| } else { |
| ReportBlockedStartService(identity_.name(), target_filter.service_name()); |
| } |
| |
| return false; |
| } |
| |
| void ServiceInstance::BindInterface( |
| const ServiceFilter& target_filter, |
| const std::string& interface_name, |
| mojo::ScopedMessagePipeHandle receiving_pipe, |
| mojom::BindInterfacePriority priority, |
| BindInterfaceCallback callback) { |
| if (!CanConnectToOtherInstance(target_filter, interface_name)) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED, absl::nullopt); |
| return; |
| } |
| |
| ServiceInstance* target_instance = |
| service_manager_->FindOrCreateMatchingTargetInstance(*this, |
| target_filter); |
| bool allowed = |
| target_instance && |
| target_instance->MaybeAcceptConnectionRequest( |
| *this, interface_name, std::move(receiving_pipe), priority); |
| if (!allowed) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED, absl::nullopt); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::ConnectResult::SUCCEEDED, |
| target_instance->identity()); |
| } |
| |
| void ServiceInstance::QueryService(const std::string& service_name, |
| QueryServiceCallback callback) { |
| std::string sandbox_type; |
| const bool success = service_manager_->QueryCatalog( |
| service_name, identity_.instance_group(), &sandbox_type); |
| if (success) |
| std::move(callback).Run(mojom::ServiceInfo::New(sandbox_type)); |
| else |
| std::move(callback).Run(nullptr); |
| } |
| |
| void ServiceInstance::WarmService(const ServiceFilter& target_filter, |
| WarmServiceCallback callback) { |
| if (!CanConnectToOtherInstance(target_filter, |
| absl::nullopt /* interface_name */)) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED, absl::nullopt); |
| return; |
| } |
| |
| ServiceInstance* target_instance = |
| service_manager_->FindOrCreateMatchingTargetInstance(*this, |
| target_filter); |
| if (!target_instance) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED, absl::nullopt); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::ConnectResult::SUCCEEDED, |
| target_instance->identity()); |
| } |
| |
| void ServiceInstance::RegisterServiceInstance( |
| const Identity& identity, |
| mojo::ScopedMessagePipeHandle service_remote_handle, |
| mojo::PendingReceiver<mojom::ProcessMetadata> metadata_receiver, |
| RegisterServiceInstanceCallback callback) { |
| auto target_filter = ServiceFilter::ForExactIdentity(identity); |
| if (!CanConnectToOtherInstance(target_filter, |
| absl::nullopt /* interface_name */)) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED); |
| return; |
| } |
| |
| mojo::PendingRemote<mojom::Service> service_remote( |
| std::move(service_remote_handle), 0); |
| |
| if (!manifest_.options.can_register_other_service_instances) { |
| LOG(ERROR) << "Instance: " << identity_.name() << " attempting " |
| << "to register an instance for a process it created for " |
| << "target: " << identity.name() << " without " |
| << "the 'can_create_other_service_instances' option."; |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED); |
| return; |
| } |
| |
| if (service_manager_->GetExistingInstance(identity)) { |
| LOG(ERROR) << "Instance already exists: " << identity.ToString(); |
| std::move(callback).Run(mojom::ConnectResult::INVALID_ARGUMENT); |
| return; |
| } |
| |
| if (!service_manager_->RegisterService(identity, std::move(service_remote), |
| std::move(metadata_receiver))) { |
| std::move(callback).Run(mojom::ConnectResult::ACCESS_DENIED); |
| return; |
| } |
| |
| std::move(callback).Run(mojom::ConnectResult::SUCCEEDED); |
| } |
| |
| void ServiceInstance::Clone(mojo::PendingReceiver<mojom::Connector> receiver) { |
| connector_receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void ServiceInstance::RequestQuit() { |
| // Ignore quit requests when there are in-flight connection requests that the |
| // instance hasn't acknowledged yet. |
| if (pending_service_connections_) |
| return; |
| |
| // Otherwise behave as if the instance was disconnected. |
| OnServiceDisconnected(); |
| } |
| |
| void ServiceInstance::AddListener( |
| mojo::PendingRemote<mojom::ServiceManagerListener> listener) { |
| service_manager_->AddListener(std::move(listener)); |
| } |
| |
| } // namespace service_manager |