<commit_before>/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///    http://www.apache.org/licenses/LICENSE-2.0
///
///    Unless required by applicable law or agreed to in writing, software
///    distributed under the License is distributed on an "AS IS" BASIS,
///    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
///    See the License for the specific language governing permissions and
///    limitations under the License.

#include "SantaDecisionManager.h"

#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);

#pragma mark Object Lifecycle

bool SantaDecisionManager::init() {
  if (!super::init()) return false;

  sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
  sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
  sdm_lock_attr_ = lck_attr_alloc_init();

  decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
  log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);

  decision_cache_ = new SantaCache<uint64_t>(10000, 2);
  vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);

  decision_dataqueue_ = IOSharedDataQueue::withEntries(
      kMaxDecisionQueueEvents, sizeof(santa_message_t));
  if (!decision_dataqueue_) return kIOReturnNoMemory;

  log_dataqueue_ = IOSharedDataQueue::withEntries(
      kMaxLogQueueEvents, sizeof(santa_message_t));
  if (!log_dataqueue_) return kIOReturnNoMemory;

  client_pid_ = 0;

  return true;
}

void SantaDecisionManager::free() {
  delete decision_cache_;
  delete vnode_pid_map_;

  if (decision_dataqueue_lock_) {
    lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
    decision_dataqueue_lock_ = nullptr;
  }

  if (log_dataqueue_lock_) {
    lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
    log_dataqueue_lock_ = nullptr;
  }

  if (sdm_lock_attr_) {
    lck_attr_free(sdm_lock_attr_);
    sdm_lock_attr_ = nullptr;
  }

  if (sdm_lock_grp_) {
    lck_grp_free(sdm_lock_grp_);
    sdm_lock_grp_ = nullptr;
  }

  if (sdm_lock_grp_attr_) {
    lck_grp_attr_free(sdm_lock_grp_attr_);
    sdm_lock_grp_attr_ = nullptr;
  }

  OSSafeReleaseNULL(decision_dataqueue_);
  OSSafeReleaseNULL(log_dataqueue_);

  super::free();
}

#pragma mark Client Management

void SantaDecisionManager::ConnectClient(pid_t pid) {
  if (!pid) return;

  client_pid_ = pid;

  // Any decisions made while the daemon wasn't
  // connected should be cleared
  ClearCache();

  failed_decision_queue_requests_ = 0;
  failed_log_queue_requests_ = 0;
}

void SantaDecisionManager::DisconnectClient(bool itDied) {
  if (client_pid_ < 1) return;
  client_pid_ = 0;

  // Ask santad to shutdown, in case it's running.
  if (!itDied) {
    auto message = new santa_message_t;
    message->action = ACTION_REQUEST_SHUTDOWN;
    PostToDecisionQueue(message);
    delete message;
    decision_dataqueue_->setNotificationPort(nullptr);
  } else {
    // If the client died, reset the data queues so when it reconnects
    // it doesn't get swamped straight away.
    lck_mtx_lock(decision_dataqueue_lock_);
    decision_dataqueue_->release();
    decision_dataqueue_ = IOSharedDataQueue::withEntries(
        kMaxDecisionQueueEvents, sizeof(santa_message_t));
    lck_mtx_unlock(decision_dataqueue_lock_);

    lck_mtx_lock(log_dataqueue_lock_);
    log_dataqueue_->release();
    log_dataqueue_ = IOSharedDataQueue::withEntries(
        kMaxLogQueueEvents, sizeof(santa_message_t));
    lck_mtx_unlock(log_dataqueue_lock_);
  }
}

bool SantaDecisionManager::ClientConnected() const {
  if (client_pid_ <= 0) return false;
  auto p = proc_find(client_pid_);
  auto is_exiting = false;
  if (p) {
    is_exiting = proc_exiting(p);
    proc_rele(p);
  }
  return (client_pid_ > 0 && !is_exiting);
}

void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
  lck_mtx_lock(decision_dataqueue_lock_);
  decision_dataqueue_->setNotificationPort(port);
  lck_mtx_unlock(decision_dataqueue_lock_);
}

void SantaDecisionManager::SetLogPort(mach_port_t port) {
  lck_mtx_lock(log_dataqueue_lock_);
  log_dataqueue_->setNotificationPort(port);
  lck_mtx_unlock(log_dataqueue_lock_);
}

IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
  return decision_dataqueue_->getMemoryDescriptor();
}

IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
  return log_dataqueue_->getMemoryDescriptor();
}

#pragma mark Listener Control

kern_return_t SantaDecisionManager::StartListener() {
  vnode_listener_ = kauth_listen_scope(
      KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
  if (!vnode_listener_) return kIOReturnInternalError;

  fileop_listener_ = kauth_listen_scope(
      KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
  if (!fileop_listener_) return kIOReturnInternalError;

  LOGD("Listeners started.");

  return kIOReturnSuccess;
}

kern_return_t SantaDecisionManager::StopListener() {
  kauth_unlisten_scope(vnode_listener_);
  vnode_listener_ = nullptr;

  kauth_unlisten_scope(fileop_listener_);
  fileop_listener_ = nullptr;

  // Wait for any active invocations to finish before returning
  do {
    IOSleep(5);
  } while (listener_invocations_);

  // Delete any cached decisions
  ClearCache();

  LOGD("Listeners stopped.");

  return kIOReturnSuccess;
}

#pragma mark Cache Management

void SantaDecisionManager::AddToCache(
    uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
  // Decision is stored in upper 8 bits, timestamp in remaining 56.
  uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);

  // If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
  // existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
  if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
    decision_cache_->remove(identifier);
  }
}

void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
  decision_cache_->remove(identifier);
}

uint64_t SantaDecisionManager::CacheCount() const {
  return decision_cache_->count();
}

void SantaDecisionManager::ClearCache() {
  decision_cache_->clear();
}

#pragma mark Decision Fetching

santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
  auto result = ACTION_UNSET;
  uint64_t decision_time = 0;

  uint64_t cache_val = decision_cache_->get(identifier);
  if (cache_val == 0) return result;

  // Decision is stored in upper 8 bits, timestamp in remaining 56.
  result = (santa_action_t)(cache_val >> 56);
  decision_time = (cache_val & ~(0xFF00000000000000));

  if (RESPONSE_VALID(result)) {
    if (result == ACTION_RESPOND_DENY) {
      auto diff_time = GetCurrentUptime();
      if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
        diff_time = 0;
      } else {
        diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
      }
      if (decision_time < diff_time) {
        decision_cache_->remove(identifier);
        return ACTION_UNSET;
      }
    }
  }

  return result;
}

santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
  auto return_action = ACTION_UNSET;

  // Wait for the daemon to respond or die.
  do {
    // Add pending request to cache, to be replaced by daemon with actual response
    AddToCache(identifier, ACTION_REQUEST_BINARY, 0);

    // Send request to daemon...
    if (!PostToDecisionQueue(message)) {
      OSIncrementAtomic(&failed_decision_queue_requests_);
      if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
        LOGE("Failed to queue more than %d requests, killing daemon",
             kMaxDecisionQueueFailures);
        proc_signal(client_pid_, SIGKILL);
        client_pid_ = 0;
      }
      LOGE("Failed to queue request for %s.", message->path);
      RemoveFromCache(identifier);
      return ACTION_ERROR;
    }

    do {
      IOSleep(kRequestLoopSleepMilliseconds);
      return_action = GetFromCache(identifier);
    } while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
  } while (!RESPONSE_VALID(return_action) && ClientConnected());

  // If response is still not valid, the daemon exited
  if (!RESPONSE_VALID(return_action)) {
    LOGE("Daemon process did not respond correctly. Allowing executions "
         "until it comes back. Executable path: %s", message->path);
    RemoveFromCache(identifier);
    return ACTION_ERROR;
  }

  return return_action;
}

santa_action_t SantaDecisionManager::FetchDecision(
    const kauth_cred_t cred,
    const vnode_t vp,
    const uint64_t vnode_id,
    const char *vnode_id_str) {
  if (!ClientConnected()) return ACTION_RESPOND_ALLOW;

  // Check to see if item is in cache
  auto return_action = GetFromCache(vnode_id);

  // If item was in cache return it.
  if (RESPONSE_VALID(return_action)) return return_action;

  // Get path
  char path[MAXPATHLEN];<commit_msg>santa-driver: Add debug logging of decision times to GetFromDaemon<commit_after>/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///    http://www.apache.org/licenses/LICENSE-2.0
///
///    Unless required by applicable law or agreed to in writing, software
///    distributed under the License is distributed on an "AS IS" BASIS,
///    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
///    See the License for the specific language governing permissions and
///    limitations under the License.

#include "SantaDecisionManager.h"

#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);

#pragma mark Object Lifecycle

bool SantaDecisionManager::init() {
  if (!super::init()) return false;

  sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
  sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
  sdm_lock_attr_ = lck_attr_alloc_init();

  decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
  log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);

  decision_cache_ = new SantaCache<uint64_t>(10000, 2);
  vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);

  decision_dataqueue_ = IOSharedDataQueue::withEntries(
      kMaxDecisionQueueEvents, sizeof(santa_message_t));
  if (!decision_dataqueue_) return kIOReturnNoMemory;

  log_dataqueue_ = IOSharedDataQueue::withEntries(
      kMaxLogQueueEvents, sizeof(santa_message_t));
  if (!log_dataqueue_) return kIOReturnNoMemory;

  client_pid_ = 0;

  return true;
}

