From d51b2338dfcec280c5eb554fcb9d6b2be699ad4e Mon Sep 17 00:00:00 2001 From: Alexander Kindyakov Date: Mon, 4 Feb 2019 09:02:52 -0800 Subject: [PATCH] Class to join exit-enter event pairs Summary: Hash multimap based joiner with ability to perform clean up old unpaired events from time to time. Part of a linux tracing system, blueprint: [#5218](https://github.com/facebook/osquery/issues/5218) Reviewed By: SAlexandru Differential Revision: D13761675 fbshipit-source-id: 8be920f2059ead508c5530a4f427db130b7e826c --- osquery/events/linux/probes/syscall_event.cpp | 70 +++++++++- osquery/events/linux/probes/syscall_event.h | 30 +++- .../linux/probes/tests/syscall_event.cpp | 130 ++++++++++++++++++ 3 files changed, 227 insertions(+), 3 deletions(-) diff --git a/osquery/events/linux/probes/syscall_event.cpp b/osquery/events/linux/probes/syscall_event.cpp index 3f1f4552788..cd08edd2b32 100644 --- a/osquery/events/linux/probes/syscall_event.cpp +++ b/osquery/events/linux/probes/syscall_event.cpp @@ -11,5 +11,73 @@ #include 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(pid)); + key <<= 32; + key |= static_cast(tgid); + key <<= 32; + key |= static_cast(type); + return key; +} + +} // namespace + +boost::optional 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 diff --git a/osquery/events/linux/probes/syscall_event.h b/osquery/events/linux/probes/syscall_event.h index 183b69acc64..294f6992eae 100644 --- a/osquery/events/linux/probes/syscall_event.h +++ b/osquery/events/linux/probes/syscall_event.h @@ -13,9 +13,14 @@ #include #include +#include + +#include +#include +#include + namespace osquery { namespace events { - namespace syscall { enum class Type : __s32 { @@ -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 join(Event event); + + bool isEmpty() const; + + using CounterType = int; + static constexpr std::size_t KeyBitSize = 32u * 3u; + using KeyType = std::bitset; + + private: + void drop_stuck_events(); + + private: + CounterType counter_ = 0; + std::unordered_multimap table_; +}; + } // namespace syscall } // namespace events } // namespace osquery diff --git a/osquery/events/linux/probes/tests/syscall_event.cpp b/osquery/events/linux/probes/tests/syscall_event.cpp index b0f34ac793b..e74bbd9d300 100644 --- a/osquery/events/linux/probes/tests/syscall_event.cpp +++ b/osquery/events/linux/probes/tests/syscall_event.cpp @@ -16,6 +16,7 @@ namespace osquery { namespace { class SyscallsTracepointTests : public testing::Test {}; +class EnterExitJoinerTests : public testing::Test {}; template void checkEventPair() { @@ -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