diff --git a/bpfloader/Android.bp b/bpfloader/Android.bp index eea547d..9f92ce3 100644 --- a/bpfloader/Android.bp +++ b/bpfloader/Android.bp @@ -37,6 +37,7 @@ cc_binary { "liblog", "libnetdbpf", "libnetdutils", + "libbpf", ], srcs: [ "BpfLoader.cpp", @@ -44,6 +45,8 @@ cc_binary { required: [ "bpf_kern.o", +// Uncomment once security related patches ready +// "time_in_state.o", ], } diff --git a/bpfloader/BpfLoader.cpp b/bpfloader/BpfLoader.cpp index e173e67..a09af29 100644 --- a/bpfloader/BpfLoader.cpp +++ b/bpfloader/BpfLoader.cpp @@ -19,6 +19,7 @@ #endif #include +#include #include #include #include @@ -38,19 +39,22 @@ #include #include +#include #include +#include #include - #include #include #include "bpf/BpfUtils.h" #include "netdbpf/bpf_shared.h" +using android::base::EndsWith; using android::base::unique_fd; using android::netdutils::Slice; +using std::string; -#define BPF_PROG_PATH "/system/etc/bpf" -#define BPF_PROG_SRC BPF_PROG_PATH "/bpf_kern.o" +#define BPF_PROG_PATH "/system/etc/bpf/" +#define BPF_PROG_SRC BPF_PROG_PATH "bpf_kern.o" #define CLEANANDEXIT(ret, mapPatterns) \ do { \ @@ -65,7 +69,28 @@ using android::netdutils::Slice; using android::bpf::BpfMapInfo; using android::bpf::BpfProgInfo; +void loadAllElfObjects(void) { + DIR* dir; + struct dirent* ent; + + if ((dir = opendir(BPF_PROG_PATH)) != NULL) { + while ((ent = readdir(dir)) != NULL) { + string s = ent->d_name; + if (!EndsWith(s, ".o")) continue; + + string progPath = BPF_PROG_PATH + s; + + int ret = android::bpf::loadProg(progPath.c_str()); + ALOGI("Attempted load object: %s, ret: %s", progPath.c_str(), std::strerror(-ret)); + } + closedir(dir); + } +} + int main() { + // Load all ELF objects, create programs and maps, and pin them + loadAllElfObjects(); + const std::vector mapPatterns = { BpfMapInfo(COOKIE_TAG_MAP, COOKIE_TAG_MAP_PATH), BpfMapInfo(UID_COUNTERSET_MAP, UID_COUNTERSET_MAP_PATH), diff --git a/libbpf_android/Android.bp b/libbpf_android/Android.bp index 2ffacf3..721792f 100644 --- a/libbpf_android/Android.bp +++ b/libbpf_android/Android.bp @@ -34,6 +34,7 @@ cc_library { android: { srcs: [ "BpfUtils.cpp", + "Loader.cpp", ], sanitize: { misc_undefined: ["integer"], @@ -46,6 +47,7 @@ cc_library { "libutils", "liblog", "libnetdutils", + "libbpf", ], header_libs: [ "libbpf_android_headers" diff --git a/libbpf_android/Loader.cpp b/libbpf_android/Loader.cpp new file mode 100644 index 0000000..332c0b4 --- /dev/null +++ b/libbpf_android/Loader.cpp @@ -0,0 +1,588 @@ +/* + * 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. + */ + +#define LOG_TAG "LibBpfLoader" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "LoaderUtils.h" +#include "include/libbpf_android.h" + +#include +#include +#include +#include + +#include + +#define BPF_FS_PATH "/sys/fs/bpf/" + +// Size of the BPF log buffer for verifier logging +#define BPF_LOAD_LOG_SZ 0x1ffff + +using android::base::StartsWith; +using std::ifstream; +using std::ios; +using std::vector; + +namespace android { +namespace bpf { + +typedef struct { + const char* name; + enum bpf_prog_type type; +} sectionType; + +/* + * Map section name prefixes to program types, the section name will be: + * SEC(/) + * For example: + * SEC("tracepoint/sched_switch_func") where sched_switch_funcs + * is the name of the program, and tracepoint is the type. + */ +sectionType sectionNameTypes[] = { + { "kprobe", BPF_PROG_TYPE_KPROBE }, + { "tracepoint", BPF_PROG_TYPE_TRACEPOINT }, + { "skfilter", BPF_PROG_TYPE_SOCKET_FILTER }, + { "cgroupskb", BPF_PROG_TYPE_CGROUP_SKB }, + + /* End of table */ + { "END", BPF_PROG_TYPE_UNSPEC }, +}; + +typedef struct { + enum bpf_prog_type type; + string name; + vector data; + vector rel_data; + + int prog_fd; /* fd after loading */ +} codeSection; + +/* Common with the eBPF C program */ +struct bpf_map_def { + enum bpf_map_type type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; + unsigned int inner_map_idx; + unsigned int numa_node; +}; + +static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) { + elfFile.seekg(0); + if (elfFile.fail()) return -1; + + if (!elfFile.read((char*)eh, sizeof(*eh))) return -1; + + return 0; +} + +/* Reads all section header tables into an Shdr array */ +static int readSectionHeadersAll(ifstream& elfFile, vector& shTable) { + Elf64_Ehdr eh; + int ret = 0; + + ret = readElfHeader(elfFile, &eh); + if (ret) return ret; + + elfFile.seekg(eh.e_shoff); + if (elfFile.fail()) return -1; + + /* Read shdr table entries */ + shTable.resize(eh.e_shnum); + + if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM; + + return 0; +} + +/* Read a section by its index - for ex to get sec hdr strtab blob */ +static int readSectionByIdx(ifstream& elfFile, int id, vector& sec) { + vector shTable; + int entries, ret = 0; + + ret = readSectionHeadersAll(elfFile, shTable); + if (ret) return ret; + entries = shTable.size(); + + elfFile.seekg(shTable[id].sh_offset); + if (elfFile.fail()) return -1; + + sec.resize(shTable[id].sh_size); + if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1; + + return 0; +} + +/* Read whole section header string table */ +static int readSectionHeaderStrtab(ifstream& elfFile, vector& strtab) { + Elf64_Ehdr eh; + int ret = 0; + + ret = readElfHeader(elfFile, &eh); + if (ret) return ret; + + ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab); + if (ret) return ret; + + return 0; +} + +/* Get name from offset in strtab */ +static int getSymName(ifstream& elfFile, int nameOff, string& name) { + int ret; + vector secStrTab; + + ret = readSectionHeaderStrtab(elfFile, secStrTab); + if (ret) return ret; + + if (nameOff >= (int)secStrTab.size()) return -1; + + name = string((char*)secStrTab.data() + nameOff); + return 0; +} + +/* Reads a full section by name - example to get the GPL license */ +static int readSectionByName(const char* name, ifstream& elfFile, vector& data) { + vector secStrTab; + vector shTable; + int ret; + + ret = readSectionHeadersAll(elfFile, shTable); + if (ret) return ret; + + ret = readSectionHeaderStrtab(elfFile, secStrTab); + if (ret) return ret; + + for (int i = 0; i < (int)shTable.size(); i++) { + char* secname = secStrTab.data() + shTable[i].sh_name; + if (!secname) continue; + + if (!strcmp(secname, name)) { + vector dataTmp; + dataTmp.resize(shTable[i].sh_size); + + elfFile.seekg(shTable[i].sh_offset); + if (elfFile.fail()) return -1; + + if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1; + + data = dataTmp; + return 0; + } + } + return -2; +} + +static int readSectionByType(ifstream& elfFile, int type, vector& data) { + int ret; + vector shTable; + + ret = readSectionHeadersAll(elfFile, shTable); + if (ret) return ret; + + for (int i = 0; i < (int)shTable.size(); i++) { + if ((int)shTable[i].sh_type != type) continue; + + vector dataTmp; + dataTmp.resize(shTable[i].sh_size); + + elfFile.seekg(shTable[i].sh_offset); + if (elfFile.fail()) return -1; + + if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1; + + data = dataTmp; + return 0; + } + return -2; +} + +static bool symCompare(Elf64_Sym a, Elf64_Sym b) { + return (a.st_value < b.st_value); +} + +static int readSymTab(ifstream& elfFile, int sort, vector& data) { + int ret, numElems; + Elf64_Sym* buf; + vector secData; + + ret = readSectionByType(elfFile, SHT_SYMTAB, secData); + if (ret) return ret; + + buf = (Elf64_Sym*)secData.data(); + numElems = (secData.size() / sizeof(Elf64_Sym)); + data.assign(buf, buf + numElems); + + if (sort) std::sort(data.begin(), data.end(), symCompare); + return 0; +} + +static enum bpf_prog_type getSectionType(string& name) { + for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++) + if (StartsWith(name, sectionNameTypes[i].name)) return sectionNameTypes[i].type; + + return BPF_PROG_TYPE_UNSPEC; +} + +/* If ever needed +static string getSectionName(enum bpf_prog_type type) +{ + for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++) + if (sectionNameTypes[i].type == type) + return std::string(sectionNameTypes[i].name); + + return NULL; +} +*/ + +static bool isRelSection(codeSection& cs, string& name) { + for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++) { + sectionType st = sectionNameTypes[i]; + + if (st.type != cs.type) continue; + + if (StartsWith(name, std::string(".rel") + st.name + "/")) + return true; + else + return false; + } + return false; +} + +/* Read a section by its index - for ex to get sec hdr strtab blob */ +static int readCodeSections(ifstream& elfFile, vector& cs) { + vector shTable; + int entries, ret = 0; + + ret = readSectionHeadersAll(elfFile, shTable); + if (ret) return ret; + entries = shTable.size(); + + for (int i = 0; i < entries; i++) { + string name; + codeSection cs_temp; + cs_temp.type = BPF_PROG_TYPE_UNSPEC; + + ret = getSymName(elfFile, shTable[i].sh_name, name); + if (ret) return ret; + + enum bpf_prog_type ptype = getSectionType(name); + if (ptype != BPF_PROG_TYPE_UNSPEC) { + deslash(name); + cs_temp.type = ptype; + cs_temp.name = name; + + ret = readSectionByIdx(elfFile, i, cs_temp.data); + if (ret) return ret; + ALOGD("Loaded code section %d (%s)\n", i, name.c_str()); + } + + /* Check for rel section */ + if (cs_temp.data.size() > 0 && i < entries) { + ret = getSymName(elfFile, shTable[i + 1].sh_name, name); + if (ret) return ret; + + if (isRelSection(cs_temp, name)) { + ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data); + if (ret) return ret; + ALOGD("Loaded relo section %d (%s)\n", i, name.c_str()); + } + } + + if (cs_temp.data.size() > 0) { + cs.push_back(cs_temp); + ALOGD("Adding section %d to cs list\n", i); + } + } + return 0; +} + +static int getSymNameByIdx(ifstream& elfFile, int index, string& name) { + vector symtab; + int ret = 0; + + ret = readSymTab(elfFile, 0 /* !sort */, symtab); + if (ret) return ret; + + if (index >= (int)symtab.size()) return -1; + + return getSymName(elfFile, symtab[index].st_name, name); +} + +static int getMapNames(ifstream& elfFile, vector& names) { + int ret; + string mapName; + vector symtab; + vector shTable; + + ret = readSymTab(elfFile, 1 /* sort */, symtab); + if (ret) return ret; + + /* Get index of maps section */ + ret = readSectionHeadersAll(elfFile, shTable); + if (ret) return ret; + + int maps_idx = -1; + for (int i = 0; i < (int)shTable.size(); i++) { + ret = getSymName(elfFile, shTable[i].sh_name, mapName); + if (ret) return ret; + + if (!mapName.compare("maps")) { + maps_idx = i; + break; + } + } + + /* No maps found */ + if (maps_idx == -1) { + ALOGE("No maps could be found in elf object\n"); + return -1; + } + + for (int i = 0; i < (int)symtab.size(); i++) { + if (symtab[i].st_shndx == maps_idx) { + string s; + ret = getSymName(elfFile, symtab[i].st_name, s); + if (ret) return ret; + names.push_back(s); + } + } + + return 0; +} + +static int createMaps(const char* elfPath, ifstream& elfFile, vector& mapFds) { + int ret, fd; + vector mdData; + vector md; + vector mapNames; + string fname = pathToFilename(string(elfPath), true); + + ret = readSectionByName("maps", elfFile, mdData); + if (ret) return ret; + md.resize(mdData.size() / sizeof(struct bpf_map_def)); + memcpy(md.data(), mdData.data(), mdData.size()); + + ret = getMapNames(elfFile, mapNames); + if (ret) return ret; + + mapFds.resize(mapNames.size()); + + for (int i = 0; i < (int)mapNames.size(); i++) { + // Format of pin location is /sys/fs/bpf/map__ + string mapPinLoc; + bool reuse = false; + + mapPinLoc = string(BPF_FS_PATH) + "map_" + fname + "_" + string(mapNames[i]); + if (access(mapPinLoc.c_str(), F_OK) == 0) { + fd = bpf_obj_get(mapPinLoc.c_str()); + ALOGD("bpf_create_map reusing map %s, ret: %d\n", mapNames[i].c_str(), fd); + reuse = true; + } else { + fd = bpf_create_map(md[i].type, mapNames[i].c_str(), md[i].key_size, md[i].value_size, + md[i].max_entries, md[i].map_flags); + ALOGD("bpf_create_map name %s, ret: %d\n", mapNames[i].c_str(), fd); + } + + if (fd < 0) return fd; + if (fd == 0) return -EINVAL; + + if (!reuse) { + ret = bpf_obj_pin(fd, mapPinLoc.c_str()); + if (ret < 0) return ret; + } + + mapFds[i] = fd; + } + + return ret; +} + +/* For debugging, dump all instructions */ +static void dumpIns(char* ins, int size) { + for (int row = 0; row < size / 8; row++) { + ALOGE("%d: ", row); + for (int j = 0; j < 8; j++) { + ALOGE("%3x ", ins[(row * 8) + j]); + } + ALOGE("\n"); + } +} + +/* For debugging, dump all code sections from cs list */ +static void dumpAllCs(vector& cs) { + for (int i = 0; i < (int)cs.size(); i++) { + ALOGE("Dumping cs %d, name %s\n", int(i), cs[i].name.c_str()); + dumpIns((char*)cs[i].data.data(), cs[i].data.size()); + ALOGE("-----------\n"); + } +} + +static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) { + int insnIndex; + struct bpf_insn *insn, *insns; + + insns = (struct bpf_insn*)(insnsPtr); + + insnIndex = offset / sizeof(struct bpf_insn); + insn = &insns[insnIndex]; + + ALOGD( + "applying relo to instruction at byte offset: %d, \ + insn offset %d , insn %lx\n", + (int)offset, (int)insnIndex, *(unsigned long*)insn); + + if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) { + ALOGE("Dumping all instructions till ins %d\n", insnIndex); + ALOGE("invalid relo for insn %d: code 0x%x\n", insnIndex, insn->code); + dumpIns((char*)insnsPtr, (insnIndex + 3) * 8); + return; + } + + insn->imm = fd; + insn->src_reg = BPF_PSEUDO_MAP_FD; +} + +static void applyMapRelo(ifstream& elfFile, vector mapFds, vector& cs) { + vector mapNames; + + int ret = getMapNames(elfFile, mapNames); + if (ret) return; + + for (int k = 0; k != (int)cs.size(); k++) { + Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data()); + int n_rel = cs[k].rel_data.size() / sizeof(*rel); + + for (int i = 0; i < n_rel; i++) { + int symIndex = ELF64_R_SYM(rel[i].r_info); + string symName; + + ret = getSymNameByIdx(elfFile, symIndex, symName); + if (ret) return; + + /* Find the map fd and apply relo */ + for (int j = 0; j < (int)mapNames.size(); j++) { + if (!mapNames[j].compare(symName)) { + applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]); + break; + } + } + } + } +} + +static int loadCodeSections(const char* elfPath, vector& cs, string license) { + int ret, fd, kvers; + + if ((kvers = getMachineKvers()) < 0) return -1; + + string fname = pathToFilename(string(elfPath), true); + + for (int i = 0; i < (int)cs.size(); i++) { + string progPinLoc; + bool reuse = false; + + // Format of pin location is + // /sys/fs/bpf/prog__ + progPinLoc = string(BPF_FS_PATH) + "prog_" + fname + "_" + cs[i].name; + if (access(progPinLoc.c_str(), F_OK) == 0) { + fd = bpf_obj_get(progPinLoc.c_str()); + ALOGD("New bpf prog load reusing prog %s, ret: %d\n", cs[i].name.c_str(), fd); + reuse = true; + } else { + vector log_buf(BPF_LOAD_LOG_SZ, 0); + + fd = bpf_prog_load(cs[i].type, cs[i].name.c_str(), (struct bpf_insn*)cs[i].data.data(), + cs[i].data.size(), license.c_str(), kvers, 0, + log_buf.data(), log_buf.size()); + ALOGD("New bpf core prog_load for %s (%s) returned: %d\n", elfPath, cs[i].name.c_str(), + fd); + + if (fd <= 0) + ALOGE("bpf_prog_load: log_buf contents: %s\n", (char *)log_buf.data()); + } + + if (fd < 0) return fd; + if (fd == 0) return -EINVAL; + + if (!reuse) { + ret = bpf_obj_pin(fd, progPinLoc.c_str()); + if (ret < 0) return ret; + } + + cs[i].prog_fd = fd; + } + + return 0; +} + +int loadProg(const char* elfPath) { + vector license; + vector cs; + vector mapFds; + int ret; + + ifstream elfFile(elfPath, ios::in | ios::binary); + if (!elfFile.is_open()) return -1; + + ret = readSectionByName("license", elfFile, license); + if (ret) { + ALOGE("Couldn't find license in %s\n", elfPath); + return ret; + } else { + ALOGD("Loading ELF object %s with license %s\n", elfPath, (char*)license.data()); + } + + ret = readCodeSections(elfFile, cs); + if (ret) { + ALOGE("Couldn't read all code sections in %s\n", elfPath); + return ret; + } + + /* Just for future debugging */ + if (0) dumpAllCs(cs); + + ret = createMaps(elfPath, elfFile, mapFds); + if (ret) { + ALOGE("Failed to create maps: (ret=%d) in %s\n", ret, elfPath); + return ret; + } + + for (int i = 0; i < (int)mapFds.size(); i++) + ALOGD("map_fd found at %d is %d in %s\n", i, mapFds[i], elfPath); + + applyMapRelo(elfFile, mapFds, cs); + + ret = loadCodeSections(elfPath, cs, string(license.data())); + if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d\n", ret); + + return ret; +} + +} // namespace bpf +} // namespace android diff --git a/libbpf_android/LoaderUtils.h b/libbpf_android/LoaderUtils.h new file mode 100644 index 0000000..f4912e5 --- /dev/null +++ b/libbpf_android/LoaderUtils.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#include +#include +#include + +#include + +using android::base::Split; +using std::string; +using std::vector; + +static string pathToFilename(string path, bool noext = false) { + vector spath = android::base::Split(path, "/"); + string ret = spath.back(); + + if (noext) { + size_t lastindex = ret.find_last_of("."); + return ret.substr(0, lastindex); + } + return ret; +} + +static int getMachineKvers(void) { + struct utsname un; + char* unameOut; + int nums[3]; // maj, min, sub + + if (uname(&un)) return -1; + unameOut = un.release; + + string s = unameOut; + string token; + size_t pos = 0; + int cur_num = 0; + + while ((pos = s.find(".")) != string::npos && cur_num < 3) { + token = s.substr(0, pos); + s.erase(0, pos + 1); + + if ((pos = token.find("-")) != string::npos) token = token.substr(0, pos); + + nums[cur_num++] = stoi(token); + } + + if ((pos = s.find("-")) != string::npos) + token = s.substr(0, pos); + else + token = s; + + if (token.length() > 0 && cur_num < 3) { + nums[cur_num++] = stoi(token); + } + + if (cur_num != 3) + return -1; + else + return (65536 * nums[0] + 256 * nums[1] + nums[2]); +} + +static void deslash(string& s) { + std::replace(s.begin(), s.end(), '/', '_'); +} diff --git a/libbpf_android/include/libbpf_android.h b/libbpf_android/include/libbpf_android.h new file mode 100644 index 0000000..cea707c --- /dev/null +++ b/libbpf_android/include/libbpf_android.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Android BPF library - public API + * + * 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. + */ + +#ifndef LIBBPF_SYSTEM_H +#define LIBBPF_SYSTEM_H + +#include +#include + +namespace android { +namespace bpf { +// BPF loader implementation. Loads an eBPF ELF object +int loadProg(const char* elfpath); +} // namespace bpf +} // namespace android + +#endif