android_packages_modules_Co.../bpf/loader/NetBpfLoad.cpp

2123 lines
76 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2018-2024 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 "NetBpfLoad"
#include <algorithm>
#include <arpa/inet.h>
#include <bpf/btf.h>
#include <bpf/libbpf.h>
#include <dirent.h>
#include <elf.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <fstream>
#include <inttypes.h>
#include <iostream>
#include <linux/unistd.h>
#include <log/log.h>
#include <net/if.h>
#include <optional>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include <android-base/cmsg.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <android/api-level.h>
NetBpfLoad: workaround for Xiaomi kernel uapi bug See https://b.corp.google.com/issues/414570528#comment96 which can be summarized as https://github.com/MiCode/Xiaomi_Kernel_OpenSource/blob/vayu-r-oss/include/uapi/linux/bpf.h has: enum bpf_cmd { BPF_MAP_CREATE, <-- UAPI 0 BPF_MAP_LOOKUP_ELEM, <-- UAPI 1 BPF_MAP_UPDATE_ELEM, <-- UAPI 2 BPF_MAP_DELETE_ELEM, <-- UAPI 3 BPF_MAP_GET_NEXT_KEY, <-- UAPI 4 BPF_PROG_LOAD, <-- UAPI 5 BPF_OBJ_PIN, <-- UAPI 6 BPF_OBJ_GET, <-- UAPI 7 BPF_PROG_ATTACH, <-- UAPI 8 BPF_PROG_DETACH, <-- UAPI 9 BPF_GET_COMM_HASH, <-- breaks Linux uapi (uses 10) BPF_PROG_TEST_RUN, <-- UAPI 10 becomes 11 (aka. BPF_PROG_RUN) BPF_PROG_GET_NEXT_ID, <-- UAPI 11 becomes 12 BPF_MAP_GET_NEXT_ID, <-- UAPI 12 becomes 13 BPF_PROG_GET_FD_BY_ID, <-- UAPI 13 becomes 14 BPF_MAP_GET_FD_BY_ID, <-- UAPI 14 becomes 15 BPF_OBJ_GET_INFO_BY_FD, <-- UAPI 15 becomes 16 }; this addition in the middle of the userspace api enum, causes all following enum values to be greater by one. In particular this increases the value of BPF_OBJ_GET_INFO_BY_FD from 15 to 16, thus break it. It also causes UAPI 12 (BPF_MAP_GET_NEXT_ID) to actually execute BPF_PROG_GET_NEXT_ID (normally UAPI 13) As such this can be detected via finding the index of the last map (on buggy kernels this will actually find the index of the last program), then creating a map, then checking if the last map index has changed (on buggy kernels this will not be the case because we did not create another program). Bug: 414570528 Test: TreeHugger Flag: EXEMPT mainline Change-Id: Iecad121ac624df2d123a13e90a46792bd567a4c6 Signed-off-by: Maciej Żenczykowski <maze@google.com>
2025-06-10 01:56:48 -07:00
#define BPF_SUPPORT_CMD_FIXUP
#include "BpfSyscallWrappers.h"
#include "bpf/BpfUtils.h"
#include "bpf_map_def.h"
// The following matches bpf_helpers.h, which is only for inclusion in bpf code
#define BPFLOADER_MAINLINE_S_VERSION 42u
#define BPFLOADER_MAINLINE_25Q2_VERSION 47u
using android::base::borrowed_fd;
using android::base::EndsWith;
using android::base::GetIntProperty;
using android::base::GetProperty;
using android::base::InitLogging;
using android::base::KernelLogger;
using android::base::SetProperty;
using android::base::Split;
using android::base::StartsWith;
using android::base::Tokenize;
using android::base::unique_fd;
using std::ifstream;
using std::ios;
using std::optional;
using std::string;
using std::vector;
namespace android {
namespace bpf {
// Returns the build type string (from ro.build.type).
const std::string& getBuildType() {
static std::string t = GetProperty("ro.build.type", "unknown");
return t;
}
// The following functions classify the 3 Android build types.
inline bool isEng() {
return getBuildType() == "eng";
}
inline bool isUser() {
return getBuildType() == "user";
}
inline bool isUserdebug() {
return getBuildType() == "userdebug";
}
#define BPF_FS_PATH "/sys/fs/bpf/"
static unsigned int page_size = static_cast<unsigned int>(getpagesize());
static string pathToObjName(const string& path) {
// extract everything after the final slash, ie. this is the filename 'foo.o'
string filename = Split(path, "/").back();
// strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo'
return filename.substr(0, filename.find_last_of('.'));
}
typedef struct {
const char* name;
enum bpf_prog_type type;
enum bpf_attach_type attach_type;
} sectionType;
/*
* Map section name prefixes to program types, the section name will be:
* SECTION(<prefix>/<name-of-program>)
* For example:
* SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
* is the name of the program, and tracepoint is the type.
*
* However, be aware that you should not be directly using the SECTION() macro.
* Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE macros.
*
* Programs shipped inside the tethering apex should be limited to networking stuff,
* as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
* since they are less stable abi/api and may conflict with platform uses of bpf.
*/
sectionType sectionNameTypes[] = {
{"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
{"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
{"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB},
{"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK},
{"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
{"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
{"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
{"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
{"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
{"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
{"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
{"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
{"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
{"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
{"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
{"schedact/", BPF_PROG_TYPE_SCHED_ACT},
{"schedcls/", BPF_PROG_TYPE_SCHED_CLS},
{"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
{"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
{"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
{"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER},
{"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
{"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
{"xdp/", BPF_PROG_TYPE_XDP},
};
typedef struct {
enum bpf_prog_type type;
enum bpf_attach_type attach_type;
string name; // The canonicalized section name.
string program_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 ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
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 = 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
template <typename T>
static int readSectionByName(const char* name, ifstream& elfFile, vector<T>& 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)) {
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
if (shTable[i].sh_size % sizeof(T)) return -1;
data.resize(shTable[i].sh_size / sizeof(T));
if (!elfFile.read(reinterpret_cast<char*>(data.data()), shTable[i].sh_size))
return -1;
return 0;
}
}
return -2;
}
unsigned int readSectionUint(const char* name, ifstream& elfFile) {
vector<char> theBytes;
int ret = readSectionByName(name, elfFile, theBytes);
if (ret) {
ALOGE("Couldn't find section %s.", name);
abort();
} else if (theBytes.size() < sizeof(unsigned int)) {
ALOGE("Section %s is too short.", name);
abort();
} else {
// decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
unsigned int value = static_cast<unsigned char>(theBytes[3]);
value <<= 8;
value += static_cast<unsigned char>(theBytes[2]);
value <<= 8;
value += static_cast<unsigned char>(theBytes[1]);
value <<= 8;
value += static_cast<unsigned char>(theBytes[0]);
ALOGD("Section %s value is %u [0x%x]", name, value, value);
return value;
}
}
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;
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
data.resize(shTable[i].sh_size);
if (!elfFile.read(data.data(), shTable[i].sh_size)) return -1;
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 (auto& snt : sectionNameTypes)
if (StartsWith(name, snt.name)) return snt.type;
return BPF_PROG_TYPE_UNSPEC;
}
static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
optional<unsigned> symbolType = std::nullopt) {
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) {
ALOGW("No %s section could be found in elf object", sectionName.c_str());
return -1;
}
for (int i = 0; i < (int)symtab.size(); i++) {
if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
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 = readSectionByName("progs", 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) continue;
// This must be done before '/' is replaced with '_'.
for (auto& snt : sectionNameTypes)
if (StartsWith(name, snt.name)) cs_temp.attach_type = snt.attach_type;
string oldName = name;
// convert all slashes to underscores
std::replace(name.begin(), name.end(), '/', '_');
cs_temp.type = ptype;
cs_temp.name = name;
ret = readSectionByIdx(elfFile, i, cs_temp.data);
if (ret) return ret;
ALOGV("Loaded code section %d (%s)", i, name.c_str());
vector<string> csSymNames;
ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
if (ret || !csSymNames.size()) return ret;
cs_temp.program_name = csSymNames[0];
for (size_t j = 0; j < progDefNames.size(); ++j) {
if (!progDefNames[j].compare(csSymNames[0] + "_def")) {
cs_temp.prog_def = pd[j];
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 (name == (".rel" + oldName)) {
ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
if (ret) return ret;
ALOGV("Loaded relo section %d (%s)", i, name.c_str());
}
}
if (cs_temp.data.size() > 0) {
cs.push_back(std::move(cs_temp));
ALOGV("Adding section %d to cs list", 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 bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
// bpfGetFd... family of functions require at minimum a 4.14 kernel,
// so on 4.9-T kernels just pretend the map matches our expectations.
// Additionally we'll get almost equivalent test coverage on newer devices/kernels.
// This is because the primary failure mode we're trying to detect here
// is either a source code misconfiguration (which is likely kernel independent)
// or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
if (!isAtLeastKernelVersion(4, 14, 0)) return true;
// Assuming fd is a valid Bpf Map file descriptor then
// all the following should always succeed on a 4.14+ kernel.
// If they somehow do fail, they'll return -1 (and set errno),
// which should then cause (among others) a key_size mismatch.
int fd_type = bpfGetFdMapType(fd);
int fd_key_size = bpfGetFdKeySize(fd);
int fd_value_size = bpfGetFdValueSize(fd);
int fd_max_entries = bpfGetFdMaxEntries(fd);
int fd_map_flags = bpfGetFdMapFlags(fd);
// DEVMAPs are readonly from the bpf program side's point of view, as such
// the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
int desired_map_flags = (int)mapDef.map_flags;
if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
desired_map_flags |= BPF_F_RDONLY_PROG;
// The .h file enforces that this is a power of two, and page size will
// also always be a power of two, so this logic is actually enough to
// force it to be a multiple of the page size, as required by the kernel.
unsigned int desired_max_entries = mapDef.max_entries;
if (type == BPF_MAP_TYPE_RINGBUF) {
if (desired_max_entries < page_size) desired_max_entries = page_size;
}
// The following checks should *never* trigger, if one of them somehow does,
// it probably means a bpf .o file has been changed/replaced at runtime
// and bpfloader was manually rerun (normally it should only run *once*
// early during the boot process).
// Another possibility is that something is misconfigured in the code:
// most likely a shared map is declared twice differently.
// But such a change should never be checked into the source tree...
if ((fd_type == type) &&
(fd_key_size == (int)mapDef.key_size) &&
(fd_value_size == (int)mapDef.value_size) &&
(fd_max_entries == (int)desired_max_entries) &&
(fd_map_flags == desired_map_flags)) {
return true;
}
ALOGE("bpf map name %s mismatch: desired/found (errno: %d): "
"type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
mapName.c_str(), errno, type, fd_type, mapDef.key_size, fd_key_size,
mapDef.value_size, fd_value_size, mapDef.max_entries, fd_max_entries,
desired_map_flags, fd_map_flags);
return false;
}
static int setBtfDatasecSize(ifstream &elfFile, struct btf *btf,
struct btf_type *bt) {
const char *name = btf__name_by_offset(btf, bt->name_off);
if (!name) {
ALOGE("Couldn't resolve section name, errno: %d", errno);
return -errno;
}
vector<char> data;
int ret = readSectionByName(name, elfFile, data);
if (ret) {
ALOGE("Couldn't read section %s, ret: %d", name, ret);
return ret;
}
bt->size = data.size();
return 0;
}
static int getSymOffsetByName(ifstream &elfFile, const char *name, int *off) {
vector<Elf64_Sym> symtab;
int ret = readSymTab(elfFile, 1 /* sort */, symtab);
if (ret) return ret;
for (int i = 0; i < (int)symtab.size(); i++) {
string s;
ret = getSymName(elfFile, symtab[i].st_name, s);
if (ret) continue;
if (!strcmp(s.c_str(), name)) {
*off = symtab[i].st_value;
return 0;
}
}
return -1;
}
static int setBtfVarOffset(ifstream &elfFile, struct btf *btf,
struct btf_type *datasecBt) {
int i, vars = btf_vlen(datasecBt);
struct btf_var_secinfo *vsi;
const char *datasecName = btf__name_by_offset(btf, datasecBt->name_off);
if (!datasecName) {
ALOGE("Couldn't resolve section name, errno: %d", errno);
return -errno;
}
for (i = 0, vsi = btf_var_secinfos(datasecBt); i < vars; i++, vsi++) {
const struct btf_type *varBt = btf__type_by_id(btf, vsi->type);
if (!varBt || !btf_is_var(varBt)) {
ALOGE("Found non VAR kind btf_type, section: %s id: %d", datasecName,
vsi->type);
return -1;
}
const struct btf_var *var = btf_var(varBt);
if (var->linkage == BTF_VAR_STATIC) continue;
const char *varName = btf__name_by_offset(btf, varBt->name_off);
if (!varName) {
ALOGE("Failed to resolve var name, section: %s", datasecName);
return -1;
}
int off;
int ret = getSymOffsetByName(elfFile, varName, &off);
if (ret) {
ALOGE("No offset found in symbol table, section: %s, var: %s, ret: %d",
datasecName, varName, ret);
return ret;
}
vsi->offset = off;
}
return 0;
}
#define BTF_INFO_ENC(kind, kind_flag, vlen) \
((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
#define BTF_INT_ENC(encoding, bits_offset, nr_bits) \
((encoding) << 24 | (bits_offset) << 16 | (nr_bits))
static int sanitizeBtf(struct btf *btf) {
for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
// Replace BTF_KIND_VAR (5.2+) with BTF_KIND_INT (4.18+)
if (btf_is_var(bt)) {
bt->info = BTF_INFO_ENC(BTF_KIND_INT, 0, 0);
// using size = 1 is the safest choice, 4 will be too
// big and cause kernel BTF validation failure if
// original variable took less than 4 bytes
bt->size = 1;
*(int *)(bt + 1) = BTF_INT_ENC(0, 0, 8);
continue;
}
// Replace BTF_KIND_FUNC_PROTO (5.0+) with BTF_KIND_ENUM (4.18+)
if (btf_is_func_proto(bt)) {
int vlen = btf_vlen(bt);
bt->info = BTF_INFO_ENC(BTF_KIND_ENUM, 0, vlen);
bt->size = sizeof(__u32); // kernel enforced
continue;
}
// Replace BTF_KIND_FUNC (5.0+) with BTF_KIND_TYPEDEF (4.18+)
if (btf_is_func(bt)) {
bt->info = BTF_INFO_ENC(BTF_KIND_TYPEDEF, 0, 0);
continue;
}
// Replace BTF_KIND_DATASEC (5.2+) with BTF_KIND_STRUCT (4.18+)
if (btf_is_datasec(bt)) {
const struct btf_var_secinfo *v = btf_var_secinfos(bt);
struct btf_member *m = btf_members(bt);
char *name;
name = (char *)btf__name_by_offset(btf, bt->name_off);
while (*name) {
if (*name == '.' || *name == '?') *name = '_';
name++;
}
int vlen = btf_vlen(bt);
bt->info = BTF_INFO_ENC(BTF_KIND_STRUCT, 0, vlen);
for (int j = 0; j < vlen; j++, v++, m++) {
// order of field assignments is important
m->offset = v->offset * 8;
m->type = v->type;
// preserve variable name as member name
const struct btf_type *vt = btf__type_by_id(btf, v->type);
m->name_off = vt->name_off;
}
}
}
return 0;
}
static int loadBtf(ifstream &elfFile, struct btf *btf) {
int ret;
for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
if (!btf_is_datasec(bt)) continue;
ret = setBtfDatasecSize(elfFile, btf, bt);
if (ret) return ret;
ret = setBtfVarOffset(elfFile, btf, bt);
if (ret) return ret;
}
if (!isAtLeastKernelVersion(5, 10, 0)) {
// Likely unnecessary on kernel 5.4 but untested.
sanitizeBtf(btf);
}
ret = btf__load_into_kernel(btf);
if (ret) {
if (errno != EINVAL) {
ALOGE("btf__load_into_kernel failed, errno: %d", errno);
return ret;
};
// For BTF_KIND_FUNC, newer kernels can read the BTF_INFO_VLEN bits of
// struct btf_type to distinguish static vs. global vs. extern
// functions, but older kernels enforce that only the BTF_INFO_KIND bits
// can be set. Retry with non-BTF_INFO_KIND bits zeroed out to handle
// this case.
for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
if (btf_is_func(bt)) {
bt->info = (BTF_INFO_KIND(bt->info)) << 24;
}
}
ret = btf__load_into_kernel(btf);
if (ret) {
ALOGE("btf__load_into_kernel retry failed, errno: %d", errno);
return ret;
};
}
return 0;
}
int getKeyValueTids(const struct btf *btf, const char *mapName,
uint32_t expectedKeySize, uint32_t expectedValueSize,
uint32_t *keyTypeId, uint32_t *valueTypeId) {
const struct btf_type *kvBt;
const struct btf_member *key, *value;
const size_t max_name = 256;
char kvTypeName[max_name];
int64_t keySize, valueSize;
int32_t kvId;
if (snprintf(kvTypeName, max_name, "____btf_map_%s", mapName) == max_name) {
ALOGE("____btf_map_%s is too long", mapName);
return -1;
}
kvId = btf__find_by_name(btf, kvTypeName);
if (kvId < 0) {
ALOGE("section not found, map: %s typeName: %s", mapName, kvTypeName);
return -1;
}
kvBt = btf__type_by_id(btf, kvId);
if (!kvBt) {
ALOGE("Couldn't find BTF type, map: %s id: %u", mapName, kvId);
return -1;
}
if (!btf_is_struct(kvBt) || btf_vlen(kvBt) < 2) {
ALOGE("Non Struct kind or invalid vlen, map: %s id: %u", mapName, kvId);
return -1;
}
key = btf_members(kvBt);
value = key + 1;
keySize = btf__resolve_size(btf, key->type);
if (keySize < 0) {
ALOGE("Couldn't get key size, map: %s errno: %d", mapName, errno);
return -1;
}
valueSize = btf__resolve_size(btf, value->type);
if (valueSize < 0) {
ALOGE("Couldn't get value size, map: %s errno: %d", mapName, errno);
return -1;
}
if (expectedKeySize != keySize || expectedValueSize != valueSize) {
ALOGE("Key value size mismatch, map: %s key size: %d expected key size: "
"%d value size: %d expected value size: %d",
mapName, (uint32_t)keySize, expectedKeySize, (uint32_t)valueSize,
expectedValueSize);
return -1;
}
*keyTypeId = key->type;
*valueTypeId = value->type;
return 0;
}
static bool isBtfSupported(enum bpf_map_type type) {
return type != BPF_MAP_TYPE_DEVMAP_HASH && type != BPF_MAP_TYPE_RINGBUF;
}
static int pinMap(const borrowed_fd& fd, const struct bpf_map_def& mapDef, const string& mapPinLoc) {
int ret;
if (mapDef.create_location[0]) {
ret = bpfFdPin(fd, mapDef.create_location);
if (ret) {
const int err = errno;
ALOGE("create %s -> %d [%d:%s]", mapDef.create_location, ret, err, strerror(err));
return -err;
}
ret = renameat2(AT_FDCWD, mapDef.create_location,
AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
if (ret) {
const int err = errno;
ALOGE("rename %s %s -> %d [%d:%s]", mapDef.create_location, mapPinLoc.c_str(), ret,
err, strerror(err));
return -err;
}
} else {
ret = bpfFdPin(fd, mapPinLoc.c_str());
if (ret) {
const int err = errno;
ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
return -err;
}
}
ret = chmod(mapPinLoc.c_str(), mapDef.mode);
if (ret) {
const int err = errno;
ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), mapDef.mode, ret, err,
strerror(err));
return -err;
}
ret = chown(mapPinLoc.c_str(), (uid_t)mapDef.uid, (gid_t)mapDef.gid);
if (ret) {
const int err = errno;
ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), mapDef.uid, mapDef.gid,
ret, err, strerror(err));
return -err;
}
if (isAtLeastKernelVersion(4, 14, 0)) {
int mapId = bpfGetFdMapId(fd);
if (mapId == -1) {
const int err = errno;
ALOGE("bpfGetFdMapId failed, errno: %d", err);
return -err;
}
ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
}
return 0;
}
static int readMapNames(ifstream& elfFile, vector<string>& mapNames) {
int ret = getSectionSymNames(elfFile, ".android_maps", mapNames);
if (ret) return ret;
const string suffix = "_def";
for (string& name : mapNames) {
if (EndsWith(name, suffix)) {
name.erase(name.length() - suffix.length());
} else {
ALOGE("Failed to get map names, invalid symbol in .android_maps: %s", name.c_str());
return 1;
}
}
return 0;
}
static bool isMapTypeSupported(enum bpf_map_type type) {
if (type == BPF_MAP_TYPE_LPM_TRIE && !isAtLeastKernelVersion(4, 14, 0)) {
// On Linux Kernels older than 4.14 this map type doesn't exist - autoskip.
return false;
}
return true;
}
static enum bpf_map_type sanitizeMapType(enum bpf_map_type type) {
if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
// On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
// of be approximated: ARRAY has the same userspace api, though it is not usable
// by the same ebpf programs. However, that's okay because the bpf_redirect_map()
// helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
// and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
// could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
// Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
return BPF_MAP_TYPE_ARRAY;
}
if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
// On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
// of be approximated: HASH has the same userspace visible api.
// However it cannot be used by ebpf programs in the same way.
// Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
// would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
// One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
// programs as being 5.4+...
return BPF_MAP_TYPE_HASH;
}
// No sanitization is required.
return type;
}
static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
const unsigned int bpfloader_ver) {
int ret;
vector<char> btfData;
vector<struct bpf_map_def> md;
vector<string> mapNames;
string objName = pathToObjName(string(elfPath));
ret = readSectionByName(".android_maps", elfFile, md);
if (ret == -2) return 0; // no maps to read
if (ret) return ret;
ret = readMapNames(elfFile, mapNames);
if (ret) return ret;
struct btf *btf = NULL;
auto btfGuard = base::make_scope_guard([&btf] { if (btf) btf__free(btf); });
if (isAtLeastKernelVersion(4, 19, 0)) {
// On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist.
ret = readSectionByName(".BTF", elfFile, btfData);
if (ret) {
ALOGE("Failed to read .BTF section, ret:%d", ret);
return ret;
}
btf = btf__new(btfData.data(), btfData.size());
if (btf == NULL) {
ALOGE("btf__new failed, errno: %d", errno);
return -errno;
}
ret = loadBtf(elfFile, btf);
if (ret) return ret;
}
unsigned kvers = kernelVersion();
for (int i = 0; i < (int)mapNames.size(); i++) {
if (bpfloader_ver < md[i].bpfloader_min_ver) {
ALOGD("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
md[i].bpfloader_min_ver);
mapFds.push_back(unique_fd());
continue;
}
if (bpfloader_ver >= md[i].bpfloader_max_ver) {
ALOGD("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
md[i].bpfloader_max_ver);
mapFds.push_back(unique_fd());
continue;
}
if (kvers < md[i].min_kver) {
ALOGD("skipping map %s which requires kernel version 0x%x >= 0x%x",
mapNames[i].c_str(), kvers, md[i].min_kver);
mapFds.push_back(unique_fd());
continue;
}
if (kvers >= md[i].max_kver) {
ALOGD("skipping map %s which requires kernel version 0x%x < 0x%x",
mapNames[i].c_str(), kvers, md[i].max_kver);
mapFds.push_back(unique_fd());
continue;
}
if (!isMapTypeSupported(md[i].type)) {
ALOGD("skipping unsupported map type(%d): %s", md[i].type, mapNames[i].c_str());
mapFds.push_back(unique_fd());
continue;
}
enum bpf_map_type type = sanitizeMapType(md[i].type);
// The .h file enforces that this is a power of two, and page size will
// also always be a power of two, so this logic is actually enough to
// force it to be a multiple of the page size, as required by the kernel.
unsigned int max_entries = md[i].max_entries;
if (type == BPF_MAP_TYPE_RINGBUF) {
if (max_entries < page_size) max_entries = page_size;
}
string mapPinLoc = string(md[i].pin_prefix) + mapNames[i];
unique_fd fd;
int saved_errno;
if (access(mapPinLoc.c_str(), F_OK) == 0) {
fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
saved_errno = errno;
ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
abort();
} else {
union bpf_attr req = {
.map_type = type,
.key_size = md[i].key_size,
.value_size = md[i].value_size,
.max_entries = max_entries,
.map_flags = md[i].map_flags,
};
if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
bool haveBtf = btf && isBtfSupported(type);
if (haveBtf) {
uint32_t kTid, vTid;
ret = getKeyValueTids(btf, mapNames[i].c_str(), md[i].key_size,
md[i].value_size, &kTid, &vTid);
if (ret) return ret;
req.btf_fd = btf__fd(btf);
req.btf_key_type_id = kTid;
req.btf_value_type_id = vTid;
}
fd.reset(bpf(BPF_MAP_CREATE, req));
saved_errno = errno;
if (fd.ok()) {
ALOGD("bpf_create_map[%s] btf:%d -> %d",
mapNames[i].c_str(), haveBtf, fd.get());
} else {
ALOGE("bpf_create_map[%s] btf:%d -> %d errno:%d",
mapNames[i].c_str(), haveBtf, fd.get(), saved_errno);
}
}
if (!fd.ok()) return -saved_errno;
// When reusing a pinned map, we need to check the map type/sizes/etc match, but for
// safety (since reuse code path is rare) run these checks even if we just created it.
// We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
ret = pinMap(fd, md[i], mapPinLoc);
if (ret) return ret;
mapFds.push_back(std::move(fd));
}
return ret;
}
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];
// Occasionally might be useful for relocation debugging, but pretty spammy
if (0) {
ALOGV("applying relo to instruction at byte offset: %llu, "
"insn offset %d, insn %llx",
(unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
}
if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
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 = readMapNames(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 pinProg(const borrowed_fd& fd, const struct bpf_prog_def& progDef,
const string& progPinLoc) {
int ret;
if (progDef.create_location[0]) {
ret = bpfFdPin(fd, progDef.create_location);
if (ret) {
const int err = errno;
ALOGE("create %s -> %d [%d:%s]", progDef.create_location, ret, err, strerror(err));
return -err;
}
ret = renameat2(AT_FDCWD, progDef.create_location,
AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
if (ret) {
const int err = errno;
ALOGE("rename %s %s -> %d [%d:%s]", progDef.create_location, progPinLoc.c_str(), ret,
err, strerror(err));
return -err;
}
} else {
ret = bpfFdPin(fd, progPinLoc.c_str());
if (ret) {
const int err = errno;
ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
return -err;
}
}
if (chmod(progPinLoc.c_str(), 0440)) {
const int err = errno;
ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
return -err;
}
if (chown(progPinLoc.c_str(), (uid_t)progDef.uid,
(gid_t)progDef.gid)) {
const int err = errno;
ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), progDef.uid,
progDef.gid, err, strerror(err));
return -err;
}
return 0;
}
static int validateProg(const borrowed_fd& fd, string& progPinLoc,
const unsigned int bpfloader_ver) {
if (!isAtLeastKernelVersion(4, 14, 0)) {
return 0;
}
int progId = bpfGetFdProgId(fd);
if (progId == -1) {
const int err = errno;
ALOGE("bpfGetFdProgId failed, errno: %d", err);
return -err;
}
int jitLen = bpfGetFdJitProgLen(fd);
if (jitLen == -1) {
const int err = errno;
ALOGE("bpfGetFdJitProgLen failed, ret: %d", err);
return -err;
}
int xlatLen = bpfGetFdXlatProgLen(fd);
if (xlatLen == -1) {
const int err = errno;
ALOGE("bpfGetFdXlatProgLen failed, ret: %d", err);
return -err;
}
ALOGI("prog %s id %d len jit:%d xlat:%d", progPinLoc.c_str(), progId, jitLen, xlatLen);
if (!jitLen && bpfloader_ver >= BPFLOADER_MAINLINE_25Q2_VERSION) {
ALOGE("Kernel eBPF JIT failure for %s", progPinLoc.c_str());
return -ENOTSUP;
}
return 0;
}
static string buildProgPinLoc(const char pin_subdir[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE],
const string& objName, const string& name) {
// Format of pin location is /sys/fs/bpf/<prefix>prog_<objName>_<progName>
return string(BPF_FS_PATH) + pin_subdir + "prog_" + objName + '_' + string(name);
}
static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
const unsigned int bpfloader_ver) {
unsigned kvers = kernelVersion();
if (!kvers) {
ALOGE("unable to get kernel version");
return -EINVAL;
}
string objName = pathToObjName(string(elfPath));
for (int i = 0; i < (int)cs.size(); i++) {
unique_fd& fd = cs[i].prog_fd;
int ret;
string name = cs[i].name;
if (!cs[i].prog_def.has_value()) {
ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
return -EINVAL;
}
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)", i, name.c_str(), min_kver,
max_kver, kvers);
if (kvers < min_kver) continue;
if (kvers >= max_kver) continue;
unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
bpfMinVer, bpfMaxVer);
if (bpfloader_ver < bpfMinVer) continue;
if (bpfloader_ver >= bpfMaxVer) 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;
string progPinLoc = buildProgPinLoc(cs[i].prog_def->pin_subdir, objName, name);
if (access(progPinLoc.c_str(), F_OK) == 0) {
fd.reset(retrieveProgram(progPinLoc.c_str()));
ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
!fd.ok() ? std::strerror(errno) : "ok");
reuse = true;
} else {
static char log_buf[1 << 20]; // 1 MiB logging buffer
union bpf_attr req = {
.prog_type = cs[i].type,
.insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
.insns = ptr_to_u64(cs[i].data.data()),
.license = ptr_to_u64(license.c_str()),
.log_level = 1,
.log_size = sizeof(log_buf),
.log_buf = ptr_to_u64(log_buf),
.expected_attach_type = cs[i].attach_type,
};
if (isAtLeastKernelVersion(4, 15, 0))
strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
fd.reset(bpf(BPF_PROG_LOAD, req));
// Kernel should have NULL terminated the log buffer, but force it anyway for safety
log_buf[sizeof(log_buf) - 1] = 0;
// Strip out final newline if present
int log_chars = strlen(log_buf);
if (log_chars && log_buf[log_chars - 1] == '\n') log_buf[--log_chars] = 0;
bool log_oneline = !strchr(log_buf, '\n');
ALOGD("BPF_PROG_LOAD call for %s (%s) returned '%s' fd: %d (%s)", elfPath,
cs[i].name.c_str(), log_oneline ? log_buf : "{multiline}",
fd.get(), !fd.ok() ? std::strerror(errno) : "ok");
if (!fd.ok()) {
// kernel NULL terminates log_buf, so this checks for non-empty string
if (log_buf[0] && !isUser()) {
vector<string> lines = Split(log_buf, "\n");
ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
for (const auto& line : lines) ALOGW("%s", line.c_str());
ALOGW("BPF_PROG_LOAD - END log_buf contents.");
}
if (cs[i].prog_def->optional) {
ALOGW("failed program %s is marked optional - continuing...",
cs[i].name.c_str());
continue;
}
ALOGE("non-optional program %s failed to load.", cs[i].name.c_str());
}
}
if (!fd.ok()) return fd.get();
if (!reuse) {
ret = pinProg(fd, cs[i].prog_def.value(), progPinLoc);
if (ret) return ret;
}
ret = validateProg(fd, progPinLoc, bpfloader_ver);
if (ret) return ret;
}
return 0;
}
static int prepareLoadMaps(const struct bpf_object* obj, const vector<struct bpf_map_def>& md,
const vector<string>& mapNames, const unsigned int bpfloader_ver) {
unsigned kvers = kernelVersion();
for (int i = 0; i < (int)mapNames.size(); i++) {
struct bpf_map* m = bpf_object__find_map_by_name(obj, mapNames[i].c_str());
if (!m) {
ALOGE("bpf_object does not contain map: %s", mapNames[i].c_str());
return -1;
}
if (bpfloader_ver < md[i].bpfloader_min_ver || bpfloader_ver >= md[i].bpfloader_max_ver) {
ALOGD("skipping map %s: bpfloader 0x%05x is outside required range [0x%05x, 0x%05x)",
mapNames[i].c_str(), bpfloader_ver,
md[i].bpfloader_min_ver, md[i].bpfloader_max_ver);
bpf_map__set_autocreate(m, false);
continue;
}
if (kvers < md[i].min_kver || kvers >= md[i].max_kver) {
ALOGD("skipping map %s: kernel version 0x%x is outside required range [0x%x, 0x%x)",
mapNames[i].c_str(), kvers, md[i].min_kver, md[i].max_kver);
bpf_map__set_autocreate(m, false);
continue;
}
if (!isMapTypeSupported(md[i].type)) {
ALOGD("skipping unsupported map type(%d): %s", md[i].type, mapNames[i].c_str());
bpf_map__set_autocreate(m, false);
continue;
}
bpf_map__set_type(m, sanitizeMapType(md[i].type));
bpf_map__set_map_flags(m, md[i].map_flags);
}
return 0;
}
static int prepareLoadProgs(const struct bpf_object* obj, const vector<codeSection>& cs,
const unsigned int bpfloader_ver) {
unsigned kvers = kernelVersion();
for (int i = 0; i < (int)cs.size(); i++) {
string name = cs[i].name;
if (!cs[i].prog_def.has_value()) {
ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
return -EINVAL;
}
string program_name = cs[i].program_name;
struct bpf_program* prog = bpf_object__find_program_by_name(obj, program_name.c_str());
if (!prog) {
ALOGE("bpf_object does not contain program: %s", cs[i].program_name.c_str());
return -1;
}
unsigned min_kver = cs[i].prog_def->min_kver;
unsigned max_kver = cs[i].prog_def->max_kver;
if (kvers < min_kver || kvers >= max_kver) {
ALOGD("skipping prog %s: kernel version 0x%x is outside required range [0x%x, 0x%x)",
name.c_str(), kvers, min_kver, max_kver);
bpf_program__set_autoload(prog, false);
continue;
}
unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
if (bpfloader_ver < bpfMinVer || bpfloader_ver >= bpfMaxVer) {
ALOGD("skipping prog %s: bpfloader 0x%05x is outside required range [0x%05x, 0x%05x)",
name.c_str(), bpfloader_ver, bpfMinVer, bpfMaxVer);
bpf_program__set_autoload(prog, false);
continue;
}
if (cs[i].prog_def->optional) {
// TODO: Support optional program
ALOGE("Optional program cannot be loaded by libbpf");
return -1;
}
bpf_program__set_type(prog, cs[i].type);
bpf_program__set_expected_attach_type(prog, cs[i].attach_type);
}
return 0;
}
static int pinMaps(const char* const elfPath, const struct bpf_object* obj,
const vector<struct bpf_map_def>& md, const vector<string>& mapNames) {
int ret;
string objName = pathToObjName(string(elfPath));
for (int i = 0; i < (int)mapNames.size(); i++) {
struct bpf_map* m = bpf_object__find_map_by_name(obj, mapNames[i].c_str());
if (!m) {
ALOGE("bpf_object does not contain map: %s", mapNames[i].c_str());
return -1;
}
// This map was skipped
if (!bpf_map__autocreate(m)) continue;
string mapPinLoc = string(md[i].pin_prefix) + mapNames[i];
if (access(mapPinLoc.c_str(), F_OK) == 0) {
ALOGE("Reusing map is not supported: %s", mapNames[i].c_str());
return -1;
}
ret = pinMap(bpf_map__fd(m), md[i], mapPinLoc);
if (ret) return ret;
}
return 0;
}
static int pinProgs(const char* const elfPath, const struct bpf_object * obj,
const vector<codeSection>& cs, const unsigned int bpfloader_ver) {
int ret;
string objName = pathToObjName(string(elfPath));
for (int i = 0; i < (int)cs.size(); i++) {
string program_name = cs[i].program_name;
struct bpf_program* prog = bpf_object__find_program_by_name(obj, program_name.c_str());
if (!prog) {
ALOGE("bpf_object does not contain program: %s", program_name.c_str());
return -1;
}
// This program was skipped
if (!bpf_program__autoload(prog)) continue;
string name = cs[i].name;
name = name.substr(0, name.find_last_of('$'));
string progPinLoc = buildProgPinLoc(cs[i].prog_def->pin_subdir, objName, name);
if (access(progPinLoc.c_str(), F_OK) == 0) {
// TODO: Skip loading lower priority program
ALOGI("Higher priority program is already pinned, skip pinning %s", cs[i].name.c_str());
continue;
}
int fd = bpf_program__fd(prog);
ret = pinProg(fd, cs[i].prog_def.value(), progPinLoc);
if (ret) return ret;
ret = validateProg(fd, progPinLoc, bpfloader_ver);
if (ret) return ret;
}
return 0;
}
static int loadProgByLibbpf(const char* const elfPath, const unsigned int bpfloader_ver) {
int ret;
vector<string> mapNames;
vector<struct bpf_map_def> md;
vector<codeSection> cs;
ifstream elfFile(elfPath, ios::in | ios::binary);
if (!elfFile.is_open()) return -1;
LIBBPF_OPTS(bpf_object_open_opts, opts,
.bpf_token_path = "",
);
struct bpf_object* obj = bpf_object__open_file(elfPath, &opts);
if (!obj) return -1;
auto objGuard = base::make_scope_guard([&obj] { bpf_object__close(obj); });
ret = readSectionByName(".android_maps", elfFile, md);
if (ret) return ret;
ret = readMapNames(elfFile, mapNames);
if (ret) return ret;
ret = prepareLoadMaps(obj, md, mapNames, bpfloader_ver);
if (ret) return ret;
ret = readCodeSections(elfFile, cs);
if (ret && ret != -ENOENT) return ret;
ret = prepareLoadProgs(obj, cs, bpfloader_ver);
if (ret) return ret;
ret = bpf_object__load(obj);
if (ret) return ret;
ret = pinMaps(elfPath, obj, md, mapNames);
if (ret) return ret;
ret = pinProgs(elfPath, obj, cs, bpfloader_ver);
if (ret) return ret;
return 0;
}
int loadProg(const char* const elfPath, const unsigned int bpfloader_ver) {
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", elfPath);
return ret;
} else {
ALOGD("Loading ELF object %s with license %s",
elfPath, (char*)license.data());
}
ALOGD("BpfLoader ver 0x%05x processing ELF object %s", bpfloader_ver, elfPath);
ret = createMaps(elfPath, elfFile, mapFds, bpfloader_ver);
if (ret) {
ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
return ret;
}
for (int i = 0; i < (int)mapFds.size(); i++)
ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
ret = readCodeSections(elfFile, cs);
if (ret == -ENOENT) return 0;
if (ret) {
ALOGE("Couldn't read all code sections in %s", elfPath);
return ret;
}
applyMapRelo(elfFile, mapFds, cs);
ret = loadCodeSections(elfPath, cs, string(license.data()), bpfloader_ver);
if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
return ret;
}
static bool exists(const char* const path) {
int v = access(path, F_OK);
if (!v) return true;
if (errno == ENOENT) return false;
ALOGE("FATAL: access(%s, F_OK) -> %d [%d:%s]", path, v, errno, strerror(errno));
abort(); // can only hit this if permissions (likely selinux) are screwed up
}
#define APEXROOT "/apex/com.android.tethering"
#define BPFROOT APEXROOT "/etc/bpf/mainline/"
static int loadObject(const unsigned int bpfloader_ver,
const char* const fname, const bool useLibbpf = false) {
string progPath = string(BPFROOT) + fname;
int ret = useLibbpf ? loadProgByLibbpf(progPath.c_str(), bpfloader_ver) :
loadProg(progPath.c_str(), bpfloader_ver);
if (ret) {
ALOGE("Failed to load object: %s, ret: %s, libbpf: %d",
progPath.c_str(), std::strerror(-ret), useLibbpf);
return 1;
}
ALOGD("Loaded object: %s, libbpf: %d", progPath.c_str(), useLibbpf);
return 0;
}
static int loadAllObjects(const unsigned int bpfloader_ver) {
// S+ Tethering mainline module (network_stack): tether offload
// loads under /sys/fs/bpf/tethering:
if (loadObject(bpfloader_ver, "offload.o")) return 1;
NetBpfLoad: Load test.o by libbpf on 25Q3+ Before this change """ $ adb shell ls -lZ /sys/fs/bpf/tethering/map_test* -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:35 /sys/fs/bpf/tethering/map_test_bitmap -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:35 /sys/fs/bpf/tethering/map_test_tether2_downstream6_map -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:35 /sys/fs/bpf/tethering/map_test_tether3_downstream6_map -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:35 /sys/fs/bpf/tethering/map_test_tether_downstream6_map $ adb logcat 07-29 19:35:18.994 790 790 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/test.o, libbpf: 0 """ After this change """ $ adb shell ls -lZ /sys/fs/bpf/tethering/map_test* -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:41 /sys/fs/bpf/tethering/map_test_bitmap -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:41 /sys/fs/bpf/tethering/map_test_tether2_downstream6_map -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:41 /sys/fs/bpf/tethering/map_test_tether3_downstream6_map -rw-rw---- 1 root network_stack u:object_r:fs_bpf_tethering:s0 0 2025-07-29 19:41 /sys/fs/bpf/tethering/map_test_tether_downstream6_map $ adb logcat 07-29 19:41:34.547 800 800 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/test.o, libbpf: 1 """ Bug: 428792764 Flag: EXEMPT mainline Test: TH Change-Id: I5525bc277960c7257724bd6a0a764653f6dfb186
2025-07-29 19:37:13 +09:00
if (loadObject(bpfloader_ver, "test.o", isAtLeast25Q3)) return 1;
if (isAtLeastT) {
// T+ Tethering mainline module loads under:
// /sys/fs/bpf/net_shared: shared with netd & system server
NetBpfLoad: Load clatd.o by libbpf on 25Q3+ Before this change """ $ adb shell ls -lZ /sys/fs/bpf/net_shared/*clat* -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:41 /sys/fs/bpf/net_shared/map_clatd_clat_egress4_map -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:41 /sys/fs/bpf/net_shared/map_clatd_clat_ingress6_map -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:41 /sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:41 /sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_ether -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:41 /sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_rawip $ adb logcat 07-29 19:41:34.556 800 800 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/clatd.o, libbpf: 0 """ After this change """ $ adb shell ls -lZ /sys/fs/bpf/net_shared/*clat* -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/map_clatd_clat_egress4_map -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/map_clatd_clat_ingress6_map -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_ether -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_rawip $ adb logcat 07-29 19:46:24.453 794 794 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/clatd.o, libbpf: 1 """ Bug: 428792764 Flag: EXEMPT mainline Test: TH Change-Id: Iecb07123ca7bc6bfa8649202ed212731398beeae
2025-07-29 19:43:40 +09:00
if (loadObject(bpfloader_ver, "clatd.o", isAtLeast25Q3)) return 1;
NetBpfLoad: Load dscpPolicy.o by libbpf on 25Q3+ Before this change """ $ adb shell ls -lZ /sys/fs/bpf/net_shared/*dscp* -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/map_dscpPolicy_ipv4_dscp_policies_map -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/map_dscpPolicy_ipv6_dscp_policies_map ---------- 1 root root u:object_r:fs_bpf_loader:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/map_dscpPolicy_socket_policy_cache_map -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:46 /sys/fs/bpf/net_shared/prog_dscpPolicy_schedcls_set_dscp_ether $ adb logcat 07-29 19:46:24.468 794 794 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/dscpPolicy.o, libbpf: 0 """ After this change """ $ adb shell ls -lZ /sys/fs/bpf/net_shared/*dscp* -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 /sys/fs/bpf/net_shared/map_dscpPolicy_ipv4_dscp_policies_map -rw-rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 /sys/fs/bpf/net_shared/map_dscpPolicy_ipv6_dscp_policies_map ---------- 1 root root u:object_r:fs_bpf_loader:s0 0 2025-07-29 19:52 /sys/fs/bpf/net_shared/map_dscpPolicy_socket_policy_cache_map -r--r----- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 /sys/fs/bpf/net_shared/prog_dscpPolicy_schedcls_set_dscp_ether $ adb logcat 07-29 19:52:24.493 792 792 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/dscpPolicy.o, libbpf: 1 """ Bug: 428792764 Flag: EXEMPT mainline Test: TH Change-Id: If57594b490908eb46070812c12e611c2804fe251
2025-07-29 19:49:02 +09:00
if (loadObject(bpfloader_ver, "dscpPolicy.o", isAtLeast25Q3)) return 1;
// /sys/fs/bpf/netd_shared: shared with netd & system server
// - netutils_wrapper (for iptables xt_bpf) has access to programs
// WARNING: Android T+ non-updatable netd depends on both of the
// 'netd_shared' & 'netd' strings for xt_bpf programs it loads
NetBpfLoad: Load netd.o by libbpf on 25Q3+ Before this change """ $ adb shell ls -lZ /sys/fs/bpf/netd_shared/ total 0 drwxrwxrwt 2 root root u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 mainline_done ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_app_uid_stats_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_blocked_ports_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_configuration_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 map_netd_cookie_tag_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_data_saver_enabled_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_iface_index_name_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_iface_stats_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_ingress_discard_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_local_net_access_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_local_net_blocked_uid_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 map_netd_lock_array_test_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 map_netd_lock_hash_test_map ----rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_packet_trace_enabled_map ----rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_packet_trace_ringbuf ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_sk_storage -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_stats_map_A -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_stats_map_B ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 map_netd_uid_counterset_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_uid_owner_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 map_netd_uid_permission_map -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_bind4_inet4_bind -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_bind6_inet6_bind -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_cgroupskb_egress_stats -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_cgroupskb_ingress_stats -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_cgroupsock_inet_create -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_cgroupsockrelease_inet_release -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_connect4_inet4_connect -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_connect6_inet6_connect -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_getsockopt_prog -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_recvmsg4_udp4_recvmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_recvmsg6_udp6_recvmsg -r--r----- 1 root net_admin u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:52 prog_netd_schedact_ingress_account -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_sendmsg4_udp4_sendmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_sendmsg6_udp6_sendmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:52 prog_netd_setsockopt_prog -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 prog_netd_skfilter_allowlist_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 prog_netd_skfilter_denylist_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 prog_netd_skfilter_egress_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:52 prog_netd_skfilter_ingress_xtbpf $ adb logcat 07-29 19:52:24.598 792 792 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/netd.o, libbpf: 0 """ After this change """ $ adb shell ls -lZ /sys/fs/bpf/netd_shared/ motomuman@DESK:/disk/main$ adb shell ls -lZ /sys/fs/bpf/netd_shared/ total 0 drwxrwxrwt 2 root root u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 mainline_done ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_app_uid_stats_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_blocked_ports_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_configuration_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 map_netd_cookie_tag_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_data_saver_enabled_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_iface_index_name_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_iface_stats_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_ingress_discard_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_local_net_access_map ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_local_net_blocked_uid_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 map_netd_lock_array_test_map -rw-rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 map_netd_lock_hash_test_map ----rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_packet_trace_enabled_map ----rw---- 1 root system u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_packet_trace_ringbuf ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_sk_storage -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_stats_map_A -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_stats_map_B ----rw---- 1 root net_bw_acct u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 map_netd_uid_counterset_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_uid_owner_map -r--rw---- 1 root net_bw_acct u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 map_netd_uid_permission_map -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_bind4_inet4_bind -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_bind6_inet6_bind -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_cgroupskb_egress_stats -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_cgroupskb_ingress_stats -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_cgroupsock_inet_create -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_cgroupsockrelease_inet_release -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_connect4_inet4_connect -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_connect6_inet6_connect -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_getsockopt_prog -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_recvmsg4_udp4_recvmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_recvmsg6_udp6_recvmsg -r--r----- 1 root net_admin u:object_r:fs_bpf_net_shared:s0 0 2025-07-29 19:56 prog_netd_schedact_ingress_account -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_sendmsg4_udp4_sendmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_sendmsg6_udp6_sendmsg -r--r----- 1 root root u:object_r:fs_bpf_netd_readonly:s0 0 2025-07-29 19:56 prog_netd_setsockopt_prog -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 prog_netd_skfilter_allowlist_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 prog_netd_skfilter_denylist_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 prog_netd_skfilter_egress_xtbpf -r--r----- 1 root net_admin u:object_r:fs_bpf_netd_shared:s0 0 2025-07-29 19:56 prog_netd_skfilter_ingress_xtbpf $ adb logcat 07-29 19:56:31.625 788 788 D NetBpfLoad: Loaded object: /apex/com.android.tethering/etc/bpf/mainline/netd.o, libbpf: 1 """ Bug: 428792764 Flag: EXEMPT mainline Test: TH Change-Id: I79b007450df134533fd780a9e94361361d862fb8
2025-07-29 19:53:35 +09:00
if (loadObject(bpfloader_ver, "netd.o", isAtLeast25Q3)) return 1;
// /sys/fs/bpf/netd_readonly: shared with netd & system server
// - netutils_wrapper has no access, netd has read only access
// /sys/fs/bpf/net_private: not shared, just network_stack
}
return 0;
}
static int createDir(const char* const dir) {
mode_t prevUmask = umask(0);
errno = 0;
int ret = mkdir(dir, S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
if (ret && errno != EEXIST) {
const int err = errno;
umask(prevUmask);
ALOGE("Failed to create directory: %s, ret: %s", dir, std::strerror(err));
return -err;
}
umask(prevUmask);
return 0;
}
// Technically 'value' doesn't need to be newline terminated, but it's best
// to include a newline to match 'echo "value" > /proc/sys/...foo' behaviour,
// which is usually how kernel devs test the actual sysctl interfaces.
static int writeFile(const char *filename, const char *value) {
unique_fd fd(open(filename, O_WRONLY | O_CLOEXEC));
if (fd < 0) {
const int err = errno;
ALOGE("open('%s', O_WRONLY | O_CLOEXEC) -> %s", filename, strerror(err));
return -err;
}
int len = strlen(value);
int v = write(fd, value, len);
if (v < 0) {
const int err = errno;
ALOGE("write('%s', '%s', %d) -> %s", filename, value, len, strerror(err));
return -err;
}
if (v != len) {
ALOGE("write('%s', '%s', %d) -> short write [%d]", filename, value, len, v);
return -EINVAL;
}
return 0;
}
#define APEX_MOUNT_POINT "/apex/com.android.tethering"
const char * const platformBpfLoader = "/system/bin/bpfloader";
const char *const uprobestatsBpfLoader =
"/apex/com.android.uprobestats/bin/uprobestatsbpfload";
static int logTetheringApexVersion(void) {
char * found_blockdev = NULL;
FILE * f = NULL;
char buf[4096];
f = fopen("/proc/mounts", "re");
if (!f) return 1;
// /proc/mounts format: block_device [space] mount_point [space] other stuff... newline
while (fgets(buf, sizeof(buf), f)) {
char * blockdev = buf;
char * space = strchr(blockdev, ' ');
if (!space) continue;
*space = '\0';
char * mntpath = space + 1;
space = strchr(mntpath, ' ');
if (!space) continue;
*space = '\0';
if (strcmp(mntpath, APEX_MOUNT_POINT)) continue;
found_blockdev = strdup(blockdev);
break;
}
fclose(f);
f = NULL;
if (!found_blockdev) return 2;
ALOGV("Found Tethering Apex mounted from blockdev %s", found_blockdev);
f = fopen("/proc/mounts", "re");
if (!f) { free(found_blockdev); return 3; }
while (fgets(buf, sizeof(buf), f)) {
char * blockdev = buf;
char * space = strchr(blockdev, ' ');
if (!space) continue;
*space = '\0';
char * mntpath = space + 1;
space = strchr(mntpath, ' ');
if (!space) continue;
*space = '\0';
if (strcmp(blockdev, found_blockdev)) continue;
if (strncmp(mntpath, APEX_MOUNT_POINT "@", strlen(APEX_MOUNT_POINT "@"))) continue;
char * at = strchr(mntpath, '@');
if (!at) continue;
char * ver = at + 1;
ALOGI("Tethering APEX version %s", ver);
}
fclose(f);
free(found_blockdev);
return 0;
}
static bool hasGSM() {
static string ph = GetProperty("gsm.current.phone-type", "");
static bool gsm = (ph != "");
static bool logged = false;
if (!logged) {
logged = true;
ALOGI("hasGSM(gsm.current.phone-type='%s'): %s", ph.c_str(), gsm ? "true" : "false");
}
return gsm;
}
static bool isTV() {
if (hasGSM()) return false; // TVs don't do GSM
static string key = GetProperty("ro.oem.key1", "");
static bool tv = StartsWith(key, "ATV00");
static bool logged = false;
if (!logged) {
logged = true;
ALOGI("isTV(ro.oem.key1='%s'): %s.", key.c_str(), tv ? "true" : "false");
}
return tv;
}
static bool isWear() {
static string wearSdkStr = GetProperty("ro.cw_build.wear_sdk.version", "");
static int wearSdkInt = GetIntProperty("ro.cw_build.wear_sdk.version", 0);
static string buildChars = GetProperty("ro.build.characteristics", "");
static vector<string> v = Tokenize(buildChars, ",");
static bool watch = (std::find(v.begin(), v.end(), "watch") != v.end());
static bool wear = (wearSdkInt > 0) || watch;
static bool logged = false;
if (!logged) {
logged = true;
ALOGI("isWear(ro.cw_build.wear_sdk.version=%d[%s] ro.build.characteristics='%s'): %s",
wearSdkInt, wearSdkStr.c_str(), buildChars.c_str(), wear ? "true" : "false");
}
return wear;
}
static int libbpfPrint(enum libbpf_print_level lvl, const char *const formatStr,
va_list argList) {
#ifndef NETBPFLOAD_VERBOSE_LOG
if (lvl != LIBBPF_WARN) return 0;
#endif
int32_t prio;
switch (lvl) {
case LIBBPF_WARN:
prio = ANDROID_LOG_WARN;
break;
case LIBBPF_INFO:
prio = ANDROID_LOG_INFO;
break;
case LIBBPF_DEBUG:
prio = ANDROID_LOG_DEBUG;
break;
}
if (!formatStr) {
LOG_PRI(prio, LOG_TAG, "libbpf (null format string)");
return 0;
}
// Print each line to avoid being truncated.
char *s = NULL;
int ret = vasprintf(&s, formatStr, argList);
if (ret == -1) {
LOG_PRI(prio, LOG_TAG, "libbpf (format failure)");
return 0;
}
int len = strlen(s);
if (len && s[len - 1] == '\n')
s[len - 1] = 0;
vector<string> lines = Split(s, "\n");
for (const auto& line : lines) LOG_PRI(prio, LOG_TAG, "%s", line.c_str());
free(s);
return 0;
}
static int doLoad(char** argv, char * const envp[]) {
if (!isAtLeastS) {
ALOGE("Impossible - not reachable on Android <S.");
// for safety, we don't fail, this is a just-in-case workaround
// for any possible busted 'optimized' start everything vendor init hacks on R
return 0;
}
libbpf_set_print(libbpfPrint);
const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
const int first_api_level = GetIntProperty("ro.board.first_api_level", api_level);
// last in U QPR2 beta1
const bool has_platform_bpfloader_rc = exists("/system/etc/init/bpfloader.rc");
// first in U QPR2 beta~2
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
// Version of Network BpfLoader depends on the Android OS version
unsigned int bpfloader_ver = BPFLOADER_MAINLINE_S_VERSION; // [42u]
if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
if (isAtLeast25Q2) ++bpfloader_ver; // [47] BPFLOADER_MAINLINE_25Q2_VERSION
if (isAtLeast25Q3) ++bpfloader_ver; // [48] BPFLOADER_MAINLINE_25Q3_VERSION
if (isAtLeast25Q4) ++bpfloader_ver; // [49] BPFLOADER_MAINLINE_25Q4_VERSION
if (isAtLeast26Q1) ++bpfloader_ver; // [50] BPFLOADER_MAINLINE_26Q1_VERSION
if (isAtLeast26Q2) ++bpfloader_ver; // [51] BPFLOADER_MAINLINE_26Q2_VERSION
ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) libbpf: v%u.%u "
"uid:%d rc:%d%d",
bpfloader_ver, argv[0], android_get_device_api_level(), api_level,
kernelVersion(), describeArch(), libbpf_major_version(),
libbpf_minor_version(), getuid(), has_platform_bpfloader_rc,
has_platform_netbpfload_rc);
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
ALOGE("Unable to find platform's bpfloader & netbpfload init scripts.");
return 1;
}
if (has_platform_bpfloader_rc && has_platform_netbpfload_rc) {
ALOGE("Platform has *both* bpfloader & netbpfload init scripts.");
return 1;
}
logTetheringApexVersion();
// both S and T require kernel 4.9 (and eBpf support)
if (!isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android S & T require kernel 4.9.");
return 1;
}
// U bumps the kernel requirement up to 4.14
if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
ALOGE("Android U requires kernel 4.14.");
return 1;
}
// V bumps the kernel requirement up to 4.19
// see also: //system/netd/tests/kernel_test.cpp TestKernel419
if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
ALOGE("Android V requires kernel 4.19.");
return 1;
}
// 25Q2 bumps the kernel requirement up to 5.4
// see also: //system/netd/tests/kernel_test.cpp TestKernel54
if (isAtLeast25Q2 && !isAtLeastKernelVersion(5, 4, 0)) {
ALOGE("Android 25Q2 requires kernel 5.4.");
return 1;
}
// 25Q4 bumps the kernel requirement up to 5.10
// see also: //system/netd/tests/kernel_test.cpp TestKernel510
if (isAtLeast25Q4 && !isAtLeastKernelVersion(5, 10, 0)) {
ALOGE("Android 25Q4 requires kernel 5.10.");
return 1;
}
// Technically already required by U, but only enforce on V+
// see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
ALOGE("Android V+ platform with 32 bit kernel version >= 5.16.0 is unsupported");
if (!isTV()) return 1;
}
if (isKernel32Bit() && isAtLeast25Q2) {
ALOGE("Android 25Q2 requires 64 bit kernel.");
return 1;
}
// 6.6 is highest version supported by Android V, so this is effectively W+ (sdk=36+)
if (isKernel32Bit() && isAtLeastKernelVersion(6, 7, 0)) {
ALOGE("Android platform with 32 bit kernel version >= 6.7.0 is unsupported");
return 1;
}
// Various known ABI layout issues, particularly wrt. bpf and ipsec/xfrm.
if (isAtLeastV && isKernel32Bit() && isX86()) {
ALOGE("Android V requires X86 kernel to be 64-bit.");
if (!isTV()) return 1;
}
if (isAtLeastV) {
bool bad = false;
if (!isLtsKernel()) {
ALOGW("Android V+ only supports LTS kernels.");
bad = true;
}
#define REQUIRE(maj, min, sub) \
if (isKernelVersion(maj, min) && !isAtLeastKernelVersion(maj, min, sub)) { \
ALOGW("Android V+ requires %d.%d kernel to be %d.%d.%d+.", maj, min, maj, min, sub); \
bad = true; \
}
REQUIRE(4, 19, 236)
REQUIRE(5, 4, 186)
REQUIRE(5, 10, 199)
REQUIRE(5, 15, 136)
REQUIRE(6, 1, 57)
REQUIRE(6, 6, 0)
REQUIRE(6, 12, 0)
#undef REQUIRE
if (bad) {
ALOGE("Unsupported kernel version (%07x).", kernelVersion());
}
}
/* Android 14/U should only launch on 64-bit kernels
* T launches on 5.10/5.15
* U launches on 5.15/6.1
* So >=5.16 implies isKernel64Bit()
*
* We thus added a test to V VTS which requires 5.16+ devices to use 64-bit kernels.
*
* Starting with Android V, which is the first to support a post 6.1 Linux Kernel,
* we also require 64-bit userspace.
*
* There are various known issues with 32-bit userspace talking to various
* kernel interfaces (especially CAP_NET_ADMIN ones) on a 64-bit kernel.
* Some of these have userspace or kernel workarounds/hacks.
* Some of them don't...
* We're going to be removing the hacks.
* (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
* Note: this check/enforcement only applies to *system* userspace code,
* it does not affect unprivileged apps, the 32-on-64 compatibility
* problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
* Note, however, that TV and Wear devices will continue to support 32-bit userspace
* on ARM64.
*/
if (isUserspace32bit() && isAtLeastKernelVersion(6, 2, 0)) {
// Stuff won't work reliably, but...
if (isArm() && (isTV() || isWear())) {
// exempt Arm TV or Wear devices (arm32 ABI is far less problematic than x86-32)
ALOGW("[Arm TV/Wear] 32-bit userspace unsupported on 6.2+ kernels.");
} else if (first_api_level <= 33 /*T*/ && isArm()) {
// also exempt Arm devices upgrading with major kernel rev from T-
// might possibly be better for them to run with a newer kernel...
ALOGW("[Arm KernelUpRev] 32-bit userspace unsupported on 6.2+ kernels.");
} else if (isArm()) {
ALOGE("[Arm] 64-bit userspace required on 6.2+ kernels (%d).", first_api_level);
return 1;
} else { // x86 since RiscV cannot be 32-bit
ALOGE("[x86] 64-bit userspace required on 6.2+ kernels.");
return 1;
}
}
// Linux 6.12 was an LTS released at the end of 2024 (Nov 17),
// and was first supported by Android 16 / 25Q2 (released in June 2025).
// The next Linux LTS should be released near the end of 2025,
// and will likely be 6.18.
// Since officially Android only supports LTS, 6.13+ really means 6.18+,
// and won't be supported before 2026, most likely Android 17 / 26Q2.
// 6.13+ (implying 26Q2+) requires 64-bit userspace.
if (isUserspace32bit() && isAtLeastKernelVersion(6, 13, 0)) {
// due to previous check only reachable on Arm && (<=T kernel uprev || TV || Wear)
ALOGE("64-bit userspace required on 6.13+ kernels.");
return 1;
}
if (isAtLeast25Q2) {
FILE * f = fopen("/system/etc/init/netbpfload.rc", "re");
if (!f) {
ALOGE("failure opening /system/etc/init/netbpfload.rc");
return 1;
}
int y = -1, q = -1, a = -1, b = -1, c = -1;
int v = fscanf(f, "# %d %d %d %d %d #", &y, &q, &a, &b, &c);
ALOGI("detected %d of 5: %dQ%d api:%d.%d.%d", v, y, q, a, b, c);
fclose(f);
if (v != 5) return 1;
if (y < 2025 || y > 2099) return 1;
if (q < 1 || q > 4) return 1;
if (a < 36) return 1;
if (b < 0 || b > 4) return 1;
if (c < 0) return 1;
}
// Ensure we can determine the Android build type.
if (!isEng() && !isUser() && !isUserdebug()) {
ALOGE("Failed to determine the build type: got %s, want 'eng', 'user', or 'userdebug'",
getBuildType().c_str());
return 1;
}
if (runningAsRoot) {
// Note: writing this proc file requires being root (always the case on V+)
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
// pre-5.13, on 5.13+ it depends on CONFIG_BPF_UNPRIV_DEFAULT_OFF)
if (writeFile("/proc/sys/kernel/unprivileged_bpf_disabled", "0\n") &&
isAtLeastKernelVersion(5, 13, 0)) return 1;
}
if (isAtLeastU) {
// Note: writing these proc files requires CAP_NET_ADMIN
// and sepolicy which is only present on U+,
// on Android T and earlier versions they're written from the 'load_bpf_programs'
// trigger (ie. by init itself) instead.
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
// kernel does not have CONFIG_BPF_JIT=y)
// BPF_JIT is required by R VINTF (which means 4.14/4.19/5.4 kernels),
// but 4.14/4.19 were released with P & Q, and only 5.4 is new in R+.
if (writeFile("/proc/sys/net/core/bpf_jit_enable", "1\n")) return 1;
// Enable JIT kallsyms export for privileged users only
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
// kernel does not have CONFIG_HAVE_EBPF_JIT=y)
if (writeFile("/proc/sys/net/core/bpf_jit_kallsyms", "1\n")) return 1;
}
if (runningAsRoot) { // implies U QPR3+ and kernel 4.14+
// There should not be any programs or maps yet
errno = 0;
uint32_t progId = bpfGetNextProgId(0); // expect 0 with errno == ENOENT
if (progId || errno != ENOENT) {
ALOGE("bpfGetNextProgId(zero) returned %u (errno %d)", progId, errno);
return 1;
}
errno = 0;
uint32_t mapId = bpfGetNextMapId(0); // expect 0 with errno == ENOENT
if (mapId || errno != ENOENT) {
ALOGE("bpfGetNextMapId(zero) returned %u (errno %d)", mapId, errno);
return 1;
}
} else if (isAtLeastKernelVersion(4, 14, 0)) { // implies S through U QPR2
// bpfGetNext{Prog,Map}Id require 4.14+
// furthermore since we're not running as root, we're not the initial
// platform bpfloader, so there may already be some maps & programs.
uint32_t mapId = 0;
while (true) {
errno = 0;
uint32_t next = bpfGetNextMapId(mapId);
if (!next && errno == ENOENT) break;
if (next <= mapId) {
ALOGE("bpfGetNextMapId(%u) returned %u errno %d", mapId, next, errno);
return 1;
}
mapId = next;
}
// mapId is now the last map id, creating a new map should change that
unique_fd map(createMap(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(int), 1, 0));
errno = 0;
uint32_t next = bpfGetNextMapId(mapId);
if (next <= mapId) {
// We should fail here on Xiaomi S 4.14.180 due to kernel uapi bug,
// which causes bpfGetNextMapId to behave as bpfGetNextProgId,
// and thus it should return 0 with errno == ENOENT.
ALOGE("bpfGetNextMapId(final %d) returned %d errno %d", mapId, next, errno);
NetBpfLoad: workaround for Xiaomi kernel uapi bug See https://b.corp.google.com/issues/414570528#comment96 which can be summarized as https://github.com/MiCode/Xiaomi_Kernel_OpenSource/blob/vayu-r-oss/include/uapi/linux/bpf.h has: enum bpf_cmd { BPF_MAP_CREATE, <-- UAPI 0 BPF_MAP_LOOKUP_ELEM, <-- UAPI 1 BPF_MAP_UPDATE_ELEM, <-- UAPI 2 BPF_MAP_DELETE_ELEM, <-- UAPI 3 BPF_MAP_GET_NEXT_KEY, <-- UAPI 4 BPF_PROG_LOAD, <-- UAPI 5 BPF_OBJ_PIN, <-- UAPI 6 BPF_OBJ_GET, <-- UAPI 7 BPF_PROG_ATTACH, <-- UAPI 8 BPF_PROG_DETACH, <-- UAPI 9 BPF_GET_COMM_HASH, <-- breaks Linux uapi (uses 10) BPF_PROG_TEST_RUN, <-- UAPI 10 becomes 11 (aka. BPF_PROG_RUN) BPF_PROG_GET_NEXT_ID, <-- UAPI 11 becomes 12 BPF_MAP_GET_NEXT_ID, <-- UAPI 12 becomes 13 BPF_PROG_GET_FD_BY_ID, <-- UAPI 13 becomes 14 BPF_MAP_GET_FD_BY_ID, <-- UAPI 14 becomes 15 BPF_OBJ_GET_INFO_BY_FD, <-- UAPI 15 becomes 16 }; this addition in the middle of the userspace api enum, causes all following enum values to be greater by one. In particular this increases the value of BPF_OBJ_GET_INFO_BY_FD from 15 to 16, thus break it. It also causes UAPI 12 (BPF_MAP_GET_NEXT_ID) to actually execute BPF_PROG_GET_NEXT_ID (normally UAPI 13) As such this can be detected via finding the index of the last map (on buggy kernels this will actually find the index of the last program), then creating a map, then checking if the last map index has changed (on buggy kernels this will not be the case because we did not create another program). Bug: 414570528 Test: TreeHugger Flag: EXEMPT mainline Change-Id: Iecad121ac624df2d123a13e90a46792bd567a4c6 Signed-off-by: Maciej Żenczykowski <maze@google.com>
2025-06-10 01:56:48 -07:00
if (next || errno != ENOENT) return 1;
if (isAtLeastT || isAtLeastKernelVersion(4, 20, 0)) return 1;
// implies Android S with 4.14 or 4.19 kernel
ALOGW("Enabling bpfCmdFixupIsNeeded.");
bpfCmdFixupIsNeeded = true;
}
} else { // implies S/T with 4.9 kernel
// nothing we can do.
}
// Create all the pin subdirectories
// (this must be done first to allow create_location and pin_subdir functionality,
// which could otherwise fail with ENOENT during object pinning or renaming,
// due to ordering issues)
if (createDir("/sys/fs/bpf/tethering")) return 1;
// This is technically T+ but S also needs it for the 'mainline_done' file.
if (createDir("/sys/fs/bpf/netd_shared")) return 1;
if (isAtLeastT) {
if (createDir("/sys/fs/bpf/netd_readonly")) return 1;
if (createDir("/sys/fs/bpf/net_shared")) return 1;
if (createDir("/sys/fs/bpf/net_private")) return 1;
// This one is primarily meant for triggering genfscon rules.
if (createDir("/sys/fs/bpf/loader")) return 1;
}
// Load all ELF objects, create programs and maps, and pin them
if (loadAllObjects(bpfloader_ver)) {
ALOGE("=== CRITICAL FAILURE LOADING BPF PROGRAMS ===");
ALOGE("If this triggers reliably, you're probably missing kernel options or patches.");
ALOGE("If this triggers randomly, you might be hitting some memory allocation "
"problems or startup script race.");
ALOGE("--- DO NOT EXPECT SYSTEM TO BOOT SUCCESSFULLY ---");
sleep(20);
return 2;
}
{
// Create a trivial bpf map: a two element array [int->int]
unique_fd map(createMap(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(int), 2, 0));
int zero = 0;
int kernel_bugs = bpfCmdFixupIsNeeded;
if (writeToMapEntry(map, &zero, &kernel_bugs, BPF_ANY)) {
ALOGE("Failure to write into index 0 of kernel bugs array.");
return 1;
}
int one = 1;
int value = 123;
if (writeToMapEntry(map, &one, &value, BPF_ANY)) {
ALOGE("Critical kernel bug - failure to write into index 1 of 2 element bpf map array.");
if (isAtLeastT) return 1;
}
int ret = bpfFdPin(map, "/sys/fs/bpf/tethering/map_kernel_bugs");
if (ret) {
const int err = errno;
ALOGE("pin -> %d [%d:%s]", ret, err, strerror(err));
return -err;
}
}
// leave a flag that we're done
if (createDir("/sys/fs/bpf/netd_shared/mainline_done")) return 1;
// platform bpfloader will only succeed when run as root
if (!runningAsRoot) {
// unreachable on U QPR3+ which always runs netbpfload as root
ALOGI("mainline done, no need to transfer control to platform bpf loader.");
return 0;
}
// unreachable before U QPR3
if (exists(uprobestatsBpfLoader)) {
ALOGI("done, transferring control to uprobestatsbpfload.");
const char *args[] = {
uprobestatsBpfLoader,
NULL,
};
execve(args[0], (char **)args, envp);
ALOGI("unable to execute uprobestatsbpfload, transferring control to "
"platform bpfloader.");
}
// platform BpfLoader *needs* to run as root
const char * args[] = { platformBpfLoader, NULL, };
execve(args[0], (char**)args, envp);
ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
return 1;
}
} // namespace bpf
} // namespace android
int main(int argc, char** argv, char * const envp[]) {
if (android::bpf::isAtLeastT) {
InitLogging(argv, &KernelLogger);
} else {
// S lacks the sepolicy to make non-root uid KernelLogger viable
InitLogging(argv);
}
if (argc == 2 && !strcmp(argv[1], "done")) {
// we're being re-exec'ed from platform bpfloader to 'finalize' things
if (!SetProperty("bpf.progs_loaded", "1")) {
ALOGE("Failed to set bpf.progs_loaded property to 1.");
return 125;
}
ALOGI("success.");
return 0;
}
return android::bpf::doLoad(argv, envp);
}