/* * Copyright (C) 2018 The Android Open Source Project * * 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. */ #pragma once #include #include #include #include #include #include "BpfSyscallWrappers.h" #include "bpf/BpfUtils.h" namespace android { namespace bpf { using base::Result; using base::unique_fd; using std::function; // This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel // data structure that stores data in pairs. It can be read/write // from userspace by passing syscalls with the map file descriptor. This class // is used to generalize the procedure of interacting with eBPF maps and hide // the implementation detail from other process. Besides the basic syscalls // wrapper, it also provides some useful helper functions as well as an iterator // nested class to iterate the map more easily. // // NOTE: A kernel eBPF map may be accessed by both kernel and userspace // processes at the same time. Or if the map is pinned as a virtual file, it can // be obtained by multiple eBPF map class object and accessed concurrently. // Though the map class object and the underlying kernel map are thread safe, it // is not safe to iterate over a map while another thread or process is deleting // from it. In this case the iteration can return duplicate entries. template class BpfMap { public: BpfMap() {}; // explicitly force no copy constructor, since it would need to dup the fd // (later on, for testing, we still make available a copy assignment operator) BpfMap(const BpfMap&) = delete; private: void abortOnKeyOrValueSizeMismatch() { if (!mMapFd.ok()) abort(); if (isAtLeastKernelVersion(4, 14, 0)) { if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort(); if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort(); } } protected: // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY BpfMap(const char* pathname, uint32_t flags) { mMapFd.reset(mapRetrieve(pathname, flags)); abortOnKeyOrValueSizeMismatch(); } public: explicit BpfMap(const char* pathname) : BpfMap(pathname, 0) {} #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // All bpf maps should be created by the bpfloader, so this constructor // is reserved for tests BpfMap(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) { mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags)); if (!mMapFd.ok()) abort(); } #endif Result getFirstKey() const { Key firstKey; if (getFirstMapKey(mMapFd, &firstKey)) { return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get()); } return firstKey; } Result getNextKey(const Key& key) const { Key nextKey; if (getNextMapKey(mMapFd, &key, &nextKey)) { return ErrnoErrorf("Get next key of map {} failed", mMapFd.get()); } return nextKey; } Result writeValue(const Key& key, const Value& value, uint64_t flags) { if (writeToMapEntry(mMapFd, &key, &value, flags)) { return ErrnoErrorf("Write to map {} failed", mMapFd.get()); } return {}; } Result readValue(const Key key) const { Value value; if (findMapEntry(mMapFd, &key, &value)) { return ErrnoErrorf("Read value of map {} failed", mMapFd.get()); } return value; } Result deleteValue(const Key& key) { if (deleteMapEntry(mMapFd, &key)) { return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get()); } return {}; } protected: [[clang::reinitializes]] Result init(const char* path, int fd) { mMapFd.reset(fd); if (!mMapFd.ok()) { return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path); } // Normally we should return an error here instead of calling abort, // but this cannot happen at runtime without a massive code bug (K/V type mismatch) // and as such it's better to just blow the system up and let the developer fix it. // Crashes are much more likely to be noticed than logs and missing functionality. abortOnKeyOrValueSizeMismatch(); return {}; } public: // Function that tries to get map from a pinned path. [[clang::reinitializes]] Result init(const char* path) { return init(path, mapRetrieveRW(path)); } #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // due to Android SELinux limitations which prevent map creation by anyone besides the bpfloader // this should only ever be used by test code, it is equivalent to: // .reset(createMap(type, keysize, valuesize, max_entries, map_flags) // TODO: derive map_flags from BpfMap vs BpfMapRO [[clang::reinitializes]] Result resetMap(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) { mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags)); if (!mMapFd.ok()) return ErrnoErrorf("Unable to create map."); return {}; } #endif // Iterate through the map and handle each key retrieved based on the filter // without modification of map content. Result iterate( const function(const Key& key, const BpfMap& map)>& filter) const; // Iterate through the map and get each pair, handle each pair based on the filter without modification of map content. Result iterateWithValue( const function(const Key& key, const Value& value, const BpfMap& map)>& filter) const; // Iterate through the map and handle each key retrieved based on the filter Result iterate( const function(const Key& key, BpfMap& map)>& filter); // Iterate through the map and get each pair, handle each pair based on the filter. Result iterateWithValue( const function(const Key& key, const Value& value, BpfMap& map)>& filter); const unique_fd& getMap() const { return mMapFd; }; #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // Copy assignment operator - due to need for fd duping, should not be used in non-test code. BpfMap& operator=(const BpfMap& other) { if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0)); return *this; } #else BpfMap& operator=(const BpfMap&) = delete; #endif // Move assignment operator BpfMap& operator=(BpfMap&& other) noexcept { if (this != &other) { mMapFd = std::move(other.mMapFd); other.reset(); } return *this; } void reset(unique_fd fd) = delete; #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING // Note that unique_fd.reset() carefully saves and restores the errno, // and BpfMap.reset() won't touch the errno if passed in fd is negative either, // hence you can do something like BpfMap.reset(systemcall()) and then // check BpfMap.isValid() and look at errno and see why systemcall() failed. [[clang::reinitializes]] void reset(int fd) { mMapFd.reset(fd); if (mMapFd.ok()) abortOnKeyOrValueSizeMismatch(); } #endif [[clang::reinitializes]] void reset() { mMapFd.reset(); } bool isValid() const { return mMapFd.ok(); } Result clear() { while (true) { auto key = getFirstKey(); if (!key.ok()) { if (key.error().code() == ENOENT) return {}; // empty: success return key.error(); // Anything else is an error } auto res = deleteValue(key.value()); if (!res.ok()) { // Someone else could have deleted the key, so ignore ENOENT if (res.error().code() == ENOENT) continue; ALOGE("Failed to delete data %s", strerror(res.error().code())); return res.error(); } } } Result isEmpty() const { auto key = getFirstKey(); if (key.ok()) return false; if (key.error().code() == ENOENT) return true; return key.error(); } private: unique_fd mMapFd; }; template Result BpfMap::iterate( const function(const Key& key, const BpfMap& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMap::iterateWithValue( const function(const Key& key, const Value& value, const BpfMap& map)>& filter) const { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); Result status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMap::iterate( const function(const Key& key, BpfMap& map)>& filter) { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result status = filter(curKey.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template Result BpfMap::iterateWithValue( const function(const Key& key, const Value& value, BpfMap& map)>& filter) { Result curKey = getFirstKey(); while (curKey.ok()) { const Result& nextKey = getNextKey(curKey.value()); Result curValue = readValue(curKey.value()); if (!curValue.ok()) return curValue.error(); Result status = filter(curKey.value(), curValue.value(), *this); if (!status.ok()) return status; curKey = nextKey; } if (curKey.error().code() == ENOENT) return {}; return curKey.error(); } template class BpfMapRO : public BpfMap { public: BpfMapRO() {}; explicit BpfMapRO(const char* pathname) : BpfMap(pathname, BPF_F_RDONLY) {} // Function that tries to get map from a pinned path. [[clang::reinitializes]] Result init(const char* path) { return BpfMap::init(path, mapRetrieveRO(path)); } }; } // namespace bpf } // namespace android