Loading health/utils/libhealthloop/Android.bp +39 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,17 @@ package { default_applicable_licenses: ["hardware_interfaces_license"], } bpf { name: "filterPowerSupplyEvents.o", srcs: ["filterPowerSupplyEvents.c"], // "vendor: true" because all binaries that use this BPF filter are vendor // binaries. vendor: true, } // Since "required" sections are ignored in static library definitions, // filterPowerSupplyEvents.o has been added in // build/make/target/product/base_vendor.mk. cc_library_static { name: "libhealthloop", vendor_available: true, Loading @@ -34,6 +45,7 @@ cc_library_static { "libcutils", ], header_libs: [ "bpf_headers", "libbatteryservice_headers", "libhealthd_headers", "libutils_headers", Loading @@ -42,3 +54,30 @@ cc_library_static { "include", ], } genrule { name: "filterPowerSupplyEvents.h", out: ["filterPowerSupplyEvents.h"], srcs: [":filterPowerSupplyEvents.o"], cmd: "cat $(in) | od -v -tx1 | cut -c9- | grep -v '^$$' | sed 's/^/0x/;s/ /, 0x/g;s/^, //;s/$$/,/' > $(out)", } cc_test_host { name: "filterPowerSupplyEventsTest", team: "trendy_team_pixel_system_sw_storage", srcs: [ "filterPowerSupplyEventsTest.cpp", ], shared_libs: [ "libbase", "libbpf", ], static_libs: [ "libgmock", ], generated_headers: [ "filterPowerSupplyEvents.h", "libbpf_headers", ], compile_multilib: "64", } health/utils/libhealthloop/HealthLoop.cpp +40 −2 Original line number Diff line number Diff line Loading @@ -30,8 +30,12 @@ #include <cutils/uevent.h> #include <healthd/healthd.h> #include <BpfSyscallWrappers.h> #include <health/utils.h> using android::base::ErrnoError; using android::base::Result; using android::base::unique_fd; using namespace android; using namespace std::chrono_literals; Loading Loading @@ -116,7 +120,6 @@ void HealthLoop::PeriodicChores() { ScheduleBatteryUpdate(); } // TODO(b/140330870): Use BPF instead. #define UEVENT_MSG_LEN 2048 void HealthLoop::UeventEvent(uint32_t epevents) { // No need to lock because uevent_fd_ is guaranteed to be initialized. Loading Loading @@ -152,8 +155,26 @@ void HealthLoop::UeventEvent(uint32_t epevents) { } } // Attach a BPF filter to the @uevent_fd file descriptor. This fails in recovery mode because BPF is // not supported in recovery mode. This fails for kernel versions 5.4 and before because the BPF // program is rejected by the BPF verifier of older kernels. Result<void> HealthLoop::AttachFilter(int uevent_fd) { static const char prg[] = "/sys/fs/bpf/vendor/prog_filterPowerSupplyEvents_skfilter_power_supply"; int filter_fd(bpf::retrieveProgram(prg)); if (filter_fd < 0) { return ErrnoError() << "failed to load BPF program " << prg; } if (setsockopt(uevent_fd, SOL_SOCKET, SO_ATTACH_BPF, &filter_fd, sizeof(filter_fd)) < 0) { close(filter_fd); return ErrnoError() << "failed to attach BPF program"; } close(filter_fd); return {}; } void HealthLoop::UeventInit(void) { uevent_fd_.reset(uevent_open_socket(64 * 1024, true)); uevent_fd_.reset(uevent_create_socket(64 * 1024, true)); if (uevent_fd_ < 0) { KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n"); Loading @@ -161,8 +182,25 @@ void HealthLoop::UeventInit(void) { } fcntl(uevent_fd_, F_SETFL, O_NONBLOCK); Result<void> attach_result = AttachFilter(uevent_fd_); if (!attach_result.ok()) { std::string error_msg = attach_result.error().message(); error_msg += ". This is expected in recovery mode and also for kernel versions before 5.10."; KLOG_WARNING(LOG_TAG, "%s", error_msg.c_str()); } else { KLOG_INFO(LOG_TAG, "Successfully attached the BPF filter to the uevent socket"); } if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD)) KLOG_ERROR(LOG_TAG, "register for uevent events failed\n"); if (uevent_bind(uevent_fd_.get()) < 0) { uevent_fd_.reset(); KLOG_ERROR(LOG_TAG, "uevent_init: binding socket failed\n"); return; } } void HealthLoop::WakeAlarmEvent(uint32_t /*epevents*/) { Loading health/utils/libhealthloop/filterPowerSupplyEvents.c 0 → 100644 +87 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #include <bpf_helpers.h> // load_word() #include <linux/bpf.h> // struct __sk_buff #include <linux/netlink.h> // struct nlmsghdr #include <stdint.h> // uint32_t // M4: match 4 bytes. Returns 0 if all bytes match. static inline uint32_t M4(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) { return load_word(skb, offset) ^ ((c0 << 24) | (c1 << 16) | (c2 << 8) | c3); } // M2: match 2 bytes. Returns 0 if all bytes match. static inline uint16_t M2(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1) { return load_half(skb, offset) ^ ((c0 << 8) | c1); } // M1: match 1 byte. Returns 0 in case of a match. static inline uint8_t M1(struct __sk_buff* skb, unsigned int offset, uint8_t c0) { return load_byte(skb, offset) ^ c0; } // Match "\0SUBSYSTEM=". Returns 0 in case of a match. #define MATCH_SUBSYSTEM_LENGTH 11 static inline uint32_t match_subsystem(struct __sk_buff* skb, unsigned int offset) { return M4(skb, offset + 0, '\0', 'S', 'U', 'B') | M4(skb, offset + 4, 'S', 'Y', 'S', 'T') | M2(skb, offset + 8, 'E', 'M') | M1(skb, offset + 10, '='); } // Match "power_supply\0". Returns 0 in case of a match. #define MATCH_POWER_SUPPLY_LENGTH 13 static inline uint32_t match_power_supply(struct __sk_buff* skb, unsigned int offset) { return M4(skb, offset + 0, 'p', 'o', 'w', 'e') | M4(skb, offset + 4, 'r', '_', 's', 'u') | M4(skb, offset + 8, 'p', 'p', 'l', 'y') | M1(skb, offset + 12, '\0'); } // The Linux kernel 5.4 BPF verifier rejects this program, probably because of its size. Hence the // restriction that the kernel version must be at least 5.10. DEFINE_BPF_PROG_KVER("skfilter/power_supply", AID_ROOT, AID_SYSTEM, filterPowerSupplyEvents, KVER(5, 10, 0)) (struct __sk_buff* skb) { uint32_t i; // The first character matched by match_subsystem() is a '\0'. Starting // right past the netlink message header is fine since the SUBSYSTEM= text // never occurs at the start. See also the kobject_uevent_env() implementation: // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kobject_uevent.c?#n473 // The upper bound of this loop has been chosen not to exceed the maximum // number of instructions in a BPF program (BPF loops are unrolled). for (i = sizeof(struct nlmsghdr); i < 256; ++i) { if (i + MATCH_SUBSYSTEM_LENGTH > skb->len) { break; } if (match_subsystem(skb, i) == 0) { goto found_subsystem; } } // The SUBSYSTEM= text has not been found in the bytes that have been // examined: let the user space software perform filtering. return skb->len; found_subsystem: i += MATCH_SUBSYSTEM_LENGTH; if (i + MATCH_POWER_SUPPLY_LENGTH <= skb->len && match_power_supply(skb, i) == 0) { return skb->len; } return 0; } LICENSE("Apache 2.0"); CRITICAL("healthd"); health/utils/libhealthloop/filterPowerSupplyEventsTest.cpp 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #include <android-base/unique_fd.h> #include <bpf/libbpf.h> #include <gtest/gtest.h> #include <linux/bpf.h> // SO_ATTACH_BPF #include <linux/netlink.h> #include <netinet/in.h> #include <stdio.h> #include <sys/socket.h> #include <string> #include <string_view> #define ASSERT_UNIX_OK(e) ASSERT_GE(e, 0) << strerror(errno) // TODO(bvanassche): remove the code below. See also b/357099095. #ifndef SO_ATTACH_BPF #define SO_ATTACH_BPF 50 // From <asm-generic/socket.h>. #endif using ::android::base::unique_fd; using ::testing::ScopedTrace; struct test_data { bool discarded; std::string_view str; }; static const uint8_t binary_bpf_prog[] = { #include "filterPowerSupplyEvents.h" }; static std::vector<std::unique_ptr<ScopedTrace>>* msg_vec; std::ostream& operator<<(std::ostream& os, const test_data& td) { os << "{.discarded=" << td.discarded << ", .str="; for (auto c : td.str) { if (isprint(c)) { os << c; } else { os << "."; } } return os << '}'; } #define RECORD_ERR_MSG(fmt, ...) \ do { \ char* str; \ if (asprintf(&str, fmt, ##__VA_ARGS__) < 0) break; \ auto st = std::make_unique<ScopedTrace>(__FILE__, __LINE__, str); \ msg_vec->emplace_back(std::move(st)); \ free(str); \ } while (0) int libbpf_print_fn(enum libbpf_print_level, const char* fmt, va_list args) { char* str; if (vasprintf(&str, fmt, args) < 0) { return 0; } msg_vec->emplace_back(std::make_unique<ScopedTrace>(__FILE__, -1, str)); free(str); return 0; } static void record_libbpf_output() { libbpf_set_print(libbpf_print_fn); } class filterPseTest : public testing::TestWithParam<test_data> {}; struct ConnectedSockets { unique_fd write_fd; unique_fd read_fd; }; // socketpair() only supports AF_UNIX sockets. AF_UNIX sockets do not // support BPF filters. Hence connect two TCP sockets with each other. static ConnectedSockets ConnectSockets(int domain, int type, int protocol) { int _server_fd = socket(domain, type, protocol); if (_server_fd < 0) { return {}; } unique_fd server_fd(_server_fd); int _write_fd = socket(domain, type, protocol); if (_write_fd < 0) { RECORD_ERR_MSG("socket: %s", strerror(errno)); return {}; } unique_fd write_fd(_write_fd); struct sockaddr_in sa = {.sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY}; if (bind(_server_fd, (const struct sockaddr*)&sa, sizeof(sa)) < 0) { RECORD_ERR_MSG("bind: %s", strerror(errno)); return {}; } if (listen(_server_fd, 1) < 0) { RECORD_ERR_MSG("listen: %s", strerror(errno)); return {}; } socklen_t addr_len = sizeof(sa); if (getsockname(_server_fd, (struct sockaddr*)&sa, &addr_len) < 0) { RECORD_ERR_MSG("getsockname: %s", strerror(errno)); return {}; } errno = 0; if (connect(_write_fd, (const struct sockaddr*)&sa, sizeof(sa)) < 0 && errno != EINPROGRESS) { RECORD_ERR_MSG("connect: %s", strerror(errno)); return {}; } int _read_fd = accept(_server_fd, NULL, NULL); if (_read_fd < 0) { RECORD_ERR_MSG("accept: %s", strerror(errno)); return {}; } unique_fd read_fd(_read_fd); return {.write_fd = std::move(write_fd), .read_fd = std::move(read_fd)}; } TEST_P(filterPseTest, filterPse) { if (getuid() != 0) { GTEST_SKIP() << "Must be run as root."; return; } if (!msg_vec) { msg_vec = new typeof(*msg_vec); } std::unique_ptr<int, void (*)(int*)> clear_msg_vec_at_end_of_scope(new int, [](int* p) { msg_vec->clear(); delete p; }); record_libbpf_output(); auto connected_sockets = ConnectSockets(AF_INET, SOCK_STREAM, 0); unique_fd write_fd = std::move(connected_sockets.write_fd); unique_fd read_fd = std::move(connected_sockets.read_fd); ASSERT_UNIX_OK(fcntl(read_fd, F_SETFL, O_NONBLOCK)); bpf_object* obj = bpf_object__open_mem(binary_bpf_prog, sizeof(binary_bpf_prog), NULL); ASSERT_TRUE(obj) << "bpf_object__open() failed" << strerror(errno); // Find the BPF program within the object. bpf_program* prog = bpf_object__find_program_by_name(obj, "filterPowerSupplyEvents"); ASSERT_TRUE(prog); ASSERT_UNIX_OK(bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER)); ASSERT_UNIX_OK(bpf_object__load(obj)); int filter_fd = bpf_program__fd(prog); ASSERT_UNIX_OK(filter_fd); int setsockopt_result = setsockopt(read_fd, SOL_SOCKET, SO_ATTACH_BPF, &filter_fd, sizeof(filter_fd)); ASSERT_UNIX_OK(setsockopt_result); const test_data param = GetParam(); const std::string header(sizeof(struct nlmsghdr), '\0'); ASSERT_EQ(header.length(), sizeof(struct nlmsghdr)); const std::string data = header + std::string(param.str); const size_t len = data.length(); std::cerr.write(data.data(), data.length()); std::cerr << ")\n"; ASSERT_EQ(write(write_fd, data.data(), len), len); std::array<uint8_t, 512> read_buf; int bytes_read = read(read_fd, read_buf.data(), read_buf.size()); if (bytes_read < 0) { ASSERT_EQ(errno, EAGAIN); bytes_read = 0; } else { ASSERT_LT(bytes_read, read_buf.size()); } EXPECT_EQ(bytes_read, param.discarded ? 0 : len); bpf_object__close(obj); } INSTANTIATE_TEST_SUITE_P( filterPse, filterPseTest, testing::Values(test_data{false, "a"}, test_data{true, std::string_view("abc\0SUBSYSTEM=block\0", 20)}, test_data{true, std::string_view("\0SUBSYSTEM=block", 16)}, test_data{true, std::string_view("\0SUBSYSTEM=power_supply", 23)}, test_data{false, std::string_view("\0SUBSYSTEM=power_supply\0", 24)}, test_data{ false, "012345678901234567890123456789012345678901234567890123456789012345" "678901234567890123456789012345678901234567890123456789012345678901" "234567890123456789012345678901234567890123456789012345678901234567" "890123456789012345678901234567890123456789\0SUBSYSTEM=block\0"})); health/utils/libhealthloop/include/health/HealthLoop.h +2 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <mutex> #include <vector> #include <android-base/result.h> #include <android-base/unique_fd.h> #include <healthd/healthd.h> Loading Loading @@ -87,6 +88,7 @@ class HealthLoop { }; int InitInternal(); static android::base::Result<void> AttachFilter(int uevent_fd); void MainLoop(); void WakeAlarmInit(); void WakeAlarmEvent(uint32_t); Loading Loading
health/utils/libhealthloop/Android.bp +39 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,17 @@ package { default_applicable_licenses: ["hardware_interfaces_license"], } bpf { name: "filterPowerSupplyEvents.o", srcs: ["filterPowerSupplyEvents.c"], // "vendor: true" because all binaries that use this BPF filter are vendor // binaries. vendor: true, } // Since "required" sections are ignored in static library definitions, // filterPowerSupplyEvents.o has been added in // build/make/target/product/base_vendor.mk. cc_library_static { name: "libhealthloop", vendor_available: true, Loading @@ -34,6 +45,7 @@ cc_library_static { "libcutils", ], header_libs: [ "bpf_headers", "libbatteryservice_headers", "libhealthd_headers", "libutils_headers", Loading @@ -42,3 +54,30 @@ cc_library_static { "include", ], } genrule { name: "filterPowerSupplyEvents.h", out: ["filterPowerSupplyEvents.h"], srcs: [":filterPowerSupplyEvents.o"], cmd: "cat $(in) | od -v -tx1 | cut -c9- | grep -v '^$$' | sed 's/^/0x/;s/ /, 0x/g;s/^, //;s/$$/,/' > $(out)", } cc_test_host { name: "filterPowerSupplyEventsTest", team: "trendy_team_pixel_system_sw_storage", srcs: [ "filterPowerSupplyEventsTest.cpp", ], shared_libs: [ "libbase", "libbpf", ], static_libs: [ "libgmock", ], generated_headers: [ "filterPowerSupplyEvents.h", "libbpf_headers", ], compile_multilib: "64", }
health/utils/libhealthloop/HealthLoop.cpp +40 −2 Original line number Diff line number Diff line Loading @@ -30,8 +30,12 @@ #include <cutils/uevent.h> #include <healthd/healthd.h> #include <BpfSyscallWrappers.h> #include <health/utils.h> using android::base::ErrnoError; using android::base::Result; using android::base::unique_fd; using namespace android; using namespace std::chrono_literals; Loading Loading @@ -116,7 +120,6 @@ void HealthLoop::PeriodicChores() { ScheduleBatteryUpdate(); } // TODO(b/140330870): Use BPF instead. #define UEVENT_MSG_LEN 2048 void HealthLoop::UeventEvent(uint32_t epevents) { // No need to lock because uevent_fd_ is guaranteed to be initialized. Loading Loading @@ -152,8 +155,26 @@ void HealthLoop::UeventEvent(uint32_t epevents) { } } // Attach a BPF filter to the @uevent_fd file descriptor. This fails in recovery mode because BPF is // not supported in recovery mode. This fails for kernel versions 5.4 and before because the BPF // program is rejected by the BPF verifier of older kernels. Result<void> HealthLoop::AttachFilter(int uevent_fd) { static const char prg[] = "/sys/fs/bpf/vendor/prog_filterPowerSupplyEvents_skfilter_power_supply"; int filter_fd(bpf::retrieveProgram(prg)); if (filter_fd < 0) { return ErrnoError() << "failed to load BPF program " << prg; } if (setsockopt(uevent_fd, SOL_SOCKET, SO_ATTACH_BPF, &filter_fd, sizeof(filter_fd)) < 0) { close(filter_fd); return ErrnoError() << "failed to attach BPF program"; } close(filter_fd); return {}; } void HealthLoop::UeventInit(void) { uevent_fd_.reset(uevent_open_socket(64 * 1024, true)); uevent_fd_.reset(uevent_create_socket(64 * 1024, true)); if (uevent_fd_ < 0) { KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n"); Loading @@ -161,8 +182,25 @@ void HealthLoop::UeventInit(void) { } fcntl(uevent_fd_, F_SETFL, O_NONBLOCK); Result<void> attach_result = AttachFilter(uevent_fd_); if (!attach_result.ok()) { std::string error_msg = attach_result.error().message(); error_msg += ". This is expected in recovery mode and also for kernel versions before 5.10."; KLOG_WARNING(LOG_TAG, "%s", error_msg.c_str()); } else { KLOG_INFO(LOG_TAG, "Successfully attached the BPF filter to the uevent socket"); } if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD)) KLOG_ERROR(LOG_TAG, "register for uevent events failed\n"); if (uevent_bind(uevent_fd_.get()) < 0) { uevent_fd_.reset(); KLOG_ERROR(LOG_TAG, "uevent_init: binding socket failed\n"); return; } } void HealthLoop::WakeAlarmEvent(uint32_t /*epevents*/) { Loading
health/utils/libhealthloop/filterPowerSupplyEvents.c 0 → 100644 +87 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #include <bpf_helpers.h> // load_word() #include <linux/bpf.h> // struct __sk_buff #include <linux/netlink.h> // struct nlmsghdr #include <stdint.h> // uint32_t // M4: match 4 bytes. Returns 0 if all bytes match. static inline uint32_t M4(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) { return load_word(skb, offset) ^ ((c0 << 24) | (c1 << 16) | (c2 << 8) | c3); } // M2: match 2 bytes. Returns 0 if all bytes match. static inline uint16_t M2(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1) { return load_half(skb, offset) ^ ((c0 << 8) | c1); } // M1: match 1 byte. Returns 0 in case of a match. static inline uint8_t M1(struct __sk_buff* skb, unsigned int offset, uint8_t c0) { return load_byte(skb, offset) ^ c0; } // Match "\0SUBSYSTEM=". Returns 0 in case of a match. #define MATCH_SUBSYSTEM_LENGTH 11 static inline uint32_t match_subsystem(struct __sk_buff* skb, unsigned int offset) { return M4(skb, offset + 0, '\0', 'S', 'U', 'B') | M4(skb, offset + 4, 'S', 'Y', 'S', 'T') | M2(skb, offset + 8, 'E', 'M') | M1(skb, offset + 10, '='); } // Match "power_supply\0". Returns 0 in case of a match. #define MATCH_POWER_SUPPLY_LENGTH 13 static inline uint32_t match_power_supply(struct __sk_buff* skb, unsigned int offset) { return M4(skb, offset + 0, 'p', 'o', 'w', 'e') | M4(skb, offset + 4, 'r', '_', 's', 'u') | M4(skb, offset + 8, 'p', 'p', 'l', 'y') | M1(skb, offset + 12, '\0'); } // The Linux kernel 5.4 BPF verifier rejects this program, probably because of its size. Hence the // restriction that the kernel version must be at least 5.10. DEFINE_BPF_PROG_KVER("skfilter/power_supply", AID_ROOT, AID_SYSTEM, filterPowerSupplyEvents, KVER(5, 10, 0)) (struct __sk_buff* skb) { uint32_t i; // The first character matched by match_subsystem() is a '\0'. Starting // right past the netlink message header is fine since the SUBSYSTEM= text // never occurs at the start. See also the kobject_uevent_env() implementation: // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kobject_uevent.c?#n473 // The upper bound of this loop has been chosen not to exceed the maximum // number of instructions in a BPF program (BPF loops are unrolled). for (i = sizeof(struct nlmsghdr); i < 256; ++i) { if (i + MATCH_SUBSYSTEM_LENGTH > skb->len) { break; } if (match_subsystem(skb, i) == 0) { goto found_subsystem; } } // The SUBSYSTEM= text has not been found in the bytes that have been // examined: let the user space software perform filtering. return skb->len; found_subsystem: i += MATCH_SUBSYSTEM_LENGTH; if (i + MATCH_POWER_SUPPLY_LENGTH <= skb->len && match_power_supply(skb, i) == 0) { return skb->len; } return 0; } LICENSE("Apache 2.0"); CRITICAL("healthd");
health/utils/libhealthloop/filterPowerSupplyEventsTest.cpp 0 → 100644 +207 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #include <android-base/unique_fd.h> #include <bpf/libbpf.h> #include <gtest/gtest.h> #include <linux/bpf.h> // SO_ATTACH_BPF #include <linux/netlink.h> #include <netinet/in.h> #include <stdio.h> #include <sys/socket.h> #include <string> #include <string_view> #define ASSERT_UNIX_OK(e) ASSERT_GE(e, 0) << strerror(errno) // TODO(bvanassche): remove the code below. See also b/357099095. #ifndef SO_ATTACH_BPF #define SO_ATTACH_BPF 50 // From <asm-generic/socket.h>. #endif using ::android::base::unique_fd; using ::testing::ScopedTrace; struct test_data { bool discarded; std::string_view str; }; static const uint8_t binary_bpf_prog[] = { #include "filterPowerSupplyEvents.h" }; static std::vector<std::unique_ptr<ScopedTrace>>* msg_vec; std::ostream& operator<<(std::ostream& os, const test_data& td) { os << "{.discarded=" << td.discarded << ", .str="; for (auto c : td.str) { if (isprint(c)) { os << c; } else { os << "."; } } return os << '}'; } #define RECORD_ERR_MSG(fmt, ...) \ do { \ char* str; \ if (asprintf(&str, fmt, ##__VA_ARGS__) < 0) break; \ auto st = std::make_unique<ScopedTrace>(__FILE__, __LINE__, str); \ msg_vec->emplace_back(std::move(st)); \ free(str); \ } while (0) int libbpf_print_fn(enum libbpf_print_level, const char* fmt, va_list args) { char* str; if (vasprintf(&str, fmt, args) < 0) { return 0; } msg_vec->emplace_back(std::make_unique<ScopedTrace>(__FILE__, -1, str)); free(str); return 0; } static void record_libbpf_output() { libbpf_set_print(libbpf_print_fn); } class filterPseTest : public testing::TestWithParam<test_data> {}; struct ConnectedSockets { unique_fd write_fd; unique_fd read_fd; }; // socketpair() only supports AF_UNIX sockets. AF_UNIX sockets do not // support BPF filters. Hence connect two TCP sockets with each other. static ConnectedSockets ConnectSockets(int domain, int type, int protocol) { int _server_fd = socket(domain, type, protocol); if (_server_fd < 0) { return {}; } unique_fd server_fd(_server_fd); int _write_fd = socket(domain, type, protocol); if (_write_fd < 0) { RECORD_ERR_MSG("socket: %s", strerror(errno)); return {}; } unique_fd write_fd(_write_fd); struct sockaddr_in sa = {.sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY}; if (bind(_server_fd, (const struct sockaddr*)&sa, sizeof(sa)) < 0) { RECORD_ERR_MSG("bind: %s", strerror(errno)); return {}; } if (listen(_server_fd, 1) < 0) { RECORD_ERR_MSG("listen: %s", strerror(errno)); return {}; } socklen_t addr_len = sizeof(sa); if (getsockname(_server_fd, (struct sockaddr*)&sa, &addr_len) < 0) { RECORD_ERR_MSG("getsockname: %s", strerror(errno)); return {}; } errno = 0; if (connect(_write_fd, (const struct sockaddr*)&sa, sizeof(sa)) < 0 && errno != EINPROGRESS) { RECORD_ERR_MSG("connect: %s", strerror(errno)); return {}; } int _read_fd = accept(_server_fd, NULL, NULL); if (_read_fd < 0) { RECORD_ERR_MSG("accept: %s", strerror(errno)); return {}; } unique_fd read_fd(_read_fd); return {.write_fd = std::move(write_fd), .read_fd = std::move(read_fd)}; } TEST_P(filterPseTest, filterPse) { if (getuid() != 0) { GTEST_SKIP() << "Must be run as root."; return; } if (!msg_vec) { msg_vec = new typeof(*msg_vec); } std::unique_ptr<int, void (*)(int*)> clear_msg_vec_at_end_of_scope(new int, [](int* p) { msg_vec->clear(); delete p; }); record_libbpf_output(); auto connected_sockets = ConnectSockets(AF_INET, SOCK_STREAM, 0); unique_fd write_fd = std::move(connected_sockets.write_fd); unique_fd read_fd = std::move(connected_sockets.read_fd); ASSERT_UNIX_OK(fcntl(read_fd, F_SETFL, O_NONBLOCK)); bpf_object* obj = bpf_object__open_mem(binary_bpf_prog, sizeof(binary_bpf_prog), NULL); ASSERT_TRUE(obj) << "bpf_object__open() failed" << strerror(errno); // Find the BPF program within the object. bpf_program* prog = bpf_object__find_program_by_name(obj, "filterPowerSupplyEvents"); ASSERT_TRUE(prog); ASSERT_UNIX_OK(bpf_program__set_type(prog, BPF_PROG_TYPE_SOCKET_FILTER)); ASSERT_UNIX_OK(bpf_object__load(obj)); int filter_fd = bpf_program__fd(prog); ASSERT_UNIX_OK(filter_fd); int setsockopt_result = setsockopt(read_fd, SOL_SOCKET, SO_ATTACH_BPF, &filter_fd, sizeof(filter_fd)); ASSERT_UNIX_OK(setsockopt_result); const test_data param = GetParam(); const std::string header(sizeof(struct nlmsghdr), '\0'); ASSERT_EQ(header.length(), sizeof(struct nlmsghdr)); const std::string data = header + std::string(param.str); const size_t len = data.length(); std::cerr.write(data.data(), data.length()); std::cerr << ")\n"; ASSERT_EQ(write(write_fd, data.data(), len), len); std::array<uint8_t, 512> read_buf; int bytes_read = read(read_fd, read_buf.data(), read_buf.size()); if (bytes_read < 0) { ASSERT_EQ(errno, EAGAIN); bytes_read = 0; } else { ASSERT_LT(bytes_read, read_buf.size()); } EXPECT_EQ(bytes_read, param.discarded ? 0 : len); bpf_object__close(obj); } INSTANTIATE_TEST_SUITE_P( filterPse, filterPseTest, testing::Values(test_data{false, "a"}, test_data{true, std::string_view("abc\0SUBSYSTEM=block\0", 20)}, test_data{true, std::string_view("\0SUBSYSTEM=block", 16)}, test_data{true, std::string_view("\0SUBSYSTEM=power_supply", 23)}, test_data{false, std::string_view("\0SUBSYSTEM=power_supply\0", 24)}, test_data{ false, "012345678901234567890123456789012345678901234567890123456789012345" "678901234567890123456789012345678901234567890123456789012345678901" "234567890123456789012345678901234567890123456789012345678901234567" "890123456789012345678901234567890123456789\0SUBSYSTEM=block\0"}));
health/utils/libhealthloop/include/health/HealthLoop.h +2 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <mutex> #include <vector> #include <android-base/result.h> #include <android-base/unique_fd.h> #include <healthd/healthd.h> Loading Loading @@ -87,6 +88,7 @@ class HealthLoop { }; int InitInternal(); static android::base::Result<void> AttachFilter(int uevent_fd); void MainLoop(); void WakeAlarmInit(); void WakeAlarmEvent(uint32_t); Loading