chromium/chrome/browser/ash/dbus/vm/vm_permission_service_provider.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/dbus/vm/vm_permission_service_provider.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/ash/borealis/borealis_prefs.h"
#include "chrome/browser/ash/bruschetta/bruschetta_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/dbus/vm_permission_service/vm_permission_service.pb.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "dbus/message.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace {

base::UnguessableToken TokenFromString(const std::string& str) {
  static constexpr int kBytesPerUint64 = sizeof(uint64_t) / sizeof(uint8_t);
  static constexpr int kMaxBytesPerToken = 2 * kBytesPerUint64;

  std::vector<uint8_t> bytes;
  if (!base::HexStringToBytes(str, &bytes) || bytes.size() == 0 ||
      bytes.size() > kMaxBytesPerToken) {
    return base::UnguessableToken();
  }

  uint64_t high = 0, low = 0;
  int count = 0;
  base::ranges::for_each(base::Reversed(bytes), [&](auto byte) {
    auto* p = count < kBytesPerUint64 ? &low : &high;
    int pos = count < kBytesPerUint64 ? count : count - kBytesPerUint64;
    *p += static_cast<uint64_t>(byte) << (pos * 8);
    count++;
  });

  std::optional<base::UnguessableToken> token =
      base::UnguessableToken::Deserialize(high, low);
  if (!token.has_value()) {
    return base::UnguessableToken();
  }
  return token.value();
}

}  // namespace

namespace ash {

VmPermissionServiceProvider::VmInfo::VmInfo(std::string vm_owner_id,
                                            std::string vm_name,
                                            VmType vm_type)
    : owner_id(std::move(vm_owner_id)),
      name(std::move(vm_name)),
      type(vm_type) {}

VmPermissionServiceProvider::VmInfo::~VmInfo() = default;

VmPermissionServiceProvider::VmPermissionServiceProvider() {}

VmPermissionServiceProvider::~VmPermissionServiceProvider() = default;

VmPermissionServiceProvider::VmMap::iterator
VmPermissionServiceProvider::FindVm(const std::string& owner_id,
                                    const std::string& name) {
  return base::ranges::find_if(vms_, [&](const auto& vm) {
    return vm.second->owner_id == owner_id && vm.second->name == name;
  });
}

void VmPermissionServiceProvider::Start(
    scoped_refptr<dbus::ExportedObject> exported_object) {
  exported_object->ExportMethod(
      chromeos::kVmPermissionServiceInterface,
      chromeos::kVmPermissionServiceRegisterVmMethod,
      base::BindRepeating(&VmPermissionServiceProvider::RegisterVm,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&VmPermissionServiceProvider::OnExported,
                     weak_ptr_factory_.GetWeakPtr()));
  exported_object->ExportMethod(
      chromeos::kVmPermissionServiceInterface,
      chromeos::kVmPermissionServiceUnregisterVmMethod,
      base::BindRepeating(&VmPermissionServiceProvider::UnregisterVm,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&VmPermissionServiceProvider::OnExported,
                     weak_ptr_factory_.GetWeakPtr()));
  exported_object->ExportMethod(
      chromeos::kVmPermissionServiceInterface,
      chromeos::kVmPermissionServiceGetPermissionsMethod,
      base::BindRepeating(&VmPermissionServiceProvider::GetPermissions,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&VmPermissionServiceProvider::OnExported,
                     weak_ptr_factory_.GetWeakPtr()));
  exported_object->ExportMethod(
      chromeos::kVmPermissionServiceInterface,
      chromeos::kVmPermissionServiceSetPermissionsMethod,
      base::BindRepeating(&VmPermissionServiceProvider::SetPermissions,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&VmPermissionServiceProvider::OnExported,
                     weak_ptr_factory_.GetWeakPtr()));
}

void VmPermissionServiceProvider::OnExported(const std::string& interface_name,
                                             const std::string& method_name,
                                             bool success) {
  if (!success)
    LOG(ERROR) << "Failed to export " << interface_name << "." << method_name;
}

void VmPermissionServiceProvider::RegisterVm(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);