void SantaDecisionManager::free() {
  delete decision_cache_;
  delete vnode_pid_map_;

  if (decision_dataqueue_lock_) {
    lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
    decision_dataqueue_lock_ = nullptr;
  }

  if (log_dataqueue_lock_) {
    lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
    log_dataqueue_lock_ = nullptr;
  }

  if (sdm_lock_attr_) {
    lck_attr_free(sdm_lock_attr_);
    sdm_lock_attr_ = nullptr;
  }

  if (sdm_lock_grp_) {
    lck_grp_free(sdm_lock_grp_);
    sdm_lock_grp_ = nullptr;
  }

  if (sdm_lock_grp_attr_) {
    lck_grp_attr_free(sdm_lock_grp_attr_);
    sdm_lock_grp_attr_ = nullptr;
  }

  OSSafeReleaseNULL(decision_dataqueue_);
  OSSafeReleaseNULL(log_dataqueue_);

  super::free();
}

#pragma mark Client Management

void SantaDecisionManager::ConnectClient(pid_t pid) {
  if (!pid) return;

  client_pid_ = pid;

  // Any decisions made while the daemon wasn't
  // connected should be cleared
  ClearCache();

  failed_decision_queue_requests_ = 0;
  failed_log_queue_requests_ = 0;
}

void SantaDecisionManager::DisconnectClient(bool itDied) {
  if (client_pid_ < 1) return;
  client_pid_ = 0;

  // Ask santad to shutdown, in case it's running.
  if (!itDied) {
    auto message = new santa_message_t;
    message->action = ACTION_REQUEST_SHUTDOWN;
    PostToDecisionQueue(message);
    delete message;
    decision_dataqueue_->setNotificationPort(nullptr);
  } else {
    // If the client died, reset the data queues so when it reconnects
    // it doesn't get swamped straight away.
    lck_mtx_lock(decision_dataqueue_lock_);
    decision_dataqueue_->release();
    decision_dataqueue_ = IOSharedDataQueue::withEntries(
        kMaxDecisionQueueEvents, sizeof(santa_message_t));
    lck_mtx_unlock(decision_dataqueue_lock_);

    lck_mtx_lock(log_dataqueue_lock_);
    log_dataqueue_->release();
    log_dataqueue_ = IOSharedDataQueue::withEntries(
        kMaxLogQueueEvents, sizeof(santa_message_t));
    lck_mtx_unlock(log_dataqueue_lock_);
  }
}

bool SantaDecisionManager::ClientConnected() const {
  if (client_pid_ <= 0) return false;
  auto p = proc_find(client_pid_);
  auto is_exiting = false;
  if (p) {
    is_exiting = proc_exiting(p);
    proc_rele(p);
  }
  return (client_pid_ > 0 && !is_exiting);
}

void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
  lck_mtx_lock(decision_dataqueue_lock_);
  decision_dataqueue_->setNotificationPort(port);
  lck_mtx_unlock(decision_dataqueue_lock_);
}

void SantaDecisionManager::SetLogPort(mach_port_t port) {
  lck_mtx_lock(log_dataqueue_lock_);
  log_dataqueue_->setNotificationPort(port);
  lck_mtx_unlock(log_dataqueue_lock_);
}

IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
  return decision_dataqueue_->getMemoryDescriptor();
}

IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
  return log_dataqueue_->getMemoryDescriptor();
}

#pragma mark Listener Control

