text: // Copyright 2018 The Chromium OS 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 "oobe_config/rollback_helper.h"
  
  #include <vector>
  
  #include <fcntl.h>
  #include <grp.h>
  #include <pwd.h>
  #include <sys/stat.h>
  #include <unistd.h>
  
  #include <base/files/file_enumerator.h>
  #include <base/files/file_path.h>
  #include <base/logging.h>
  #include <base/stl_util.h>
  #include <base/strings/stringprintf.h>
  
  #include "oobe_config/oobe_config.h"
  #include "oobe_config/rollback_constants.h"
  
  namespace oobe_config {
  
  const int kDefaultPwnameLength = 1024;
  
  bool PrepareSave(const base::FilePath& root_path,
                   bool ignore_permissions_for_testing) {
    LOG(INFO) << "Delete and recreate path to save rollback data";
    // Make sure we have an empty folder where only we can write, otherwise exit.
    base::FilePath save_path = PrefixAbsolutePath(root_path, kSaveTempPath);
    if (!base::DeletePathRecursively(save_path)) {
      PLOG(ERROR) << "Couldn't delete directory " << save_path.value();
      return false;
    }
    base::File::Error error;
    if (!base::CreateDirectoryAndGetError(save_path, &error)) {
      PLOG(ERROR) << "Couldn't create directory " << save_path.value()
                  << ", error: " << error;
      return false;
    }
  
    base::FilePath rollback_data_path =
        PrefixAbsolutePath(root_path, kUnencryptedStatefulRollbackDataPath);
  
    if (!ignore_permissions_for_testing) {
      uid_t oobe_config_save_uid;
      gid_t oobe_config_save_gid;
      uid_t root_uid;
      gid_t root_gid;
      gid_t preserve_gid;
  
      LOG(INFO) << "Setting and verifying permissions for save path";
      if (!GetUidGid(kOobeConfigSaveUsername, &oobe_config_save_uid,
                     &oobe_config_save_gid)) {
        PLOG(ERROR) << "Couldn't get uid and gid of oobe_config_save.";
        return false;
      }
      if (!GetUidGid(kRootUsername, &root_uid, &root_gid)) {
        PLOG(ERROR) << "Couldn't get uid and gid of root.";
        return false;
      }
      if (!GetGid(kPreserveGroupName, &preserve_gid)) {
        // It's OK for this to fail. This group only exists on TPM2
        // devices.
        LOG(INFO) << "preserve group does not exist on this device";
        preserve_gid = -1;
      } else {
        LOG(INFO) << "preserve group is " << preserve_gid;
      }
      // chown oobe_config_save:oobe_config_save
      if (lchown(save_path.value().c_str(), oobe_config_save_uid,
                 oobe_config_save_gid) != 0) {
        PLOG(ERROR) << "Couldn't chown " << save_path.value();
        return false;
      }
      // chmod 700
      if (chmod(save_path.value().c_str(), 0700) != 0) {
        PLOG(ERROR) << "Couldn't chmod " << save_path.value();
        return false;
      }
      if (!base::VerifyPathControlledByUser(save_path, save_path,
                                            oobe_config_save_uid,
                                            {oobe_config_save_gid})) {
        LOG(ERROR) << "VerifyPathControlledByUser failed for "
                   << save_path.value();
        return false;
      }
  
      // Preparing rollback_data file.
  
      // The directory should be root-writeable only on TPM1 devices
      // and root+preserve-writeable on TPM2 devices.
      LOG(INFO) << "Verifying only root and/or preserve can write to stateful";
      std::set<gid_t> allowed_groups = {root_gid};
      if (preserve_gid != -1) {
        allowed_groups.insert(preserve_gid);
      }
      if (!base::VerifyPathControlledByUser(
              PrefixAbsolutePath(root_path, kStatefulPartition),
              rollback_data_path.DirName(), root_uid, allowed_groups)) {
        LOG(ERROR) << "VerifyPathControlledByUser failed for "
                   << rollback_data_path.DirName().value();
        return false;
      }
  
      // Create or wipe the file.
      LOG(INFO) << "Creating an empty owned rollback file and verifying";
      if (base::WriteFile(rollback_data_path, {}, 0) != 0) {
        PLOG(ERROR) << "Couldn't write " << rollback_data_path.value();
        return false;
      }
      // chown oobe_config_save:oobe_config_save
      if (lchown(rollback_data_path.value().c_str(), oobe_config_save_uid,
                 oobe_config_save_gid) != 0) {
        PLOG(ERROR) << "Couldn't chown " << rollback_data_path.value();
        return false;
      }
      // chmod 644
      if (chmod(rollback_data_path.value().c_str(), 0644) != 0) {
        PLOG(ERROR) << "Couldn't chmod " << rollback_data_path.value();
        return false;
      }
      // The file should be only writable by kOobeConfigSaveUid.
      if (!base::VerifyPathControlledByUser(
              rollback_data_path, rollback_data_path, oobe_config_save_uid,
              {oobe_config_save_gid})) {
        LOG(ERROR) << "VerifyPathControlledByUser failed for "
                   << rollback_data_path.value();
        return false;
      }
    }
  
    LOG(INFO) << "Copying data to save path";
    TryFileCopy(PrefixAbsolutePath(root_path, kInstallAttributesPath),
                save_path.Append(kInstallAttributesFileName));
    TryFileCopy(PrefixAbsolutePath(root_path, kOwnerKeyFilePath),
                save_path.Append(kOwnerKeyFileName));
    TryFileCopy(PrefixAbsolutePath(root_path, kShillDefaultProfilePath),
                save_path.Append(kShillDefaultProfileFileName));
    TryFileCopy(PrefixAbsolutePath(root_path, kOobeCompletedFile),
                save_path.Append(kOobeCompletedFileName));
    TryFileCopy(PrefixAbsolutePath(root_path, kMetricsReportingEnabledFile),
                save_path.Append(kMetricsReportingEnabledFileName));
  
    base::FileEnumerator policy_file_enumerator(
        PrefixAbsolutePath(root_path, kPolicyFileDirectory), false,
        base::FileEnumerator::FILES, kPolicyFileNamePattern);
    for (auto file = policy_file_enumerator.Next(); !file.empty();
         file = policy_file_enumerator.Next()) {
      TryFileCopy(file, save_path.Append(file.BaseName()));
    }
  
    return true;
  }
  
  bool FinishRestore(const base::FilePath& root_path,
                     bool ignore_permissions_for_testing) {
    OobeConfig oobe_config;
    if (!root_path.empty()) {
      oobe_config.set_prefix_path_for_testing(root_path);
    }
  
    if (!oobe_config.CheckSecondStage()) {
      LOG(INFO) << "Finish restore is not in stage 2.";
      return true;
    }
  
    LOG(INFO) << "Starting rollback restore stage 2.";
    base::FilePath restore_path = PrefixAbsolutePath(root_path, kRestoreTempPath);
    // Restore install attributes. /home/.shadow should already exist at OOBE
    // time. Owner should be root:root, with permissions 644.
    if (!CopyFileAndSetPermissions(
            restore_path.Append(kInstallAttributesFileName),
            PrefixAbsolutePath(root_path, kInstallAttributesPath), kRootUsername,
            0644, ignore_permissions_for_testing)) {
      LOG(ERROR) << "Couldn't restore install attributes.";
      // Need to reset the TPM if we could not restore install attributes.
      return false;
    }
  
    // Restore owner.key. /var/lib/whitelist/ should already exist at OOBE
    // time. Owner should be root:root, with permissions 604.
    if (!CopyFileAndSetPermissions(
            restore_path.Append(kOwnerKeyFileName),
            PrefixAbsolutePath(root_path, kOwnerKeyFilePath), kRootUsername, 0604,
            ignore_permissions_for_testing)) {
      LOG(ERROR) << "Couldn't restore owner.key.";
      // Need to reset the TPM if we could not restore the owner key.
      return false;
    }
  
    // Restore shill default profile. /var/cache/shill/ should already exist at
    // OOBE time. The file is restored with owner root:root, permissions 600,
    // shill will take care of setting these properly in shill-pre-start.sh.
    if (!CopyFileAndSetPermissions(
            restore_path.Append(kShillDefaultProfileFileName),
            PrefixAbsolutePath(root_path, kShillDefaultProfilePath),
            kRootUsername, 0600, ignore_permissions_for_testing)) {
      LOG(WARNING) << "Couldn't restore shill default profile.";
    }
  
    // Restore policy files. /var/lib/whitelist/ should already exist at OOBE
    // time. Owner should be root:root, with permissions 604.
    base::FileEnumerator policy_file_enumerator(
        restore_path, false, base::FileEnumerator::FILES, kPolicyFileNamePattern);
    for (auto file = policy_file_enumerator.Next(); !file.empty();
         file = policy_file_enumerator.Next()) {
      if (!CopyFileAndSetPermissions(
              file,
              PrefixAbsolutePath(root_path, kPolicyFileDirectory)
                  .Append(file.BaseName()),
              kRootUsername, 0604, ignore_permissions_for_testing)) {
        LOG(WARNING) << "Couldn't restore policy.";
      }
    }
  
    // Delete all files from the directory except the ones needed
    // for stage 3.
    LOG(INFO) << "Cleaning up rollback restore stage 1 and 2 files.";
    std::set<std::string> excluded_files;
    excluded_files.emplace(
        PrefixAbsolutePath(root_path, kFirstStageCompletedFile).value());
    excluded_files.emplace(
        PrefixAbsolutePath(root_path, kEncryptedStatefulRollbackDataPath)
            .value());
  
    CleanupRestoreFiles(root_path, excluded_files);
  
    // Indicate that the second stage completed.
    if (IsSymlink(kSecondStageCompletedFile)) {
      PLOG(ERROR) << "Couldn't create file " << kSecondStageCompletedFile.value()
                  << " as it exists as a symlink";
      return false;
    }
    oobe_config.WriteFile(kSecondStageCompletedFile, "");
    LOG(INFO) << "Rollback restore stage 2 completed.";
  
    return true;
  }
  
  void CleanupRestoreFiles(const base::FilePath& root_path,
                           const std::set<std::string>& excluded_files) {
    // Delete everything except |excluded_files| in the restore directory.
    base::FilePath restore_path = PrefixAbsolutePath(root_path, kRestoreTempPath);
    base::FileEnumerator folder_enumerator(
        restore_path, false,
        base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
    for (auto file = folder_enumerator.Next(); !file.empty();
         file = folder_enumerator.Next()) {
      if (excluded_files.count(file.value()) == 0) {
        if (!base::DeletePathRecursively(file)) {
          PLOG(ERROR) << "Couldn't delete " << file.value();
        } else {
          LOG(INFO) << "Deleted rollback data file: " << file.value();
        }
      } else {
        LOG(INFO) << "Preserving rollback data file: " << file.value();
      }
    }
  
    // Delete the original preserved data.
    base::FilePath rollback_data_file =
        PrefixAbsolutePath(root_path, kUnencryptedStatefulRollbackDataPath);
    if (!base::DeletePathRecursively(rollback_data_file)) {
      PLOG(ERROR) << "Couldn't delete " << rollback_data_file.value();
    } else {
      LOG(INFO) << "Deleted encrypted rollback data.";
    }
  }
  
  base::FilePath PrefixAbsolutePath(const base::FilePath& prefix,
                                    const base::FilePath& file_path) {
    if (prefix.empty())
      return file_path;
    DCHECK(!file_path.empty());
    DCHECK_EQ('/', file_path.value()[0]);
    return prefix.Append(file_path.value().substr(1));
  }
  
  void TryFileCopy(const base::FilePath& source,
                   const base::FilePath& destination) {
    if (!base::CopyFile(source, destination)) {
      PLOG(WARNING) << "Couldn't copy file " << source.value() << " to "
                    << destination.value();
    } else {
      LOG(INFO) << "Copied " << source.value() << " to " << destination.value();
    }
  }
  
  // TODO(mpolzer) This was a quickfix, make sure we do not follow symlinks when
  // opening files (see crbug/960109#c11).
  bool IsSymlink(const base::FilePath& path) {
    int fd = HANDLE_EINTR(open(path.value().c_str(), O_PATH));
    if (fd < 0)
      return false;
    char buf[PATH_MAX];
    ssize_t count = readlink(base::StringPrintf("/proc/self/fd/%d", fd).c_str(),
                             buf, base::size(buf));
    if (count <= 0)
      return false;
    base::FilePath real(std::string(buf, count));
    return real != path;
  }
  
  bool CopyFileAndSetPermissions(const base::FilePath& source,
                                 const base::FilePath& destination,
                                 const std::string& owner_username,
                                 mode_t permissions,
                                 bool ignore_permissions_for_testing) {
    if (!base::PathExists(source.DirName())) {
      LOG(ERROR) << "Parent path doesn't exist: " << source.DirName().value();
      return false;
    }
    if (IsSymlink(destination)) {
      PLOG(ERROR) << "Couldn't copy file " << source.value() << " to a symlink "
                  << destination.value();
      return false;
    }
    TryFileCopy(source, destination);
    if (!ignore_permissions_for_testing) {
      uid_t owner_user;
      gid_t owner_group;
      if (!GetUidGid(owner_username, &owner_user, &owner_group)) {
        PLOG(ERROR) << "Couldn't get uid and gid of user " << owner_username;
        return false;
      }
      if (lchown(destination.value().c_str(), owner_user, owner_group) != 0) {
        PLOG(ERROR) << "Couldn't chown " << destination.value();
        return false;
      }
      if (chmod(destination.value().c_str(), permissions) != 0) {
        PLOG(ERROR) << "Couldn't chmod " << destination.value();
        return false;
      }
    }
    return true;
  }
  
  bool GetUidGid(const std::string& user, uid_t* uid, gid_t* gid) {
    // Load the passwd entry.
    int user_name_length = sysconf(_SC_GETPW_R_SIZE_MAX);
    if (user_name_length == -1) {
      user_name_length = kDefaultPwnameLength;
    }
    if (user_name_length < 0) {
      return false;
    }
    passwd user_info{}, *user_infop;
    std::vector<char> user_name_buf(static_cast<size_t>(user_name_length));
    getpwnam_r(user.c_str(), &user_info, user_name_buf.data(),
               static_cast<size_t>(user_name_length), &user_infop);
  
    // NOTE: the return value can be ambiguous in the case that the user does
    // not exist. See "man getpwnam_r" for details.
    if (user_infop == nullptr) {
      return false;
    }
  
    *uid = user_info.pw_uid;
    *gid = user_info.pw_gid;
    return true;
  }
  
  bool GetGid(const std::string& group, gid_t* gid) {
    int group_name_length = sysconf(_SC_GETGR_R_SIZE_MAX);
    if (group_name_length == -1) {
      group_name_length = kDefaultPwnameLength;
    }
    if (group_name_length < 0) {
      return false;
    }
    struct group group_info {
    }, *group_infop;
    std::vector<char> group_name_buf(static_cast<size_t>(group_name_length));
    getgrnam_r(group.c_str(), &group_info, group_name_buf.data(),
               static_cast<size_t>(group_name_length), &group_infop);
  
    // NOTE: the return value can be ambiguous in the case that the user does
    // not exist. See "man getgrnam_r" for details.
    if (group_infop == nullptr) {
      return false;
    }
  
    *gid = group_info.gr_gid;
    return true;
  }
  
  }  // namespace oobe_config
  