  dbus::MessageReader reader(method_call);
  vm_permission_service::RegisterVmRequest request;
  if (!reader.PopArrayOfBytesAsProto(&request)) {
    constexpr char error_message[] =
        "Unable to parse RegisterVmRequest from message";
    LOG(ERROR) << error_message;
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, error_message));
    return;
  }

  if (FindVm(request.owner_id(), request.name()) != vms_.end()) {
    LOG(ERROR) << "VM " << request.owner_id() << "/" << request.name()
               << " is already registered with permission service";
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "VM is already registered"));
    return;
  }

  VmInfo::VmType vm_type;
  if (request.type() == vm_permission_service::RegisterVmRequest::PLUGIN_VM) {
    vm_type = VmInfo::VmType::PluginVm;
  } else if (request.type() ==
             vm_permission_service::RegisterVmRequest::BOREALIS) {
    vm_type = VmInfo::VmType::Borealis;
  } else if (request.type() ==
             vm_permission_service::RegisterVmRequest::BRUSCHETTA) {
    vm_type = VmInfo::VmType::Bruschetta;
  } else {
    LOG(ERROR) << "Unsupported VM " << request.owner_id() << "/"
               << request.name() << " type: " << request.type();
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Unsupported VM type"));
    return;
  }

  auto vm =
      base::WrapUnique(new VmInfo(request.owner_id(), request.name(), vm_type));

  // Seed the initial set of permission. Because in the initial release we
  // only support static permissions, i.e. for changes to take effect we need
  // to re-launch the VM, we do not need to update them after this.
  UpdateVmPermissions(vm.get());

  const base::UnguessableToken token(base::UnguessableToken::Create());

  SetCameraPermission(token,
                      vm->permission_to_enabled_map[VmInfo::PermissionCamera]);

  vms_[token] = std::move(vm);

  vm_permission_service::RegisterVmResponse payload;
  payload.set_token(token.ToString());

  dbus::MessageWriter writer(response.get());
  writer.AppendProtoAsArrayOfBytes(payload);
  std::move(response_sender).Run(std::move(response));
}

void VmPermissionServiceProvider::UnregisterVm(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);

  dbus::MessageReader reader(method_call);
  vm_permission_service::UnregisterVmRequest request;
  if (!reader.PopArrayOfBytesAsProto(&request)) {
    constexpr char error_message[] =
        "Unable to parse RegisterVmRequest from message";
    LOG(ERROR) << error_message;
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, error_message));
    return;
  }

  auto iter = FindVm(request.owner_id(), request.name());
  if (iter == vms_.end()) {
    LOG(ERROR) << "VM " << request.owner_id() << "/" << request.name()
               << " is not registered with permission service";
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "VM is not registered"));
    return;
  }

  SetCameraPermission(iter->first, false);

  vms_.erase(iter);

  std::move(response_sender).Run(std::move(response));
}

void VmPermissionServiceProvider::SetPermissions(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);

  dbus::MessageReader reader(method_call);
  vm_permission_service::SetPermissionsRequest request;
  if (!reader.PopArrayOfBytesAsProto(&request)) {
    constexpr char error_message[] =
        "Unable to parse SetPermissionsRequest from message";
    LOG(ERROR) << error_message;
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, error_message));
    return;
  }

  auto iter = FindVm(request.owner_id(), request.name());
  if (iter == vms_.end()) {
    LOG(ERROR) << "VM " << request.owner_id() << "/" << request.name()
               << " is not registered with permission service";
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "VM is not registered"));
    return;
  }

  base::flat_map<VmInfo::PermissionType, bool> new_permissions(
      iter->second->permission_to_enabled_map);
  for (const auto& p : request.permissions()) {
    VmInfo::PermissionType kind;
    if (p.kind() == vm_permission_service::Permission::CAMERA) {
      kind = VmInfo::PermissionCamera;
    } else if (p.kind() == vm_permission_service::Permission::MICROPHONE) {
      kind = VmInfo::PermissionMicrophone;
    } else {
      constexpr char error_message[] = "Unknown permission type";
      LOG(ERROR) << error_message;
      std::move(response_sender)
          .Run(dbus::ErrorResponse::FromMethodCall(
              method_call, DBUS_ERROR_INVALID_ARGS, error_message));
      return;
    }

    new_permissions[kind] = p.allowed();
  }

  // Commit final version of permissions.
  iter->second->permission_to_enabled_map = std::move(new_permissions);
  SetCameraPermission(
      iter->first,
      iter->second->permission_to_enabled_map[VmInfo::PermissionCamera]);

  std::move(response_sender).Run(std::move(response));
}

