Skip to content

Commit

Permalink
Class to join exit-enter event pairs
Browse files Browse the repository at this point in the history
Differential Revision: D13761675

fbshipit-source-id: f40b6b89a209c8cbb357d88cd0bface44ad9724d
  • Loading branch information
akindyakov authored and facebook-github-bot committed Feb 4, 2019
1 parent 344fbed commit 7bd90ed
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 3 deletions.
70 changes: 69 additions & 1 deletion osquery/events/linux/probes/syscall_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,73 @@
#include <osquery/events/linux/probes/syscall_event.h>

namespace osquery {
namespace events {} // namespace events
namespace events {
namespace syscall {

namespace {

static constexpr EnterExitJoiner::CounterType kCounterLimit = 256;

EnterExitJoiner::KeyType createKey(Type const type,
__s32 const pid,
__s32 const tgid) {
auto key = EnterExitJoiner::KeyType(static_cast<std::uint32_t>(pid));
key <<= 32;
key |= static_cast<std::uint32_t>(tgid);
key <<= 32;
key |= static_cast<std::uint32_t>(type);
return key;
}

} // namespace

boost::optional<Event> EnterExitJoiner::join(Event in_event) {
++counter_;
if (counter_ > kCounterLimit) {
drop_stuck_events();
counter_ = 0;
}
auto const inv_key =
createKey(flipType(in_event.type), in_event.pid, in_event.tgid);

auto it = table_.find(inv_key);
if (it == table_.end()) {
auto const key = createKey(in_event.type, in_event.pid, in_event.tgid);
// As far as `return_value` is not used while the event is in the table_
// we can use it to preserve the counter value as the age of the event.
in_event.return_value = counter_;
table_.emplace(key, std::move(in_event));
return boost::none;
}

if (isTypeExit(in_event.type)) {
auto enter = std::move(it->second);
enter.return_value = in_event.body.exit.ret;
table_.erase(it);
return enter;
}
in_event.return_value = it->second.body.exit.ret;
table_.erase(it);
return in_event;
}

bool EnterExitJoiner::isEmpty() const {
return table_.empty();
}

void EnterExitJoiner::drop_stuck_events() {
// As far as `table_` is relatively small we can afford to iterarte over it
// once in a kCounterLimit events in order to clean it up.
for (auto it = table_.begin(); it != table_.end();) {
if (it->second.return_value < 0) {
it = table_.erase(it);
} else {
it->second.return_value -= kCounterLimit;
++it;
}
}
}

} // namespace syscall
} // namespace events
} // namespace osquery
30 changes: 28 additions & 2 deletions osquery/events/linux/probes/syscall_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@
#include <osquery/utils/expected/expected.h>
#include <osquery/utils/system/linux/ebpf/program.h>

#include <boost/optional.hpp>

#include <bitset>
#include <type_traits>
#include <unordered_map>

