diff --git a/osquery/utils/system/linux/ebpf/BUCK b/osquery/utils/system/linux/ebpf/BUCK index 54ea6dfafd7..80c05d99717 100644 --- a/osquery/utils/system/linux/ebpf/BUCK +++ b/osquery/utils/system/linux/ebpf/BUCK @@ -19,6 +19,7 @@ osquery_cxx_library( LINUX, [ "ebpf.h", + "map.h", ], ), ], @@ -27,6 +28,7 @@ osquery_cxx_library( LINUX, [ "ebpf.cpp", + "map.cpp", ], ), ], diff --git a/osquery/utils/system/linux/ebpf/map.cpp b/osquery/utils/system/linux/ebpf/map.cpp new file mode 100644 index 00000000000..4562aa356bc --- /dev/null +++ b/osquery/utils/system/linux/ebpf/map.cpp @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the Apache 2.0 license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#include +#include + +#include + +#include +#include + +namespace osquery { +namespace ebpf { + +namespace impl { + +Expected mapCreate(enum bpf_map_type map_type, + std::size_t key_size, + std::size_t value_size, + std::size_t max_entries) { + union bpf_attr attr; + memset(&attr, 0, sizeof(union bpf_attr)); + attr.map_type = map_type; + attr.key_size = static_cast(key_size); + attr.value_size = static_cast(value_size); + attr.max_entries = static_cast(max_entries); + auto exp_bpf = syscall(BPF_MAP_CREATE, &attr); + if (exp_bpf.isError()) { + return createError( + MapError::Unknown, "Creating eBPF map failed", exp_bpf.takeError()); + } + return exp_bpf.take(); +} + +ExpectedSuccess mapUpdateElement(const int fd, + void const* key, + void const* value, + unsigned long long flags) { + union bpf_attr attr; + memset(&attr, 0, sizeof(union bpf_attr)); + attr.map_fd = fd; + attr.key = reinterpret_cast(key); + attr.value = reinterpret_cast(value); + attr.flags = flags; + auto exp_bpf = syscall(BPF_MAP_UPDATE_ELEM, &attr); + if (exp_bpf.isError()) { + return createError(MapError::Unknown, + "Updating value in eBPF map failed", + exp_bpf.takeError()); + } + return Success{}; +} + +ExpectedSuccess mapLookupElement(const int fd, + void const* key, + void* value) { + union bpf_attr attr; + memset(&attr, 0, sizeof(union bpf_attr)); + attr.map_fd = fd; + attr.key = reinterpret_cast(key); + attr.value = reinterpret_cast(value); + auto exp_bpf = syscall(BPF_MAP_LOOKUP_ELEM, &attr); + if (exp_bpf.isError()) { + if (exp_bpf.getErrorCode() == PosixError::NOENT) { + return createError(MapError::NoSuchKey, + "No such key in the eBPF map", + exp_bpf.takeError()); + } + return createError( + MapError::Unknown, "Look up in eBPF map failed", exp_bpf.takeError()); + } + return Success{}; +} + +ExpectedSuccess mapDeleteElement(const int fd, void const* key) { + union bpf_attr attr; + memset(&attr, 0, sizeof(union bpf_attr)); + attr.map_fd = fd; + attr.key = reinterpret_cast(key); + auto exp_bpf = syscall(BPF_MAP_DELETE_ELEM, &attr); + if (exp_bpf.isError()) { + if (exp_bpf.getErrorCode() == PosixError::NOENT) { + return createError(MapError::NoSuchKey, + "No such key in the eBPF map", + exp_bpf.takeError()); + } + return createError(MapError::Unknown, + "Deleting element from eBPF map failed", + exp_bpf.takeError()); + } + return Success{}; +} + +} // namespace impl + +} // namespace ebpf +} // namespace osquery diff --git a/osquery/utils/system/linux/ebpf/map.h b/osquery/utils/system/linux/ebpf/map.h new file mode 100644 index 00000000000..5895d829ed8 --- /dev/null +++ b/osquery/utils/system/linux/ebpf/map.h @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the Apache 2.0 license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#pragma once + +#include + +#include + +namespace osquery { +namespace ebpf { + +enum class MapError { + Unknown = 1, + NoSuchKey = 2, +}; + +namespace impl { +/** + * Do not use implementation functions directly, use Map class to create and + * manage eBPF map + */ +Expected mapCreate(enum bpf_map_type map_type, + std::size_t key_size, + std::size_t value_size, + std::size_t max_entries); +ExpectedSuccess mapUpdateElement(int fd, + void const* key, + void const* value, + unsigned long long flags); +ExpectedSuccess mapLookupElement(int fd, + void const* key, + void* value); +ExpectedSuccess mapDeleteElement(int fd, void const* key); + +} // namespace impl + +/** + * Proxy object for the eBPF map structure in kernel. + */ + +template +class Map final { + private: + static_assert( + std::is_pod::value && std::is_pod::value, + "Both key type and value type must be a plain old data type (POD)"); + + /** + * The only constructor of Map is private for purpose. Use createMap function + * instead. Map should not be created in case of creating eBPF map failure. + */ + explicit Map(int fd, std::size_t size) : fd_(fd), size_(size) {} + + public: + ~Map() { + if (fd_ >= 0) { + close(fd_); + } + } + + Map(Map const&) = delete; + + Map(Map && from) : fd_(from.fd_), size_(from.size_) { + from.fd_ = -1; + } + + Map& operator=(Map const&) = delete; + + Map& operator=(Map&& from) { + if (fd_ >= 0) { + close(fd_); + fd_ = -1; + } + std::swap(fd_, from.fd_); + std::swap(size_, from.size_); + } + + Expected lookupElement(KeyType const& key) const { + auto value = ValueType{}; + auto exp = impl::mapLookupElement( + fd_, static_cast(&key), static_cast(&value)); + if (exp.isError()) { + return exp.takeError(); + } + return value; + } + + ExpectedSuccess updateElement(KeyType const& key, + ValueType const& value, + unsigned long long flags = BPF_ANY) { + return impl::mapUpdateElement(fd_, + static_cast(&key), + static_cast(&value), + flags); + } + + ExpectedSuccess deleteElement(KeyType const& key) { + return impl::mapDeleteElement(fd_, static_cast(&key)); + } + + std::size_t size() const { + return size_; + } + + int fd() const { + return fd_; + } + + template + friend Expected, MapError> createMap( + std::size_t size); + + private: + int fd_ = -1; + std::size_t size_; +}; + +template +static Expected, MapError> createMap( + std::size_t size) { + auto exp = + impl::mapCreate(map_type, sizeof(KeyType), sizeof(ValueType), size); + if (exp.isError()) { + return exp.takeError(); + } + return Map(exp.take(), size); +} + +} // namespace ebpf +} // namespace osquery diff --git a/osquery/utils/system/linux/ebpf/tests/BUCK b/osquery/utils/system/linux/ebpf/tests/BUCK index d2ea9607723..55ae54680e3 100644 --- a/osquery/utils/system/linux/ebpf/tests/BUCK +++ b/osquery/utils/system/linux/ebpf/tests/BUCK @@ -20,6 +20,7 @@ osquery_cxx_test( LINUX, [ "ebpf.cpp", + "map.cpp", ], ), ], diff --git a/osquery/utils/system/linux/ebpf/tests/map.cpp b/osquery/utils/system/linux/ebpf/tests/map.cpp new file mode 100644 index 00000000000..1d767a97c52 --- /dev/null +++ b/osquery/utils/system/linux/ebpf/tests/map.cpp @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under both the Apache 2.0 license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#include +#include + +#include + +#include + +namespace osquery { +namespace { + +class EbpfMapTests : public testing::Test {}; + +TEST_F(EbpfMapTests, int_key_int_value) { + if (!ebpf::isSupportedBySystem()) { + LOG(WARNING) << "This system does not support eBPF of required vesion, " + "test will be skipped"; + return; + } + auto const size = std::size_t{12}; + auto map_exp = ebpf::createMap(size); + ASSERT_TRUE(map_exp.isValue()) + << map_exp.getError().getFullMessageRecursive(); + auto map = map_exp.take(); + ASSERT_EQ(map.size(), size); + { + auto exp = map.lookupElement(0); + ASSERT_TRUE(exp.isError()); + EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); + } + { + auto exp = map.lookupElement(215); + ASSERT_TRUE(exp.isError()); + EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); + } + { + auto exp = map.updateElement(5, 53); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + } + { + auto exp = map.lookupElement(5); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + ASSERT_EQ(exp.get(), 53); + } + { + // key could be greater a size, because it is a hash map + auto exp = map.updateElement(207, 8042); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + } + { + auto exp = map.lookupElement(207); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + ASSERT_EQ(exp.get(), 8042); + } + { + // let's try to delete some existing key + auto exp = map.deleteElement(207); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + } + { + auto exp = map.lookupElement(207); + ASSERT_TRUE(exp.isError()); + EXPECT_EQ(exp.getError().getErrorCode(), ebpf::MapError::NoSuchKey); + } +} + +TEST_F(EbpfMapTests, int_key_struct_value) { + if (!ebpf::isSupportedBySystem()) { + LOG(WARNING) << "This system does not support eBPF of required vesion, " + "test will be skipped"; + return; + } + struct Value { + int left; + int right; + }; + auto const size = std::size_t{128}; + auto map_exp = ebpf::createMap(size); + ASSERT_TRUE(map_exp.isValue()); + auto map = map_exp.take(); + ASSERT_EQ(map.size(), size); + { + auto const v = Value{ + .left = -9287, + .right = 2781, + }; + auto exp = map.updateElement(72, v); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + } + { + auto exp = map.lookupElement(72); + ASSERT_TRUE(exp.isValue()) << exp.getError().getFullMessageRecursive(); + EXPECT_EQ(exp.get().left, -9287); + EXPECT_EQ(exp.get().right, 2781); + } +} + +} // namespace +} // namespace osquery