android_system_bpf/libbpf_android/Loader.cpp
Maciej Żenczykowski 681f604b2e bpf loader - finish support for kernel version conditional loading
bpf program section names must be unique to prevent programs from
appending to each other, so instead the bpf loader will strip
everything past the final $ symbol when actually pinning
the program into the filesystem.

While at it add a little bit more logging.

Example on aosp cuttlefish 5.4.30 virtual device:
  D LibBpfLoader: bpf_prog_load lib call for /system/etc/bpf/offload.o (schedcls_ingress_tether_ether) returned fd: 8 (no error)
  D LibBpfLoader: cs[1].name:schedcls_ingress_tether_rawip$stub min_kver:0 .max_kver:40e00 (kvers:5041e)
  D LibBpfLoader: cs[2].name:schedcls_ingress_tether_rawip$4_14 min_kver:40e00 .max_kver:ffffffff (kvers:5041e)
  D LibBpfLoader: bpf_prog_load lib call for /system/etc/bpf/offload.o (schedcls_ingress_tether_rawip$4_14) returned fd: 9 (no error)
  I bpfloader: Attempted load object: /system/etc/bpf/offload.o, ret: Success

  $ adb shell ls -l /sys/fs/bpf | egrep offload
  -rw-rw---- 1 root network_stack 0 2020-04-22 01:27 map_offload_tether_ingress_map
  -rw-rw---- 1 root network_stack 0 2020-04-22 01:27 map_offload_tether_stats_map
  -r--r----- 1 root root          0 2020-04-22 01:27 prog_offload_schedcls_ingress_tether_ether
  -r--r----- 1 root root          0 2020-04-22 01:27 prog_offload_schedcls_ingress_tether_rawip

Test: builds, atest, proper program loaded on 5.4.30 aosp cuttlefish
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: Id3fcb8e2a6b0087f704e77726e9961efc6145739
2020-04-21 18:29:58 -07:00

656 lines
19 KiB
C++

