xref: /aosp_15_r20/hardware/interfaces/health/utils/libhealthloop/filterPowerSupplyEvents.c (revision 4d7e907c777eeecc4c5bd7cf640a754fac206ff7)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <bpf_helpers.h>    // load_word()
18 #include <linux/bpf.h>      // struct __sk_buff
19 #include <linux/netlink.h>  // struct nlmsghdr
20 #include <stdint.h>         // uint32_t
21 
22 // M4: match 4 bytes. Returns 0 if all bytes match.
M4(struct __sk_buff * skb,unsigned int offset,uint8_t c0,uint8_t c1,uint8_t c2,uint8_t c3)23 static inline uint32_t M4(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1,
24                           uint8_t c2, uint8_t c3) {
25     return load_word(skb, offset) ^ ((c0 << 24) | (c1 << 16) | (c2 << 8) | c3);
26 }
27 
28 // M2: match 2 bytes. Returns 0 if all bytes match.
M2(struct __sk_buff * skb,unsigned int offset,uint8_t c0,uint8_t c1)29 static inline uint16_t M2(struct __sk_buff* skb, unsigned int offset, uint8_t c0, uint8_t c1) {
30     return load_half(skb, offset) ^ ((c0 << 8) | c1);
31 }
32 
33 // M1: match 1 byte. Returns 0 in case of a match.
M1(struct __sk_buff * skb,unsigned int offset,uint8_t c0)34 static inline uint8_t M1(struct __sk_buff* skb, unsigned int offset, uint8_t c0) {
35     return load_byte(skb, offset) ^ c0;
36 }
37 
38 // Match "\0SUBSYSTEM=". Returns 0 in case of a match.
39 #define MATCH_SUBSYSTEM_LENGTH 11
match_subsystem(struct __sk_buff * skb,unsigned int offset)40 static inline uint32_t match_subsystem(struct __sk_buff* skb, unsigned int offset) {
41     return M4(skb, offset + 0, '\0', 'S', 'U', 'B') | M4(skb, offset + 4, 'S', 'Y', 'S', 'T') |
42            M2(skb, offset + 8, 'E', 'M') | M1(skb, offset + 10, '=');
43 }
44 
45 // Match "power_supply\0". Returns 0 in case of a match.
46 #define MATCH_POWER_SUPPLY_LENGTH 13
match_power_supply(struct __sk_buff * skb,unsigned int offset)47 static inline uint32_t match_power_supply(struct __sk_buff* skb, unsigned int offset) {
48     return M4(skb, offset + 0, 'p', 'o', 'w', 'e') | M4(skb, offset + 4, 'r', '_', 's', 'u') |
49            M4(skb, offset + 8, 'p', 'p', 'l', 'y') | M1(skb, offset + 12, '\0');
50 }
51 
52 // The Linux kernel 5.4 BPF verifier rejects this program, probably because of its size. Hence the
53 // restriction that the kernel version must be at least 5.10.
54 DEFINE_BPF_PROG_KVER("skfilter/power_supply", AID_ROOT, AID_SYSTEM, filterPowerSupplyEvents,
55                      KVER(5, 10, 0))
56 (struct __sk_buff* skb) {
57     uint32_t i;
58 
59     // The first character matched by match_subsystem() is a '\0'. Starting
60     // right past the netlink message header is fine since the SUBSYSTEM= text
61     // never occurs at the start. See also the kobject_uevent_env() implementation:
62     // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kobject_uevent.c?#n473
63     // The upper bound of this loop has been chosen not to exceed the maximum
64     // number of instructions in a BPF program (BPF loops are unrolled).
65     for (i = sizeof(struct nlmsghdr); i < 256; ++i) {
66         if (i + MATCH_SUBSYSTEM_LENGTH > skb->len) {
67             break;
68         }
69         if (match_subsystem(skb, i) == 0) {
70             goto found_subsystem;
71         }
72     }
73 
74     // The SUBSYSTEM= text has not been found in the bytes that have been
75     // examined: let the user space software perform filtering.
76     return skb->len;
77 
78 found_subsystem:
79     i += MATCH_SUBSYSTEM_LENGTH;
80     if (i + MATCH_POWER_SUPPLY_LENGTH <= skb->len && match_power_supply(skb, i) == 0) {
81         return skb->len;
82     }
83     return 0;
84 }
85 
86 LICENSE("Apache 2.0");
87 CRITICAL("healthd");
88