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