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