|
|
@@ -26,9 +26,11 @@ namespace fs = boost::filesystem; |
|
|
|
|
|
namespace osquery { |
|
|
|
|
|
static const int kINotifyMLatency = 200; |
|
|
static const uint32_t kINotifyBufferSize = |
|
|
(10 * ((sizeof(struct inotify_event)) + NAME_MAX + 1)); |
|
|
static const size_t kINotifyMaxEvents = 512; |
|
|
static const size_t kINotifyEventSize = |
|
|
sizeof(struct inotify_event) + (NAME_MAX + 1); |
|
|
static const size_t kINotifyBufferSize = |
|
|
(kINotifyMaxEvents * kINotifyEventSize); |
|
|
|
|
|
std::map<int, std::string> kMaskActions = { |
|
|
{IN_ACCESS, "ACCESSED"}, |
|
|
@@ -64,41 +66,64 @@ Status INotifyEventPublisher::setUp() { |
|
|
return Status(0, "OK"); |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::needMonitoring(const std::string& path, |
|
|
INotifySubscriptionContextRef& isc, |
|
|
uint32_t mask, |
|
|
bool recursive, |
|
|
bool add_watch) { |
|
|
bool rc = true; |
|
|
struct stat file_dir_stat; |
|
|
time_t sc_time = isc->path_sc_time_[path]; |
|
|
|
|
|
if (stat(path.c_str(), &file_dir_stat) == -1) { |
|
|
LOG(WARNING) << "Failed to do stat on: " << path; |
|
|
return false; |
|
|
} |
|
|
|
|
|
if (sc_time != file_dir_stat.st_ctime) { |
|
|
if ((rc = addMonitor(path, isc, isc->mask, isc->recursive, add_watch))) { |
|
|
isc->path_sc_time_[path] = file_dir_stat.st_ctime; |
|
|
} |
|
|
} |
|
|
return rc; |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::monitorSubscription( |
|
|
INotifySubscriptionContextRef& sc, bool add_watch) { |
|
|
sc->discovered_ = sc->path; |
|
|
std::string discovered = sc->path; |
|
|
if (sc->path.find("**") != std::string::npos) { |
|
|
sc->recursive = true; |
|
|
sc->discovered_ = sc->path.substr(0, sc->path.find("**")); |
|
|
sc->path = sc->discovered_; |
|
|
discovered = sc->path.substr(0, sc->path.find("**")); |
|
|
sc->path = discovered; |
|
|
} |
|
|
|
|
|
if (sc->path.find('*') != std::string::npos) { |
|
|
// If the wildcard exists within the file (leaf), remove and monitor the |
|
|
// directory instead. Apply a fnmatch on fired events to filter leafs. |
|
|
auto fullpath = fs::path(sc->path); |
|
|
if (fullpath.filename().string().find('*') != std::string::npos) { |
|
|
sc->discovered_ = fullpath.parent_path().string() + '/'; |
|
|
discovered = fullpath.parent_path().string() + '/'; |
|
|
} |
|
|
|
|
|
if (sc->discovered_.find('*') != std::string::npos) { |
|
|
if (discovered.find('*') != std::string::npos) { |
|
|
// If a wildcard exists within the tree (stem), resolve at configure |
|
|
// time and monitor each path. |
|
|
std::vector<std::string> paths; |
|
|
resolveFilePattern(sc->discovered_, paths); |
|
|
resolveFilePattern(discovered, paths); |
|
|
sc->recursive_match = sc->recursive; |
|
|
for (const auto& _path : paths) { |
|
|
addMonitor(_path, sc->mask, sc->recursive, add_watch); |
|
|
needMonitoring(_path, sc, sc->mask, sc->recursive, add_watch); |
|
|
} |
|
|
sc->recursive_match = sc->recursive; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
if (isDirectory(sc->discovered_) && sc->discovered_.back() != '/') { |
|
|
if (isDirectory(discovered) && discovered.back() != '/') { |
|
|
sc->path += '/'; |
|
|
sc->discovered_ += '/'; |
|
|
discovered += '/'; |
|
|
} |
|
|
return addMonitor(sc->discovered_, sc->mask, sc->recursive, add_watch); |
|
|
|
|
|
return needMonitoring(discovered, sc, sc->mask, sc->recursive, add_watch); |
|
|
} |
|
|
|
|
|
void INotifyEventPublisher::configure() { |
|
|
@@ -107,14 +132,38 @@ void INotifyEventPublisher::configure() { |
|
|
return; |
|
|
} |
|
|
|
|
|
SubscriptionVector delete_subscriptions; |
|
|
{ |
|
|
WriteLock lock(subscription_lock_); |
|
|
auto end = std::remove_if( |
|
|
subscriptions_.begin(), |
|
|
subscriptions_.end(), |
|
|
[&delete_subscriptions](const SubscriptionRef& subscription) { |
|
|
auto inotify_sc = getSubscriptionContext(subscription->context); |
|
|
if (inotify_sc->mark_for_deletion == true) { |
|
|
delete_subscriptions.push_back(subscription); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}); |
|
|
subscriptions_.erase(end, subscriptions_.end()); |
|
|
} |
|
|
|
|
|
for (auto& sub : delete_subscriptions) { |
|
|
auto ino_sc = getSubscriptionContext(sub->context); |
|
|
for (const auto& path : ino_sc->descriptor_paths_) { |
|
|
removeMonitor(path.first, true, true); |
|
|
} |
|
|
ino_sc->descriptor_paths_.clear(); |
|
|
} |
|
|
|
|
|
delete_subscriptions.clear(); |
|
|
|
|
|
for (auto& sub : subscriptions_) { |
|
|
// Anytime a configure is called, try to monitor all subscriptions. |
|
|
// Configure is called as a response to removing/adding subscriptions. |
|
|
// This means recalculating all monitored paths. |
|
|
auto sc = getSubscriptionContext(sub->context); |
|
|
if (sc->discovered_.size() > 0) { |
|
|
continue; |
|
|
} |
|
|
monitorSubscription(sc); |
|
|
} |
|
|
} |
|
|
@@ -125,47 +174,37 @@ void INotifyEventPublisher::tearDown() { |
|
|
} |
|
|
inotify_handle_ = -1; |
|
|
|
|
|
auto descriptors = descriptors_; |
|
|
for (const auto& desc : descriptors) { |
|
|
removeMonitor(desc, false); |
|
|
} |
|
|
|
|
|
{ |
|
|
// Then remove all path/descriptor mappings. |
|
|
WriteLock lock(path_mutex_); |
|
|
path_descriptors_.clear(); |
|
|
descriptor_paths_.clear(); |
|
|
} |
|
|
|
|
|
WriteLock lock(scratch_mutex_); |
|
|
if (scratch_ != nullptr) { |
|
|
free(scratch_); |
|
|
scratch_ = nullptr; |
|
|
} |
|
|
} |
|
|
|
|
|
Status INotifyEventPublisher::restartMonitoring() { |
|
|
if (last_restart_ != 0 && getUnixTime() - last_restart_ < 10) { |
|
|
return Status(1, "Overflow"); |
|
|
void INotifyEventPublisher::handleOverflow() { |
|
|
if (inotify_events_ < kINotifyMaxEvents) { |
|
|
VLOG(1) << "inotify was overflown: increasing scratch buffer"; |
|
|
// Exponential increment. |
|
|
inotify_events_ = inotify_events_ * 2; |
|
|
} else if (last_overflow_ != -1 && getUnixTime() - last_overflow_ < 60) { |
|
|
return; |
|
|
} else { |
|
|
VLOG(1) << "inotify was overflown"; |
|
|
last_overflow_ = getUnixTime(); |
|
|
} |
|
|
last_restart_ = getUnixTime(); |
|
|
|
|
|
tearDown(); |
|
|
setUp(); |
|
|
|
|
|
// Reconfigure ourself, the subscribers will not reconfigure. |
|
|
configure(); |
|
|
return Status(0, "OK"); |
|
|
} |
|
|
|
|
|
Status INotifyEventPublisher::run() { |
|
|
struct pollfd fds[1]; |
|
|
fds[0].fd = getHandle(); |
|
|
fds[0].events = POLLIN; |
|
|
int selector = ::poll(fds, 1, 1000); |
|
|
if (selector == -1 && errno != EINTR && errno != EAGAIN) { |
|
|
if (selector == -1) { |
|
|
if (errno == EINTR) { |
|
|
return Status(0, "inotify poll interrupted"); |
|
|
} |
|
|
LOG(WARNING) << "Could not read inotify handle"; |
|
|
return restartMonitoring(); |
|
|
return Status(1, "inotify poll failed"); |
|
|
} |
|
|
|
|
|
if (selector == 0) { |
|
|
@@ -178,7 +217,8 @@ Status INotifyEventPublisher::run() { |
|
|
} |
|
|
|
|
|
WriteLock lock(scratch_mutex_); |
|
|
ssize_t record_num = ::read(getHandle(), scratch_, kINotifyBufferSize); |
|
|
ssize_t record_num = |
|
|
::read(getHandle(), scratch_, inotify_events_ * kINotifyEventSize); |
|
|
if (record_num == 0 || record_num == -1) { |
|
|
return Status(1, "INotify read failed"); |
|
|
} |
|
|
@@ -187,11 +227,9 @@ Status INotifyEventPublisher::run() { |
|
|
// Cast the inotify struct, make shared pointer, and append to contexts. |
|
|
auto event = reinterpret_cast<struct inotify_event*>(p); |
|
|
if (event->mask & IN_Q_OVERFLOW) { |
|
|
// The inotify queue was overflown (remove all paths). |
|
|
return restartMonitoring(); |
|
|
} |
|
|
|
|
|
if (event->mask & IN_IGNORED) { |
|
|
// The inotify queue was overflown (try to recieve more events from OS). |
|
|
handleOverflow(); |
|
|
} else if (event->mask & IN_IGNORED) { |
|
|
// This inotify watch was removed. |
|
|
removeMonitor(event->wd, false); |
|
|
} else if (event->mask & IN_MOVE_SELF) { |
|
|
@@ -210,7 +248,6 @@ Status INotifyEventPublisher::run() { |
|
|
p += (sizeof(struct inotify_event)) + event->len; |
|
|
} |
|
|
|
|
|
pauseMilli(kINotifyMLatency); |
|
|
return Status(0, "OK"); |
|
|
} |
|
|
|
|
|
@@ -223,11 +260,12 @@ INotifyEventContextRef INotifyEventPublisher::createEventContextFrom( |
|
|
// Get the pathname the watch fired on. |
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
if (descriptor_paths_.find(event->wd) == descriptor_paths_.end()) { |
|
|
if (descriptor_inosubctx_.find(event->wd) == descriptor_inosubctx_.end()) { |
|
|
// return a blank event context if we can't find the paths for the event |
|
|
return ec; |
|
|
} else { |
|
|
ec->path = descriptor_paths_.at(event->wd); |
|
|
auto isc = descriptor_inosubctx_.at(event->wd); |
|
|
ec->path = isc->descriptor_paths_.at(event->wd); |
|
|
} |
|
|
} |
|
|
|
|
|
@@ -271,32 +309,45 @@ bool INotifyEventPublisher::shouldFire(const INotifySubscriptionContextRef& sc, |
|
|
// inotify will not monitor recursively, new directories need watches. |
|
|
if (sc->recursive && ec->action == "CREATED" && isDirectory(ec->path)) { |
|
|
const_cast<INotifyEventPublisher*>(this)->addMonitor( |
|
|
ec->path + '/', sc->mask, true); |
|
|
ec->path + '/', |
|
|
const_cast<INotifySubscriptionContextRef&>(sc), |
|
|
sc->mask, |
|
|
true); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::addMonitor(const std::string& path, |
|
|
INotifySubscriptionContextRef& isc, |
|
|
uint32_t mask, |
|
|
bool recursive, |
|
|
bool add_watch) { |
|
|
if (!isPathMonitored(path)) { |
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
int watch = ::inotify_add_watch( |
|
|
getHandle(), path.c_str(), ((mask == 0) ? kFileDefaultMasks : mask)); |
|
|
if (add_watch && watch == -1) { |
|
|
LOG(WARNING) << "Could not add inotify watch on: " << path; |
|
|
return false; |
|
|
} |
|
|
|
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
// Keep a list of the watch descriptors |
|
|
descriptors_.push_back(watch); |
|
|
if (descriptor_inosubctx_.find(watch) != descriptor_inosubctx_.end()) { |
|
|
auto ino_sc = descriptor_inosubctx_.at(watch); |
|
|
if (inotify_sanity_check) { |
|
|
std::string watched_path = ino_sc->descriptor_paths_[watch]; |
|
|
path_descriptors_.erase(watched_path); |
|
|
} |
|
|
ino_sc->descriptor_paths_.erase(watch); |
|
|
descriptor_inosubctx_.erase(watch); |
|
|
} |
|
|
|
|
|
// Keep a map of (descriptor -> path) |
|
|
isc->descriptor_paths_[watch] = path; |
|
|
descriptor_inosubctx_[watch] = isc; |
|
|
if (inotify_sanity_check) { |
|
|
// Keep a map of the path -> watch descriptor |
|
|
path_descriptors_[path] = watch; |
|
|
// Keep a map of the opposite (descriptor -> path) |
|
|
descriptor_paths_[watch] = path; |
|
|
} |
|
|
} |
|
|
|
|
|
@@ -308,57 +359,73 @@ bool INotifyEventPublisher::addMonitor(const std::string& path, |
|
|
boost::system::error_code ec; |
|
|
for (const auto& child : children) { |
|
|
auto canonicalized = fs::canonical(child, ec).string() + '/'; |
|
|
addMonitor(canonicalized, mask, false); |
|
|
addMonitor(canonicalized, isc, mask, false); |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::removeMonitor(const std::string& path, bool force) { |
|
|
bool INotifyEventPublisher::removeMonitor(int watch, |
|
|
bool force, |
|
|
bool batch_del) { |
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
// If force then remove from INotify, otherwise cleanup file descriptors. |
|
|
if (path_descriptors_.find(path) == path_descriptors_.end()) { |
|
|
if (descriptor_inosubctx_.find(watch) == descriptor_inosubctx_.end()) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
int watch = 0; |
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
watch = path_descriptors_[path]; |
|
|
path_descriptors_.erase(path); |
|
|
descriptor_paths_.erase(watch); |
|
|
auto isc = descriptor_inosubctx_.at(watch); |
|
|
descriptor_inosubctx_.erase(watch); |
|
|
|
|
|
if (inotify_sanity_check) { |
|
|
std::string watched_path = isc->descriptor_paths_[watch]; |
|
|
path_descriptors_.erase(watched_path); |
|
|
} |
|
|
|
|
|
auto position = std::find(descriptors_.begin(), descriptors_.end(), watch); |
|
|
descriptors_.erase(position); |
|
|
if (!batch_del) { |
|
|
isc->descriptor_paths_.erase(watch); |
|
|
} |
|
|
} |
|
|
|
|
|
if (force) { |
|
|
::inotify_rm_watch(getHandle(), watch); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::removeMonitor(int watch, bool force) { |
|
|
std::string path; |
|
|
{ |
|
|
WriteLock lock(path_mutex_); |
|
|
if (descriptor_paths_.find(watch) == descriptor_paths_.end()) { |
|
|
return false; |
|
|
} |
|
|
path = descriptor_paths_[watch]; |
|
|
} |
|
|
return removeMonitor(path, force); |
|
|
void INotifyEventPublisher::removeSubscriptions(const std::string& subscriber) { |
|
|
WriteLock lock(subscription_lock_); |
|
|
std::for_each(subscriptions_.begin(), |
|
|
subscriptions_.end(), |
|
|
[&subscriber](const SubscriptionRef& sub) { |
|
|
if (sub->subscriber_name == subscriber) { |
|
|
getSubscriptionContext(sub->context)->mark_for_deletion = |
|
|
true; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
void INotifyEventPublisher::removeSubscriptions(const std::string& subscriber) { |
|
|
auto paths = descriptor_paths_; |
|
|
for (const auto& path : paths) { |
|
|
removeMonitor(path.first, true); |
|
|
Status INotifyEventPublisher::addSubscription( |
|
|
const SubscriptionRef& subscription) { |
|
|
WriteLock lock(subscription_lock_); |
|
|
auto received_inotify_sc = getSubscriptionContext(subscription->context); |
|
|
for (auto& sub : subscriptions_) { |
|
|
auto inotify_sc = getSubscriptionContext(sub->context); |
|
|
if (*received_inotify_sc == *inotify_sc) { |
|
|
if (inotify_sc->mark_for_deletion) { |
|
|
inotify_sc->mark_for_deletion = false; |
|
|
return Status(0); |
|
|
} |
|
|
// Returing non zero signals EventSubscriber::subscribe |
|
|
// dont bumpup subscription_count_. |
|
|
return Status(1); |
|
|
} |
|
|
} |
|
|
EventPublisherPlugin::removeSubscriptions(subscriber); |
|
|
|
|
|
subscriptions_.push_back(subscription); |
|
|
return Status(0); |
|
|
} |
|
|
|
|
|
bool INotifyEventPublisher::isPathMonitored(const std::string& path) const { |
|
|
|