/*
* 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 <errno.h>
#include <linux/bpf.h>
#include <linux/elf.h>
#include <log/log.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "../progs/include/bpf_map_def.h"
#include "LoaderUtils.h"
#include "bpf/BpfUtils.h"
#include "include/libbpf_android.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#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 android::base::unique_fd;
using std::ifstream;
using std::ios;
using std::optional;
using std::string;
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(<prefix>/<name-of-program>)
* 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},
{"schedcls", BPF_PROG_TYPE_SCHED_CLS},
{"cgroupsock", BPF_PROG_TYPE_CGROUP_SOCK},
/* End of table */
{"END", BPF_PROG_TYPE_UNSPEC},
};
typedef struct {
enum bpf_prog_type type;
string name;
vector<char> data;
vector<char> rel_data;
optional<struct bpf_prog_def> prog_def;
unique_fd prog_fd; /* fd after loading */
} codeSection;
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<Elf64_Shdr>& 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<char>& sec) {
vector<Elf64_Shdr> 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<char>& 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<char> 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<char>& data) {
vector<char> secStrTab;
vector<Elf64_Shdr> 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<char> 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<char>& data) {
int ret;
vector<Elf64_Shdr> 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<char> 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<Elf64_Sym>& data) {
int ret, numElems;
Elf64_Sym* buf;
vector<char> 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;
}
static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd) {
vector<char> pdData;
int ret = readSectionByName("progs", elfFile, pdData);
if (ret == -2) return 0;
if (ret) return ret;
pd.resize(pdData.size() / sizeof(struct bpf_prog_def));
memcpy(pd.data(), pdData.data(), pdData.size());
return 0;
}
static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names) {
int ret;
string name;
vector<Elf64_Sym> symtab;
vector<Elf64_Shdr> shTable;
ret = readSymTab(elfFile, 1 /* sort */, symtab);
if (ret) return ret;
/* Get index of section */
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
int sec_idx = -1;
for (int i = 0; i < (int)shTable.size(); i++) {
ret = getSymName(elfFile, shTable[i].sh_name, name);
if (ret) return ret;
if (!name.compare(sectionName)) {
sec_idx = i;
break;
}
}
/* No section found with matching name*/
if (sec_idx == -1) {
ALOGE("No %s section could be found in elf object\n", sectionName.c_str());
return -1;
}
for (int i = 0; i < (int)symtab.size(); i++) {
if (symtab[i].st_shndx == sec_idx) {
string s;
ret = getSymName(elfFile, symtab[i].st_name, s);
if (ret) return ret;
names.push_back(s);
}
}
return 0;
}
/* Read a section by its index - for ex to get sec hdr strtab blob */
static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
vector<Elf64_Shdr> shTable;
int entries, ret = 0;
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
entries = shTable.size();
vector<struct bpf_prog_def> pd;
ret = readProgDefs(elfFile, pd);
if (ret) return ret;
vector<string> progDefNames;
ret = getSectionSymNames(elfFile, "progs", progDefNames);
if (!pd.empty() && ret) return ret;
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) {
string oldName = name;
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());
vector<string> csSymNames;
ret = getSectionSymNames(elfFile, oldName, csSymNames);
if (ret || !csSymNames.size()) return ret;
for (size_t i = 0; i < progDefNames.size(); ++i) {
if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
cs_temp.prog_def = pd[i];
break;
}
}
}
/* 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(std::move(cs_temp));
ALOGD("Adding section %d to cs list\n", i);
}
}
return 0;
}
static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
vector<Elf64_Sym> 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 createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds) {
int ret;
vector<char> mdData;
vector<struct bpf_map_def> md;
vector<string> mapNames;
string fname = pathToFilename(string(elfPath), true);
ret = readSectionByName("maps", elfFile, mdData);
if (ret == -2) return 0; // no maps to read
if (ret) return ret;
md.resize(mdData.size() / sizeof(struct bpf_map_def));
memcpy(md.data(), mdData.data(), mdData.size());
ret = getSectionSymNames(elfFile, "maps", mapNames);
if (ret) return ret;
for (int i = 0; i < (int)mapNames.size(); i++) {
unique_fd fd;
// Format of pin location is /sys/fs/bpf/map_<filename>_<mapname>
string mapPinLoc;
bool reuse = false;
mapPinLoc = string(BPF_FS_PATH) + "map_" + fname + "_" + string(mapNames[i]);
if (access(mapPinLoc.c_str(), F_OK) == 0) {
fd.reset(bpf_obj_get(mapPinLoc.c_str()));
ALOGD("bpf_create_map reusing map %s, ret: %d\n", mapNames[i].c_str(), fd.get());
reuse = true;
} else {
fd.reset(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.get());
}
if (fd < 0) return fd;
if (fd == 0) return -EINVAL;
if (!reuse) {
ret = bpf_obj_pin(fd, mapPinLoc.c_str());
if (ret) return -errno;
ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
if (ret) return -errno;
ret = chmod(mapPinLoc.c_str(), md[i].mode);
if (ret) return -errno;
}
mapFds.push_back(std::move(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<codeSection>& 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<unique_fd> &mapFds, vector<codeSection>& cs) {
vector<string> mapNames;
int ret = getSectionSymNames(elfFile, "maps", 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<codeSection>& cs, const string& license) {
unsigned kvers = kernelVersion();
int ret, fd;
if (!kvers) return -1;
string fname = pathToFilename(string(elfPath), true);
for (int i = 0; i < (int)cs.size(); i++) {
string name = cs[i].name;
if (cs[i].prog_def.has_value()) {
unsigned min_kver = cs[i].prog_def->min_kver;
unsigned max_kver = cs[i].prog_def->max_kver;
ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)\n", i, name.c_str(), min_kver,
max_kver, kvers);
if (kvers < min_kver) continue;
if (kvers >= max_kver) continue;
}
// strip any potential $foo suffix
// this can be used to provide duplicate programs
// conditionally loaded based on running kernel version
name = name.substr(0, name.find_last_of("$"));
bool reuse = false;
// Format of pin location is
// /sys/fs/bpf/prog_<filename>_<mapname>
string progPinLoc = string(BPF_FS_PATH) + "prog_" + fname + "_" + 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", progPinLoc.c_str(), fd);
reuse = true;
} else {
vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
fd = bpf_prog_load(cs[i].type, 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("bpf_prog_load lib call for %s (%s) returned fd: %d (%s)\n", elfPath,
cs[i].name.c_str(), fd, (fd < 0 ? std::strerror(errno) : "no error"));
if (fd < 0) {
std::vector<std::string> lines = android::base::Split(log_buf.data(), "\n");
ALOGE("bpf_prog_load - BEGIN log_buf contents:");
for (const auto& line : lines) ALOGE("%s", line.c_str());
ALOGE("bpf_prog_load - END log_buf contents.");
}
}
if (fd < 0) return fd;
if (fd == 0) return -EINVAL;
if (!reuse) {
ret = bpf_obj_pin(fd, progPinLoc.c_str());
if (ret) return -errno;
if (cs[i].prog_def.has_value()) {
if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
(gid_t)cs[i].prog_def->gid)) {
return -errno;
}
}
if (chmod(progPinLoc.c_str(), 0440)) return -errno;
}
cs[i].prog_fd.reset(fd);
}
return 0;
}
int loadProg(const char* elfPath) {
vector<char> license;
vector<codeSection> cs;
vector<unique_fd> 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].get(), 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;
}
void waitForProgsLoaded() {
while (!android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(5))) {
ALOGW("Waited 5s for bpf.progs_loaded, still waiting...");
}
}
} // namespace bpf
} // namespace android