namespace osquery {
namespace events {

namespace syscall {

enum class Type : __s32 {
Expand Down Expand Up @@ -79,10 +84,31 @@ struct Event {
} exit;
} body;

// final return value of the syscall is palced here by EnterExitJoiner
// This value is used by EnterExitJoiner, final return value of the syscall
// is placed here as a result of join().
// Also this member is used by EnterExitJoiner to preserve the age of the
// event.
__s32 return_value;
};

class EnterExitJoiner {
public:
boost::optional<Event> join(Event event);

bool isEmpty() const;

using CounterType = int;
static constexpr std::size_t KeyBitSize = 32u * 3u;
using KeyType = std::bitset<KeyBitSize>;

private:
void drop_stuck_events();

private:
CounterType counter_ = 0;
std::unordered_multimap<KeyType, Event> table_;
};

} // namespace syscall
} // namespace events
} // namespace osquery
130 changes: 130 additions & 0 deletions osquery/events/linux/probes/tests/syscall_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace osquery {
namespace {

class SyscallsTracepointTests : public testing::Test {};
class EnterExitJoinerTests : public testing::Test {};

template <events::syscall::Type enter, events::syscall::Type exit>
void checkEventPair() {
Expand Down Expand Up @@ -67,5 +68,134 @@ TEST_F(SyscallsTracepointTests, SyscallEvent_isTypeEnter) {
events::syscall::isTypeEnter(events::syscall::Type::SetuidEnter), "");
}

TEST_F(EnterExitJoinerTests,
EnterExitJoiner_many_pair_enter_exit_events_with_different_pid) {
auto joiner = events::syscall::EnterExitJoiner{};
{
auto enter_event = events::syscall::Event{};
enter_event.type = events::syscall::Type::SetuidEnter;
enter_event.tgid = 146;
enter_event.body.setuid_enter.arg_uid = 48;
enter_event.body.setuid_enter.uid = 49;
enter_event.body.setuid_enter.gid = 50;
enter_event.return_value = -1;
for (int pid = 0; pid < 64; ++pid) {
enter_event.pid = pid;
auto out = joiner.join(enter_event);
ASSERT_FALSE(out);
}
}
auto exit_event = events::syscall::Event{};
exit_event.type = events::syscall::Type::SetuidExit;
exit_event.tgid = 146;
exit_event.body.exit.ret = -59;

for (int pid = 0; pid < 64; ++pid) {
exit_event.pid = pid;

auto event = joiner.join(exit_event);
ASSERT_TRUE(event);
EXPECT_EQ(event->type, events::syscall::Type::SetuidEnter);
EXPECT_EQ(event->pid, pid);
EXPECT_EQ(event->tgid, 146);
EXPECT_EQ(event->body.setuid_enter.arg_uid, 48);
EXPECT_EQ(event->body.setuid_enter.uid, 49);
EXPECT_EQ(event->body.setuid_enter.gid, 50);
EXPECT_EQ(event->return_value, -59);
}

EXPECT_TRUE(joiner.isEmpty());
}

TEST_F(EnterExitJoinerTests, EnterExitJoiner_one_non_paired_event_by_pid) {
auto joiner = events::syscall::EnterExitJoiner{};

auto enter_event = events::syscall::Event{};
enter_event.type = events::syscall::Type::SetuidEnter;
enter_event.pid = 45;
enter_event.tgid = 146;
enter_event.body.setuid_enter.arg_uid = 48;
enter_event.body.setuid_enter.uid = 49;
enter_event.body.setuid_enter.gid = 50;
enter_event.return_value = -1;

auto out = joiner.join(enter_event);
ASSERT_FALSE(out);

auto exit_event = events::syscall::Event{};
exit_event.type = events::syscall::Type::SetuidExit;
exit_event.pid = enter_event.pid + 12; // pid is different
exit_event.tgid = enter_event.tgid;
exit_event.body.exit.ret = -59;

auto event = joiner.join(exit_event);
ASSERT_FALSE(event);
ASSERT_FALSE(joiner.isEmpty());
}

TEST_F(EnterExitJoinerTests, EnterExitJoiner_one_non_paired_event_by_type) {
auto joiner = events::syscall::EnterExitJoiner{};

auto enter_event = events::syscall::Event{};
enter_event.type = events::syscall::Type::SetuidEnter;
enter_event.pid = 45;
enter_event.tgid = 146;
enter_event.body.setuid_enter.arg_uid = 48;
enter_event.body.setuid_enter.uid = 49;
enter_event.body.setuid_enter.gid = 50;
enter_event.return_value = -1;

auto out = joiner.join(enter_event);
ASSERT_FALSE(out);

auto exit_event = events::syscall::Event{};
exit_event.type = events::syscall::Type::KillExit; // type is different
exit_event.pid = enter_event.pid;
exit_event.tgid = enter_event.tgid;
exit_event.body.exit.ret = -59;

auto event = joiner.join(exit_event);
ASSERT_FALSE(event);
EXPECT_FALSE(joiner.isEmpty());
}

TEST_F(EnterExitJoinerTests, EnterExitJoiner_many_same_enter_exit_events) {
auto joiner = events::syscall::EnterExitJoiner{};

auto enter_event = events::syscall::Event{};
enter_event.type = events::syscall::Type::SetuidEnter;
enter_event.pid = 218;
enter_event.tgid = 146;
enter_event.body.setuid_enter.arg_uid = 165;
enter_event.body.setuid_enter.uid = 49;
enter_event.body.setuid_enter.gid = 50;
enter_event.return_value = -1;
for (int i = 0; i < 12; ++i) {
joiner.join(enter_event);
}

auto exit_event = events::syscall::Event{};
exit_event.type = events::syscall::Type::SetuidExit;
exit_event.pid = enter_event.pid;
exit_event.tgid = enter_event.tgid;
exit_event.body.exit.ret = -59;

for (int i = 0; i < 12; ++i) {
auto event = joiner.join(exit_event);
ASSERT_TRUE(event);

EXPECT_EQ(event->type, events::syscall::Type::SetuidEnter);
EXPECT_EQ(event->pid, enter_event.pid);
EXPECT_EQ(event->tgid, enter_event.tgid);
EXPECT_EQ(event->body.setuid_enter.arg_uid,
enter_event.body.setuid_enter.arg_uid);
EXPECT_EQ(event->body.setuid_enter.uid, enter_event.body.setuid_enter.uid);
EXPECT_EQ(event->body.setuid_enter.gid, enter_event.body.setuid_enter.gid);
EXPECT_EQ(event->return_value, exit_event.body.exit.ret);
}

EXPECT_TRUE(joiner.isEmpty());
}

} // namespace
} // namespace osquery

0 comments on commit 7bd90ed

Please sign in to comment.