void VmPermissionServiceProvider::GetPermissions(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);

  dbus::MessageReader reader(method_call);
  vm_permission_service::GetPermissionsRequest request;
  if (!reader.PopArrayOfBytesAsProto(&request)) {
    constexpr char error_message[] =
        "Unable to parse GetPermissionsRequest from message";
    LOG(ERROR) << error_message;
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, error_message));
    return;
  }

  auto token = TokenFromString(request.token());
  if (!token) {
    LOG(ERROR) << "Malformed token '" << request.token() << "'";
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Malformed token"));
    return;
  }

  auto iter = vms_.find(token);
  if (iter == vms_.end()) {
    LOG(ERROR) << "Invalid token " << token;
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Invalid token"));
    return;
  }

  vm_permission_service::GetPermissionsResponse payload;
  for (auto permission : iter->second->permission_to_enabled_map) {
    auto* p = payload.add_permissions();
    switch (permission.first) {
      case VmInfo::PermissionCamera:
        p->set_kind(vm_permission_service::Permission::CAMERA);
        break;
      case VmInfo::PermissionMicrophone:
        p->set_kind(vm_permission_service::Permission::MICROPHONE);
        break;
    }
    p->set_allowed(permission.second);
  }

  dbus::MessageWriter writer(response.get());
  writer.AppendProtoAsArrayOfBytes(payload);
  std::move(response_sender).Run(std::move(response));
}

void VmPermissionServiceProvider::UpdateVmPermissions(VmInfo* vm) {
  vm->permission_to_enabled_map.clear();
  switch (vm->type) {
    case VmInfo::PluginVm:
      UpdatePluginVmPermissions(vm);
      break;
    case VmInfo::Borealis:
      UpdateBorealisPermissions(vm);
      break;
    case VmInfo::Bruschetta:
      UpdateBruschettaPermissions(vm);
      break;
    case VmInfo::CrostiniVm:
      NOTREACHED_IN_MIGRATION();
  }
}

void VmPermissionServiceProvider::UpdatePluginVmPermissions(VmInfo* vm) {
  Profile* profile = ProfileManager::GetPrimaryUserProfile();
  if (!profile ||
      ProfileHelper::GetUserIdHashFromProfile(profile) != vm->owner_id) {
    return;
  }

  const PrefService* prefs = profile->GetPrefs();
  if (prefs->GetBoolean(prefs::kVideoCaptureAllowed)) {
    vm->permission_to_enabled_map[VmInfo::PermissionCamera] =
        prefs->GetBoolean(plugin_vm::prefs::kPluginVmCameraAllowed);
  }

  if (prefs->GetBoolean(prefs::kAudioCaptureAllowed)) {
    vm->permission_to_enabled_map[VmInfo::PermissionMicrophone] =
        prefs->GetBoolean(plugin_vm::prefs::kPluginVmMicAllowed);
  }
}

void VmPermissionServiceProvider::UpdateBorealisPermissions(VmInfo* vm) {
  Profile* profile = ProfileManager::GetPrimaryUserProfile();
  if (!profile ||
      ProfileHelper::GetUserIdHashFromProfile(profile) != vm->owner_id) {
    return;
  }

  const PrefService* prefs = profile->GetPrefs();
  if (prefs->GetBoolean(prefs::kAudioCaptureAllowed)) {
    vm->permission_to_enabled_map[VmInfo::PermissionMicrophone] =
        prefs->GetBoolean(borealis::prefs::kBorealisMicAllowed);
  }
}

void VmPermissionServiceProvider::UpdateBruschettaPermissions(VmInfo* vm) {
  Profile* profile = Profile::FromBrowserContext(
      BrowserContextHelper::Get()->GetBrowserContextByUser(
          user_manager::UserManager::Get()->GetPrimaryUser()));

  if (!profile ||
      ProfileHelper::GetUserIdHashFromProfile(profile) != vm->owner_id) {
    return;
  }

  const PrefService* prefs = profile->GetPrefs();
  if (prefs->GetBoolean(prefs::kAudioCaptureAllowed)) {
    vm->permission_to_enabled_map[VmInfo::PermissionMicrophone] =
        prefs->GetBoolean(bruschetta::prefs::kBruschettaMicAllowed);
  }
}

void VmPermissionServiceProvider::SetCameraPermission(
    base::UnguessableToken token,
    bool enabled) {
  if (enabled) {
    media::CameraHalDispatcherImpl::GetInstance()->RegisterPluginVmToken(token);
  } else {
    media::CameraHalDispatcherImpl::GetInstance()->UnregisterPluginVmToken(
        token);
  }
}

}  // namespace ash