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 8 : static int _bf_printk_str_new(struct bf_printk_str **pstr, size_t insn_idx,
25 : const char *str)
26 : {
27 8 : bf_assert(pstr && str);
28 :
29 8 : *pstr = malloc(sizeof(struct bf_printk_str));
30 8 : if (!*pstr)
31 : return -ENOMEM;
32 :
33 8 : (*pstr)->insn_idx = insn_idx;
34 8 : (*pstr)->str = str;
35 :
36 8 : return 0;
37 : }
38 :
39 16 : static void _bf_printk_str_free(struct bf_printk_str **pstr)
40 : {
41 16 : if (!*pstr)
42 : return;
43 :
44 : freep((void *)pstr);
45 : }
46 :
47 16 : static int _bf_elfstub_prepare(struct bf_elfstub *stub,
48 : const struct bf_rawstub *raw)
49 : {
50 16 : 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 16 : 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 16 : 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 16 : 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 16 : 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 16 : if (ehdr->e_shstrndx >= ehdr->e_shnum)
78 0 : return bf_err_r(-EINVAL, "invalid string table index");
79 :
80 16 : shdrs = (Elf64_Shdr *)(raw->elf + ehdr->e_shoff);
81 16 : shstrtab = &shdrs[ehdr->e_shstrndx];
82 16 : if (shstrtab->sh_offset >= raw->len)
83 0 : return bf_err_r(-EINVAL, "invalid string table offset");
84 :
85 16 : strtab = (char *)(raw->elf + shstrtab->sh_offset);
86 :
87 : // Find .text section
88 48 : for (int i = 0; i < ehdr->e_shnum; i++) {
89 48 : if (!bf_streq(&strtab[shdrs[i].sh_name], ".text"))
90 : continue;
91 :
92 16 : if (shdrs[i].sh_offset + shdrs[i].sh_size > raw->len)
93 0 : return bf_err_r(-EINVAL, "invalid .text section");
94 :
95 16 : stub->insns = malloc(shdrs[i].sh_size);
96 16 : if (!stub->insns)
97 : return -ENOMEM;
98 :
99 16 : memcpy(stub->insns, raw->elf + shdrs[i].sh_offset, shdrs[i].sh_size);
100 16 : stub->ninsns = shdrs[i].sh_size / sizeof(struct bpf_insn);
101 16 : break;
102 : }
103 :
104 16 : if (!stub->insns)
105 0 : return bf_err_r(-ENOENT, ".text section not found");
106 :
107 : // Find symbol table and its string table
108 408 : for (int i = 0; i < ehdr->e_shnum; i++) {
109 392 : if (shdrs[i].sh_type == SHT_SYMTAB) {
110 : symtab = &shdrs[i];
111 16 : if (shdrs[i].sh_link < ehdr->e_shnum)
112 16 : symstrtab = &shdrs[shdrs[i].sh_link];
113 376 : } else if (bf_streq(&strtab[shdrs[i].sh_name], ".rodata")) {
114 : rodata_shdr = &shdrs[i];
115 : }
116 : }
117 :
118 16 : if (!symtab || !symstrtab)
119 0 : return bf_err_r(-ENOENT, "symbol table not found");
120 :
121 16 : symbols = (Elf64_Sym *)(raw->elf + symtab->sh_offset);
122 16 : sym_strtab = (char *)(raw->elf + symstrtab->sh_offset);
123 16 : sym_count = symtab->sh_size / sizeof(Elf64_Sym);
124 :
125 : // Process REL relocation sections
126 408 : for (int i = 0; i < ehdr->e_shnum; i++) {
127 392 : if (shdrs[i].sh_type != SHT_REL)
128 272 : continue;
129 :
130 : // Check if this relocation section applies to .text
131 120 : if (shdrs[i].sh_info != 0) {
132 : // sh_info contains the section index this relocation applies to
133 120 : if (!bf_streq(&strtab[shdrs[shdrs[i].sh_info].sh_name], ".text"))
134 104 : continue;
135 : }
136 :
137 : // REL relocations (no addend)
138 16 : Elf64_Rel *rels = (Elf64_Rel *)(raw->elf + shdrs[i].sh_offset);
139 16 : size_t rel_count = shdrs[i].sh_size / sizeof(Elf64_Rel);
140 :
141 32 : for (size_t j = 0; j < rel_count; j++) {
142 16 : uint32_t type = ELF64_R_TYPE(rels[j].r_info);
143 16 : uint32_t sym_idx = ELF64_R_SYM(rels[j].r_info);
144 :
145 16 : if (type == R_BPF_64_32 && sym_idx < sym_count) {
146 8 : uint32_t name_idx = symbols[sym_idx].st_name;
147 8 : if (name_idx < symstrtab->sh_size) {
148 8 : const char *name = &sym_strtab[name_idx];
149 8 : int id = bf_btf_get_id(name);
150 8 : if (id < 0)
151 0 : return bf_err_r(id, "function %s not found", name);
152 :
153 8 : size_t idx = rels[j].r_offset / 8;
154 8 : 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 8 : bf_dbg("updated stub to call '%s' from instruction %lu",
162 : name, idx);
163 : }
164 8 : } else if (type == R_BPF_64_64 && rodata_shdr) {
165 0 : _free_bf_printk_str_ struct bf_printk_str *pstr = NULL;
166 8 : size_t insn_idx = rels[j].r_offset / 8;
167 8 : size_t str_offset = stub->insns[insn_idx].imm;
168 8 : const char *str =
169 8 : raw->elf + rodata_shdr->sh_offset + str_offset;
170 :
171 8 : r = _bf_printk_str_new(&pstr, insn_idx, str);
172 8 : if (r)
173 0 : return bf_err_r(r, "failed to create printk_str");
174 :
175 8 : r = bf_list_add_tail(&stub->strs, pstr);
176 8 : if (r)
177 0 : return bf_err_r(r, "failed to add printk_str to elfstub");
178 :
179 8 : TAKE_PTR(pstr);
180 : }
181 : }
182 : }
183 :
184 : return 0;
185 : }
186 :
187 16 : int bf_elfstub_new(struct bf_elfstub **stub, enum bf_elfstub_id id)
188 : {
189 16 : _free_bf_elfstub_ struct bf_elfstub *_stub = NULL;
190 : int r;
191 :
192 16 : bf_assert(stub);
193 :
194 16 : _stub = calloc(1, sizeof(*_stub));
195 16 : if (!_stub)
196 : return -ENOMEM;
197 :
198 16 : _stub->strs = bf_list_default(_bf_printk_str_free, NULL);
199 :
200 16 : r = _bf_elfstub_prepare(_stub, &_bf_rawstubs[id]);
201 16 : if (r)
202 : return r;
203 :
204 16 : *stub = TAKE_PTR(_stub);
205 :
206 16 : return 0;
207 : }
208 :
209 32 : void bf_elfstub_free(struct bf_elfstub **stub)
210 : {
211 32 : bf_assert(stub);
212 :
213 32 : if (!*stub)
214 : return;
215 :
216 16 : bf_list_clean(&(*stub)->strs);
217 16 : freep((void *)&(*stub)->insns);
218 : freep((void *)stub);
219 : }
|