Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2023 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include "bpfilter/cgen/elfstub.h"
7 :
8 : #include <linux/bpf.h>
9 :
10 : #include <elf.h>
11 : #include <errno.h>
12 :
13 : #include "bpfilter/cgen/rawstubs.h"
14 : #include "core/btf.h"
15 : #include "core/helper.h"
16 : #include "core/logger.h"
17 :
18 : static_assert(ARRAY_SIZE(_bf_rawstubs) == _BF_ELFSTUB_MAX,
19 : "_bf_rawstubs doesn't contain as many entries as bf_elfstub_id");
20 :
21 : #define _free_bf_printk_str_ __attribute__((cleanup(_bf_printk_str_free)))
22 :
23 4 : static int _bf_printk_str_new(struct bf_printk_str **pstr, size_t insn_idx,
24 : const char *str)
25 : {
26 4 : bf_assert(pstr && str);
27 :
28 4 : *pstr = malloc(sizeof(struct bf_printk_str));
29 4 : if (!*pstr)
30 : return -ENOMEM;
31 :
32 4 : (*pstr)->insn_idx = insn_idx;
33 4 : (*pstr)->str = str;
34 :
35 4 : return 0;
36 : }
37 :
38 8 : static void _bf_printk_str_free(struct bf_printk_str **pstr)
39 : {
40 8 : if (!*pstr)
41 : return;
42 :
43 : freep((void *)pstr);
44 : }
45 :
46 8 : static int _bf_elfstub_prepare(struct bf_elfstub *stub,
47 : const struct bf_rawstub *raw)
48 : {
49 8 : const Elf64_Ehdr *ehdr = raw->elf;
50 : Elf64_Shdr *shstrtab;
51 : Elf64_Shdr *shdrs;
52 : Elf64_Shdr *symtab = NULL;
53 : Elf64_Shdr *symstrtab = NULL;
54 : Elf64_Shdr *rodata_shdr = NULL;
55 : Elf64_Sym *symbols;
56 : char *sym_strtab;
57 : size_t sym_count;
58 : char *strtab;
59 : int r;
60 :
61 8 : if (raw->len < sizeof(Elf64_Ehdr))
62 0 : return bf_err_r(-EINVAL, "invalid ELF header (wrong header size)");
63 :
64 : // Check ELF magic
65 8 : if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0)
66 0 : return bf_err_r(-EINVAL, "invalid ELF header (wrong magic number)");
67 :
68 : // Ensure 64-bit ELF
69 8 : if (ehdr->e_ident[EI_CLASS] != ELFCLASS64)
70 0 : return bf_err_r(-ENOTSUP, "only 64-bit ELF is supported");
71 :
72 : // Get section header table
73 8 : if (ehdr->e_shoff >= raw->len || ehdr->e_shentsize != sizeof(Elf64_Shdr))
74 0 : return bf_err_r(-EINVAL, "invalid section header table");
75 :
76 8 : if (ehdr->e_shstrndx >= ehdr->e_shnum)
77 0 : return bf_err_r(-EINVAL, "invalid string table index");
78 :
79 8 : shdrs = (Elf64_Shdr *)(raw->elf + ehdr->e_shoff);
80 8 : shstrtab = &shdrs[ehdr->e_shstrndx];
81 8 : if (shstrtab->sh_offset >= raw->len)
82 0 : return bf_err_r(-EINVAL, "invalid string table offset");
83 :
84 8 : strtab = (char *)(raw->elf + shstrtab->sh_offset);
85 :
86 : // Find .text section
87 24 : for (int i = 0; i < ehdr->e_shnum; i++) {
88 24 : if (!bf_streq(&strtab[shdrs[i].sh_name], ".text"))
89 : continue;
90 :
91 8 : if (shdrs[i].sh_offset + shdrs[i].sh_size > raw->len)
92 0 : return bf_err_r(-EINVAL, "invalid .text section");
93 :
94 8 : stub->insns = malloc(shdrs[i].sh_size);
95 8 : if (!stub->insns)
96 : return -ENOMEM;
97 :
98 8 : memcpy(stub->insns, raw->elf + shdrs[i].sh_offset, shdrs[i].sh_size);
99 8 : stub->ninsns = shdrs[i].sh_size / sizeof(struct bpf_insn);
100 8 : break;
101 : }
102 :
103 8 : if (!stub->insns)
104 0 : return bf_err_r(-ENOENT, ".text section not found");
105 :
106 : // Find symbol table and its string table
107 60 : for (int i = 0; i < ehdr->e_shnum; i++) {
108 52 : if (shdrs[i].sh_type == SHT_SYMTAB) {
109 : symtab = &shdrs[i];
110 8 : if (shdrs[i].sh_link < ehdr->e_shnum)
111 8 : symstrtab = &shdrs[shdrs[i].sh_link];
112 44 : } else if (bf_streq(&strtab[shdrs[i].sh_name], ".rodata")) {
113 : rodata_shdr = &shdrs[i];
114 : }
115 : }
116 :
117 8 : if (!symtab || !symstrtab)
118 0 : return bf_err_r(-ENOENT, "symbol table not found");
119 :
120 8 : symbols = (Elf64_Sym *)(raw->elf + symtab->sh_offset);
121 8 : sym_strtab = (char *)(raw->elf + symstrtab->sh_offset);
122 8 : sym_count = symtab->sh_size / sizeof(Elf64_Sym);
123 :
124 : // Process REL relocation sections
125 60 : for (int i = 0; i < ehdr->e_shnum; i++) {
126 52 : if (shdrs[i].sh_type != SHT_REL)
127 44 : continue;
128 :
129 : // Check if this relocation section applies to .text
130 8 : if (shdrs[i].sh_info != 0) {
131 : // sh_info contains the section index this relocation applies to
132 8 : if (!bf_streq(&strtab[shdrs[shdrs[i].sh_info].sh_name], ".text"))
133 0 : continue;
134 : }
135 :
136 : // REL relocations (no addend)
137 8 : Elf64_Rel *rels = (Elf64_Rel *)(raw->elf + shdrs[i].sh_offset);
138 8 : size_t rel_count = shdrs[i].sh_size / sizeof(Elf64_Rel);
139 :
140 16 : for (size_t j = 0; j < rel_count; j++) {
141 8 : uint32_t type = ELF64_R_TYPE(rels[j].r_info);
142 8 : uint32_t sym_idx = ELF64_R_SYM(rels[j].r_info);
143 :
144 8 : if (type == R_BPF_64_32 && sym_idx < sym_count) {
145 4 : uint32_t name_idx = symbols[sym_idx].st_name;
146 4 : if (name_idx < symstrtab->sh_size) {
147 4 : const char *name = &sym_strtab[name_idx];
148 4 : int id = bf_btf_get_id(name);
149 4 : if (id < 0)
150 0 : return bf_err_r(id, "function %s not found", name);
151 :
152 4 : size_t idx = rels[j].r_offset / 8;
153 4 : stub->insns[idx] =
154 : ((struct bpf_insn) {.code = BPF_JMP | BPF_CALL,
155 : .dst_reg = 0,
156 : .src_reg = BPF_PSEUDO_KFUNC_CALL,
157 : .off = 0,
158 : .imm = id});
159 :
160 4 : bf_dbg("updated stub to call '%s' from instruction %lu",
161 : name, idx);
162 : }
163 4 : } else if (type == R_BPF_64_64 && rodata_shdr) {
164 0 : _free_bf_printk_str_ struct bf_printk_str *pstr = NULL;
165 4 : size_t insn_idx = rels[j].r_offset / 8;
166 4 : size_t str_offset = stub->insns[insn_idx].imm;
167 4 : const char *str =
168 4 : raw->elf + rodata_shdr->sh_offset + str_offset;
169 :
170 4 : r = _bf_printk_str_new(&pstr, insn_idx, str);
171 4 : if (r)
172 0 : return bf_err_r(r, "failed to create printk_str");
173 :
174 4 : r = bf_list_add_tail(&stub->strs, pstr);
175 4 : if (r)
176 0 : return bf_err_r(r, "failed to add printk_str to elfstub");
177 :
178 4 : TAKE_PTR(pstr);
179 : }
180 : }
181 : }
182 :
183 : return 0;
184 : }
185 :
186 8 : int bf_elfstub_new(struct bf_elfstub **stub, enum bf_elfstub_id id)
187 : {
188 8 : _free_bf_elfstub_ struct bf_elfstub *_stub = NULL;
189 : int r;
190 :
191 8 : bf_assert(stub);
192 :
193 8 : _stub = calloc(1, sizeof(*_stub));
194 8 : if (!_stub)
195 : return -ENOMEM;
196 :
197 8 : _stub->strs = bf_list_default(_bf_printk_str_free, NULL);
198 :
199 8 : r = _bf_elfstub_prepare(_stub, &_bf_rawstubs[id]);
200 8 : if (r)
201 : return r;
202 :
203 8 : *stub = TAKE_PTR(_stub);
204 :
205 8 : return 0;
206 : }
207 :
208 16 : void bf_elfstub_free(struct bf_elfstub **stub)
209 : {
210 16 : bf_assert(stub);
211 :
212 16 : if (!*stub)
213 : return;
214 :
215 8 : bf_list_clean(&(*stub)->strs);
216 8 : freep((void *)&(*stub)->insns);
217 : freep((void *)stub);
218 : }
|