kern_return_t SantaDecisionManager::StartListener() {
  vnode_listener_ = kauth_listen_scope(
      KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
  if (!vnode_listener_) return kIOReturnInternalError;

  fileop_listener_ = kauth_listen_scope(
      KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
  if (!fileop_listener_) return kIOReturnInternalError;

  LOGD("Listeners started.");

  return kIOReturnSuccess;
}

kern_return_t SantaDecisionManager::StopListener() {
  kauth_unlisten_scope(vnode_listener_);
  vnode_listener_ = nullptr;

  kauth_unlisten_scope(fileop_listener_);
  fileop_listener_ = nullptr;

  // Wait for any active invocations to finish before returning
  do {
    IOSleep(5);
  } while (listener_invocations_);

  // Delete any cached decisions
  ClearCache();

  LOGD("Listeners stopped.");

  return kIOReturnSuccess;
}

#pragma mark Cache Management

void SantaDecisionManager::AddToCache(
    uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
  // Decision is stored in upper 8 bits, timestamp in remaining 56.
  uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);

  // If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
  // existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
  if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
    decision_cache_->remove(identifier);
  }
}

void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
  decision_cache_->remove(identifier);
}

uint64_t SantaDecisionManager::CacheCount() const {
  return decision_cache_->count();
}

void SantaDecisionManager::ClearCache() {
  decision_cache_->clear();
}

#pragma mark Decision Fetching

santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
  auto result = ACTION_UNSET;
  uint64_t decision_time = 0;

  uint64_t cache_val = decision_cache_->get(identifier);
  if (cache_val == 0) return result;

  // Decision is stored in upper 8 bits, timestamp in remaining 56.
  result = (santa_action_t)(cache_val >> 56);
  decision_time = (cache_val & ~(0xFF00000000000000));

  if (RESPONSE_VALID(result)) {
    if (result == ACTION_RESPOND_DENY) {
      auto diff_time = GetCurrentUptime();
      if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
        diff_time = 0;
      } else {
        diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
      }
      if (decision_time < diff_time) {
        decision_cache_->remove(identifier);
        return ACTION_UNSET;
      }
    }
  }

  return result;
}

santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
  auto return_action = ACTION_UNSET;

#ifdef DEBUG
  clock_sec_t secs = 0;
  clock_usec_t microsecs = 0;
  clock_get_system_microtime(&secs, &microsecs);
  uint64_t uptime = (secs * 1000000) + microsecs;
#endif

  // Wait for the daemon to respond or die.
  do {
    // Add pending request to cache, to be replaced by daemon with actual response
    AddToCache(identifier, ACTION_REQUEST_BINARY, 0);

    // Send request to daemon...
    if (!PostToDecisionQueue(message)) {
      OSIncrementAtomic(&failed_decision_queue_requests_);
      if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
        LOGE("Failed to queue more than %d requests, killing daemon",
             kMaxDecisionQueueFailures);
        proc_signal(client_pid_, SIGKILL);
        client_pid_ = 0;
      }
      LOGE("Failed to queue request for %s.", message->path);
      RemoveFromCache(identifier);
      return ACTION_ERROR;
    }

    do {
      IOSleep(kRequestLoopSleepMilliseconds);
      return_action = GetFromCache(identifier);
    } while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
  } while (!RESPONSE_VALID(return_action) && ClientConnected());

  // If response is still not valid, the daemon exited
  if (!RESPONSE_VALID(return_action)) {
    LOGE("Daemon process did not respond correctly. Allowing executions "
         "until it comes back. Executable path: %s", message->path);
    RemoveFromCache(identifier);
    return ACTION_ERROR;
  }

#ifdef DEBUG
  clock_get_system_microtime(&secs, &microsecs);
  LOGD("Decision time: %4lldms (%s)",
       (((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
#endif

  return return_action;
}

santa_action_t SantaDecisionManager::FetchDecision(
    const kauth_cred_t cred,
    const vnode_t vp,
    const uint64_t vnode_id,
    const char *vnode_id_str) {
  if (!ClientConnected()) return ACTION_RESPOND_ALLOW;

  // Check to see if item is in cache
  auto return_action = GetFromCache(vnode_id);

  // If item was in cache return it.
  if (RESPONSE_VALID(return_action)) return return_action;

  // Get path
  char path[MAXPATHLEN];
{'fraction_non_alphanumeric': 0.09361656703672075, 'fraction_numerical': 0.011154995730145174, 'mean_word_length': 4.546773238602723, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4141853', 'n_tokens_mistral': 6376, 'n_tokens_neox': 6100, 'n_words': 1386}