non-GPL/HostIsolation/Lib/KprobeLoader.c (201 lines of code) (raw):

// SPDX-License-Identifier: Elastic-2.0 /* * Copyright 2021 Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under * one or more contributor license agreements. Licensed under the Elastic * License 2.0; you may not use this file except in compliance with the Elastic * License 2.0. */ // // Host Isolation standalone demo // Loader for eBPF program #2 (attaches to tcp_v4_connect kprobe) // #include "KprobeLoader.h" #include "Common.h" #include <bpf/bpf.h> #include <bpf/libbpf.h> #include <elf.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <unistd.h> // requires glibc >= 2.16 //#define GETAUXVAL_SUPPORTED #ifdef GETAUXVAL_SUPPORTED #include <sys/auxv.h> #endif #if defined(__LP64__) #define ElfW(type) Elf64_##type #else #define ElfW(type) Elf32_##type #endif static unsigned int find_version_note(unsigned long base) { ElfW(Ehdr) *ehdr = NULL; unsigned int version_code = 0; int i = 0; if (!base) { ebpf_log("find_version_note error: NULL parameter\n"); goto out; } ehdr = (ElfW(Ehdr) *)base; for (i = 0; i < ehdr->e_shnum; i++) { ElfW(Shdr) *shdr = (ElfW(Shdr) *)(base + ehdr->e_shoff + (i * ehdr->e_shentsize)); if (shdr->sh_type == SHT_NOTE) { const char *ptr = (const char *)(base + shdr->sh_offset); const char *end = ptr + shdr->sh_size; while (ptr < end) { ElfW(Nhdr) *nhdr = (ElfW(Nhdr) *)ptr; ptr += sizeof(*nhdr); const char *name = ptr; ptr += (nhdr->n_namesz + sizeof(ElfW(Word)) - 1) & -sizeof(ElfW(Word)); const char *desc = ptr; ptr += (nhdr->n_descsz + sizeof(ElfW(Word)) - 1) & -sizeof(ElfW(Word)); if ((nhdr->n_namesz > 5 && !memcmp(name, "Linux", 5)) && nhdr->n_descsz == 4 && !nhdr->n_type) { version_code = *(uint32_t *)desc; goto out; } } } } out: return version_code; } static unsigned long get_auxiliary_vector_base(unsigned long at_key) { unsigned long base = 0; #ifdef GETAUXVAL_SUPPORTED base = getauxval(at_key); return base; #else FILE *f = NULL; f = fopen("/proc/self/auxv", "r"); if (!f) { ebpf_log("failed to open /proc/self/auxv: %d\n", -errno); return 0; } while (true) { unsigned long key = 0; unsigned long value = 0; size_t ret = 0; ret = fread(&key, sizeof(key), 1, f); if (ret != 1) break; ret = fread(&value, sizeof(value), 1, f); if (ret != 1) break; if (key == 0 && value == 0) break; if (key == at_key) { base = value; break; } } fclose(f); return base; #endif } static unsigned int get_kernel_version(enum ebpf_load_method method) { unsigned int code = 0; int rv = 0; FILE *f = NULL; switch (method) { case EBPF_METHOD_NO_OVERRIDE: { // default method - do not override kernel version code = 0; goto out; } case EBPF_METHOD_VDSO: { // Fetch LINUX_VERSION_CODE from the vDSO .note section. // This always matches the running kernel, but is not supported on // arm32. unsigned long base = get_auxiliary_vector_base(AT_SYSINFO_EHDR); // Check ELF magic value if (base && !memcmp((void *)base, ELFMAG, 4)) { code = find_version_note(base); } goto out; } case EBPF_METHOD_VERSION_H: { // check if version.h exists f = fopen("/usr/include/linux/version.h", "r"); if (!f) { code = 0; goto out; } rv = fscanf(f, "#define LINUX_VERSION_CODE %d", &code); if (rv != 1) { code = 0; fclose(f); goto out; } fclose(f); goto out; } default: { code = 0; goto out; } } out: return code; } struct bpf_object *ebpf_open_object_file(const char *file_path) { struct bpf_object *obj = NULL; if (!file_path) { ebpf_log("error: file path is NULL\n"); obj = NULL; goto cleanup; } obj = bpf_object__open_file(file_path, NULL); if (!obj || libbpf_get_error(obj)) { ebpf_log("failed to open BPF object\n"); bpf_object__close(obj); obj = NULL; goto cleanup; } cleanup: return obj; } int ebpf_map_set_pin_path(struct bpf_object *obj, const char *map_name, const char *map_path) { struct bpf_map *map = NULL; int rv = 0; if (!obj || !map_name || !map_path) { ebpf_log("ebp_map_set_pin_path error: NULL parameter\n"); rv = -1; goto cleanup; } map = bpf_object__find_map_by_name(obj, map_name); if (!map || libbpf_get_error(map)) { ebpf_log("failed to load %s BPF map\n", map_name); rv = -1; goto cleanup; } rv = bpf_map__set_pin_path(map, map_path); if (rv) { ebpf_log("failed to set pin path for %s map\n", map_name); rv = -1; goto cleanup; } cleanup: return rv; } struct bpf_link *ebpf_load_and_attach_kprobe(struct bpf_object *obj, const char *program_name, enum ebpf_load_method load_method) { struct bpf_program *prog = NULL; struct bpf_link *link = NULL; unsigned int kernel_version = 0; // Load may fail if an incorrect kernel version number was passed to the // bpf() syscall (old Linux kernels verify that, while newer kernels ignore // it). Try one of the methods of getting the kernel version, set it in // libbpf and load kernel_version = get_kernel_version(load_method); if (kernel_version != 0) { ebpf_log("got kernel_version=%d according to method=%d\n", kernel_version, load_method); if (bpf_object__set_kversion(obj, kernel_version) != 0) { ebpf_log("failed to set kversion\n"); } } if (bpf_object__load(obj) < 0) { ebpf_log("failed to load BPF program\n"); link = NULL; goto cleanup; } prog = bpf_object__find_program_by_name(obj, program_name); if (!prog || libbpf_get_error(prog)) { ebpf_log("failed to find BPF program by name\n"); link = NULL; goto cleanup; } link = bpf_program__attach(prog); if (!link || libbpf_get_error(link)) { ebpf_log("failed to attach BPF program\n"); bpf_link__destroy(link); link = NULL; goto cleanup; } cleanup: return link; }