1 /* 2 * Copyright (C) 2017 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 #ifndef LOG_TAG 18 #define LOG_TAG "bpfloader" 19 #endif 20 21 #include <arpa/inet.h> 22 #include <elf.h> 23 #include <error.h> 24 #include <fcntl.h> 25 #include <inttypes.h> 26 #include <linux/bpf.h> 27 #include <linux/unistd.h> 28 #include <net/if.h> 29 #include <stdint.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include <sys/mman.h> 36 #include <sys/socket.h> 37 #include <sys/stat.h> 38 #include <sys/types.h> 39 40 #include <android-base/stringprintf.h> 41 #include <android-base/unique_fd.h> 42 #include <cutils/log.h> 43 44 #include <netdutils/MemBlock.h> 45 #include <netdutils/Misc.h> 46 #include <netdutils/Slice.h> 47 #include "bpf/BpfUtils.h" 48 #include "bpf/bpf_shared.h" 49 50 using android::base::unique_fd; 51 using android::netdutils::MemBlock; 52 using android::netdutils::Slice; 53 54 #define BPF_PROG_PATH "/system/etc/bpf" 55 #define BPF_PROG_SRC BPF_PROG_PATH "/bpf_kern.o" 56 #define MAP_LD_CMD_HEAD 0x18 57 58 #define FAIL(...) \ 59 do { \ 60 ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)); \ 61 exit(-1); \ 62 } while (0) 63 64 // The BPF instruction bytes that we need to replace. x is a placeholder (e.g., COOKIE_TAG_MAP). 65 #define MAP_SEARCH_PATTERN(x) \ 66 { \ 67 0x18, 0x01, 0x00, 0x00, \ 68 (x)[0], (x)[1], (x)[2], (x)[3], \ 69 0x00, 0x00, 0x00, 0x00, \ 70 (x)[4], (x)[5], (x)[6], (x)[7] \ 71 } 72 73 // The bytes we'll replace them with. x is the actual fd number for the map at runtime. 74 // The second byte is changed from 0x01 to 0x11 since 0x11 is the special command used 75 // for bpf map fd loading. The original 0x01 is only a normal load command. 76 #define MAP_REPLACE_PATTERN(x) \ 77 { \ 78 0x18, 0x11, 0x00, 0x00, \ 79 (x)[0], (x)[1], (x)[2], (x)[3], \ 80 0x00, 0x00, 0x00, 0x00, \ 81 (x)[4], (x)[5], (x)[6], (x)[7] \ 82 } 83 84 #define DECLARE_MAP(_mapFd, _mapPath) \ 85 unique_fd _mapFd(android::bpf::mapRetrieve((_mapPath), 0)); \ 86 if (_mapFd < 0) { \ 87 FAIL("Failed to get map from %s", (_mapPath)); \ 88 } 89 90 #define MAP_CMD_SIZE 16 91 #define LOG_BUF_SIZE 65536 92 93 namespace android { 94 namespace bpf { 95 96 struct ReplacePattern { 97 std::array<uint8_t, MAP_CMD_SIZE> search; 98 std::array<uint8_t, MAP_CMD_SIZE> replace; 99 100 ReplacePattern(uint64_t dummyFd, int realFd) { 101 // Ensure that the fd numbers are big-endian. 102 uint8_t beDummyFd[sizeof(uint64_t)]; 103 uint8_t beRealFd[sizeof(uint64_t)]; 104 for (size_t i = 0; i < sizeof(uint64_t); i++) { 105 beDummyFd[i] = (dummyFd >> (i * 8)) & 0xFF; 106 beRealFd[i] = (realFd >> (i * 8)) & 0xFF; 107 } 108 search = MAP_SEARCH_PATTERN(beDummyFd); 109 replace = MAP_REPLACE_PATTERN(beRealFd); 110 } 111 }; 112 113 MemBlock cgroupIngressProg; 114 MemBlock cgroupEgressProg; 115 MemBlock xtIngressProg; 116 MemBlock xtEgressProg; 117 118 MemBlock getProgFromMem(Slice buffer, Elf64_Shdr* section) { 119 uint64_t progSize = (uint64_t)section->sh_size; 120 Slice progSection = take(drop(buffer, section->sh_offset), progSize); 121 if (progSection.size() < progSize) FAIL("programSection out of bound\n"); 122 123 MemBlock progCopy(progSection); 124 if (progCopy.get().size() != progSize) { 125 FAIL("program cannot be extracted"); 126 } 127 return progCopy; 128 } 129 130 void parseProgramsFromFile(const char* path) { 131 int fd = open(path, O_RDONLY); 132 if (fd == -1) { 133 FAIL("Failed to open %s program: %s", path, strerror(errno)); 134 } 135 136 struct stat stat; 137 if (fstat(fd, &stat)) FAIL("Fail to get file size"); 138 139 off_t fileLen = stat.st_size; 140 char* baseAddr = (char*)mmap(NULL, fileLen, PROT_READ, MAP_PRIVATE, fd, 0); 141 if (baseAddr == MAP_FAILED) FAIL("Failed to map the program into memory"); 142 143 if ((uint32_t)fileLen < sizeof(Elf64_Ehdr)) FAIL("file size too small for Elf64_Ehdr"); 144 145 Slice buffer(baseAddr, fileLen); 146 147 Slice elfHeader = take(buffer, sizeof(Elf64_Ehdr)); 148 149 if (elfHeader.size() < sizeof(Elf64_Ehdr)) FAIL("bpf buffer does not have complete elf header"); 150 151 Elf64_Ehdr* elf = (Elf64_Ehdr*)elfHeader.base(); 152 // Find section names string table. This is the section whose index is e_shstrndx. 153 if (elf->e_shstrndx == SHN_UNDEF) { 154 FAIL("cannot locate namesSection\n"); 155 } 156 size_t totalSectionSize = (elf->e_shnum) * sizeof(Elf64_Shdr); 157 Slice sections = take(drop(buffer, elf->e_shoff), totalSectionSize); 158 if (sections.size() < totalSectionSize) { 159 FAIL("sections corrupted"); 160 } 161 162 Slice namesSection = take(drop(sections, elf->e_shstrndx * sizeof(Elf64_Shdr)), 163 sizeof(Elf64_Shdr)); 164 if (namesSection.size() != sizeof(Elf64_Shdr)) { 165 FAIL("namesSection corrupted"); 166 } 167 size_t strTabOffset = ((Elf64_Shdr*) namesSection.base())->sh_offset; 168 size_t strTabSize = ((Elf64_Shdr*) namesSection.base())->sh_size; 169 170 Slice strTab = take(drop(buffer, strTabOffset), strTabSize); 171 if (strTab.size() < strTabSize) { 172 FAIL("string table out of bound\n"); 173 } 174 175 for (int i = 0; i < elf->e_shnum; i++) { 176 Slice section = take(drop(sections, i * sizeof(Elf64_Shdr)), sizeof(Elf64_Shdr)); 177 if (section.size() < sizeof(Elf64_Shdr)) { 178 FAIL("section %d is out of bound, section size: %zu, header size: %zu, total size: %zu", 179 i, section.size(), sizeof(Elf64_Shdr), sections.size()); 180 } 181 Elf64_Shdr* sectionPtr = (Elf64_Shdr*)section.base(); 182 Slice nameSlice = drop(strTab, sectionPtr->sh_name); 183 if (nameSlice.size() == 0) { 184 FAIL("nameSlice out of bound, i: %d, strTabSize: %zu, sh_name: %u", i, strTabSize, 185 sectionPtr->sh_name); 186 } 187 if (!strcmp((char *)nameSlice.base(), BPF_CGROUP_INGRESS_PROG_NAME)) { 188 cgroupIngressProg = getProgFromMem(buffer, sectionPtr); 189 } else if (!strcmp((char *)nameSlice.base(), BPF_CGROUP_EGRESS_PROG_NAME)) { 190 cgroupEgressProg = getProgFromMem(buffer, sectionPtr); 191 } else if (!strcmp((char *)nameSlice.base(), XT_BPF_INGRESS_PROG_NAME)) { 192 xtIngressProg = getProgFromMem(buffer, sectionPtr); 193 } else if (!strcmp((char *)nameSlice.base(), XT_BPF_EGRESS_PROG_NAME)) { 194 xtEgressProg = getProgFromMem(buffer, sectionPtr); 195 } 196 } 197 } 198 199 int loadProg(Slice prog, bpf_prog_type type, const std::vector<ReplacePattern>& mapPatterns) { 200 if (prog.size() == 0) { 201 FAIL("Couldn't find or parse program type %d", type); 202 } 203 Slice remaining = prog; 204 while (remaining.size() >= MAP_CMD_SIZE) { 205 // Scan the program, examining all possible places that might be the start of a map load 206 // operation (i.e., all bytes of value MAP_LD_CMD_HEAD). 207 // In each of these places, check whether it is the start of one of the patterns we want to 208 // replace, and if so, replace it. 209 Slice mapHead = findFirstMatching(remaining, MAP_LD_CMD_HEAD); 210 if (mapHead.size() < MAP_CMD_SIZE) break; 211 bool replaced = false; 212 for (const auto& pattern : mapPatterns) { 213 if (!memcmp(mapHead.base(), pattern.search.data(), MAP_CMD_SIZE)) { 214 memcpy(mapHead.base(), pattern.replace.data(), MAP_CMD_SIZE); 215 replaced = true; 216 break; 217 } 218 } 219 remaining = drop(mapHead, replaced ? MAP_CMD_SIZE : sizeof(uint8_t)); 220 } 221 char bpf_log_buf[LOG_BUF_SIZE]; 222 Slice bpfLog = Slice(bpf_log_buf, sizeof(bpf_log_buf)); 223 return bpfProgLoad(type, prog, "Apache 2.0", 0, bpfLog); 224 } 225 226 int loadAndAttachProgram(bpf_attach_type type, const char* path, const char* name, 227 std::vector<ReplacePattern> mapPatterns) { 228 229 unique_fd fd; 230 if (type == BPF_CGROUP_INET_INGRESS) { 231 fd.reset(loadProg(cgroupIngressProg, BPF_PROG_TYPE_CGROUP_SKB, mapPatterns)); 232 } else if (type == BPF_CGROUP_INET_EGRESS) { 233 fd.reset(loadProg(cgroupEgressProg, BPF_PROG_TYPE_CGROUP_SKB, mapPatterns)); 234 } else if (!strcmp(name, XT_BPF_INGRESS_PROG_NAME)) { 235 fd.reset(loadProg(xtIngressProg, BPF_PROG_TYPE_SOCKET_FILTER, mapPatterns)); 236 } else if (!strcmp(name, XT_BPF_EGRESS_PROG_NAME)) { 237 fd.reset(loadProg(xtEgressProg, BPF_PROG_TYPE_SOCKET_FILTER, mapPatterns)); 238 } else { 239 FAIL("Unrecognized program type: %s", name); 240 } 241 242 if (fd < 0) { 243 FAIL("load %s failed: %s", name, strerror(errno)); 244 } 245 int ret = 0; 246 if (type == BPF_CGROUP_INET_EGRESS || type == BPF_CGROUP_INET_INGRESS) { 247 unique_fd cg_fd(open(CGROUP_ROOT_PATH, O_DIRECTORY | O_RDONLY | O_CLOEXEC)); 248 if (cg_fd < 0) { 249 FAIL("Failed to open the cgroup directory"); 250 } 251 ret = attachProgram(type, fd, cg_fd); 252 if (ret) { 253 FAIL("%s attach failed: %s", name, strerror(errno)); 254 } 255 } 256 257 ret = mapPin(fd, path); 258 if (ret) { 259 FAIL("Pin %s as file %s failed: %s", name, path, strerror(errno)); 260 } 261 return 0; 262 } 263 264 } // namespace bpf 265 } // namespace android 266 267 using android::bpf::APP_UID_STATS_MAP_PATH; 268 using android::bpf::BPF_EGRESS_PROG_PATH; 269 using android::bpf::BPF_INGRESS_PROG_PATH; 270 using android::bpf::COOKIE_TAG_MAP_PATH; 271 using android::bpf::DOZABLE_UID_MAP_PATH; 272 using android::bpf::IFACE_STATS_MAP_PATH; 273 using android::bpf::POWERSAVE_UID_MAP_PATH; 274 using android::bpf::STANDBY_UID_MAP_PATH; 275 using android::bpf::TAG_STATS_MAP_PATH; 276 using android::bpf::UID_COUNTERSET_MAP_PATH; 277 using android::bpf::UID_STATS_MAP_PATH; 278 using android::bpf::XT_BPF_EGRESS_PROG_PATH; 279 using android::bpf::XT_BPF_INGRESS_PROG_PATH; 280 281 using android::bpf::ReplacePattern; 282 using android::bpf::loadAndAttachProgram; 283 284 static void usage(void) { 285 ALOGE( "Usage: ./bpfloader [-i] [-e]\n" 286 " -i load ingress bpf program\n" 287 " -e load egress bpf program\n" 288 " -p load prerouting xt_bpf program\n" 289 " -m load mangle xt_bpf program\n"); 290 } 291 292 int main(int argc, char** argv) { 293 int ret = 0; 294 DECLARE_MAP(cookieTagMap, COOKIE_TAG_MAP_PATH); 295 DECLARE_MAP(uidCounterSetMap, UID_COUNTERSET_MAP_PATH); 296 DECLARE_MAP(appUidStatsMap, APP_UID_STATS_MAP_PATH); 297 DECLARE_MAP(uidStatsMap, UID_STATS_MAP_PATH); 298 DECLARE_MAP(tagStatsMap, TAG_STATS_MAP_PATH); 299 DECLARE_MAP(ifaceStatsMap, IFACE_STATS_MAP_PATH); 300 DECLARE_MAP(dozableUidMap, DOZABLE_UID_MAP_PATH); 301 DECLARE_MAP(standbyUidMap, STANDBY_UID_MAP_PATH); 302 DECLARE_MAP(powerSaveUidMap, POWERSAVE_UID_MAP_PATH); 303 304 const std::vector<ReplacePattern> mapPatterns = { 305 ReplacePattern(COOKIE_TAG_MAP, cookieTagMap.get()), 306 ReplacePattern(UID_COUNTERSET_MAP, uidCounterSetMap.get()), 307 ReplacePattern(APP_UID_STATS_MAP, appUidStatsMap.get()), 308 ReplacePattern(UID_STATS_MAP, uidStatsMap.get()), 309 ReplacePattern(TAG_STATS_MAP, tagStatsMap.get()), 310 ReplacePattern(IFACE_STATS_MAP, ifaceStatsMap.get()), 311 ReplacePattern(DOZABLE_UID_MAP, dozableUidMap.get()), 312 ReplacePattern(STANDBY_UID_MAP, standbyUidMap.get()), 313 ReplacePattern(POWERSAVE_UID_MAP, powerSaveUidMap.get()), 314 }; 315 316 int opt; 317 bool doIngress = false, doEgress = false, doPrerouting = false, doMangle = false; 318 while ((opt = getopt(argc, argv, "iepm")) != -1) { 319 switch (opt) { 320 case 'i': 321 doIngress = true; 322 break; 323 case 'e': 324 doEgress = true; 325 break; 326 case 'p': 327 doPrerouting = true; 328 break; 329 case 'm': 330 doMangle = true; 331 break; 332 default: 333 usage(); 334 FAIL("unknown argument %c", opt); 335 } 336 } 337 android::bpf::parseProgramsFromFile(BPF_PROG_SRC); 338 339 if (doIngress) { 340 ret = loadAndAttachProgram(BPF_CGROUP_INET_INGRESS, BPF_INGRESS_PROG_PATH, 341 BPF_CGROUP_INGRESS_PROG_NAME, mapPatterns); 342 if (ret) { 343 FAIL("Failed to set up ingress program"); 344 } 345 } 346 if (doEgress) { 347 ret = loadAndAttachProgram(BPF_CGROUP_INET_EGRESS, BPF_EGRESS_PROG_PATH, 348 BPF_CGROUP_EGRESS_PROG_NAME, mapPatterns); 349 if (ret) { 350 FAIL("Failed to set up ingress program"); 351 } 352 } 353 if (doPrerouting) { 354 ret = loadAndAttachProgram(MAX_BPF_ATTACH_TYPE, XT_BPF_INGRESS_PROG_PATH, 355 XT_BPF_INGRESS_PROG_NAME, mapPatterns); 356 if (ret) { 357 FAIL("Failed to set up xt_bpf program"); 358 } 359 } 360 if (doMangle) { 361 ret = loadAndAttachProgram(MAX_BPF_ATTACH_TYPE, XT_BPF_EGRESS_PROG_PATH, 362 XT_BPF_EGRESS_PROG_NAME, mapPatterns); 363 if (ret) { 364 FAIL("Failed to set up xt_bpf program"); 365 } 366 } 367 return ret; 368 } 369