// Copyright 2014 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 "sandbox/linux/syscall_broker/broker_permission_list.h"

#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <string>
#include <vector>

#include "base/logging.h"
#include "sandbox/linux/syscall_broker/broker_command.h"

namespace sandbox {
namespace syscall_broker {

namespace {

bool CheckCallerArgs(const char** file_to_access) {
  if (file_to_access && *file_to_access) {
    // Make sure that callers never pass a non-empty string. In case callers
    // wrongly forget to check the return value and look at the string
    // instead, this could catch bugs.
    RAW_LOG(FATAL, "*file_to_access should be NULL");
    return false;
  }
  return true;
}

}  // namespace

BrokerPermissionList::BrokerPermissionList(
    int denied_errno,
    const std::vector<BrokerFilePermission>& permissions)
    : denied_errno_(denied_errno),
      permissions_(permissions),
      num_of_permissions_(permissions.size()) {
  // The spec guarantees vectors store their elements contiguously
  // so set up a pointer to array of element so it can be used
  // in async signal safe code instead of vector operations.
  if (num_of_permissions_ > 0) {
    permissions_array_ = &permissions_[0];
  } else {
    permissions_array_ = NULL;
  }
}

BrokerPermissionList::~BrokerPermissionList() {}

// Check if calling access() should be allowed on |requested_filename| with
// mode |requested_mode|.
// Note: access() being a system call to check permissions, this can get a bit
// confusing. We're checking if calling access() should even be allowed with
// the same policy we would use for open().
// If |file_to_access| is not NULL, we will return the matching pointer from
// the whitelist. For paranoia a caller should then use |file_to_access|. See
// GetFileNameIfAllowedToOpen() for more explanation.
// return true if calling access() on this file should be allowed, false
// otherwise.
// Async signal safe if and only if |file_to_access| is NULL.
bool BrokerPermissionList::GetFileNameIfAllowedToAccess(
    const char* requested_filename,
    int requested_mode,
    const char** file_to_access) const {
  if (!CheckCallerArgs(file_to_access))
    return false;

  for (size_t i = 0; i < num_of_permissions_; i++) {
    if (permissions_array_[i].CheckAccess(requested_filename, requested_mode,
                                          file_to_access)) {
      return true;
    }
  }
  return false;
}

// Check if |requested_filename| can be opened with flags |requested_flags|.
// If |file_to_open| is not NULL, we will return the matching pointer from the
// whitelist. For paranoia, a caller should then use |file_to_open| rather
// than |requested_filename|, so that it never attempts to open an
// attacker-controlled file name, even if an attacker managed to fool the
// string comparison mechanism.
// Return true if opening should be allowed, false otherwise.
// Async signal safe if and only if |file_to_open| is NULL.
bool BrokerPermissionList::GetFileNameIfAllowedToOpen(
    const char* requested_filename,
    int requested_flags,
    const char** file_to_open,
    bool* unlink_after_open) const {
  if (!CheckCallerArgs(file_to_open))
    return false;

  for (size_t i = 0; i < num_of_permissions_; i++) {
    if (permissions_array_[i].CheckOpen(requested_filename, requested_flags,
                                        file_to_open, unlink_after_open)) {
      return true;
    }
  }
  return false;
}

bool BrokerPermissionList::GetFileNameIfAllowedToStat(
    const char* requested_filename,
    const char** file_to_stat) const {
  if (!CheckCallerArgs(file_to_stat))
    return false;

  for (size_t i = 0; i < num_of_permissions_; i++) {
    if (permissions_array_[i].CheckStat(requested_filename, file_to_stat))
      return true;
  }
  return false;
}

}  // namespace syscall_broker
}  // namespace sandbox
