1/*
2 * Post-process a vdso elf image for inclusion into qemu.
3 * Elf size specialization.
4 *
5 * Copyright 2023 Linaro, Ltd.
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10static void elfN(bswap_ehdr)(ElfN(Ehdr) *ehdr)
11{
12    bswaps(&ehdr->e_type);            /* Object file type */
13    bswaps(&ehdr->e_machine);         /* Architecture */
14    bswaps(&ehdr->e_version);         /* Object file version */
15    bswaps(&ehdr->e_entry);           /* Entry point virtual address */
16    bswaps(&ehdr->e_phoff);           /* Program header table file offset */
17    bswaps(&ehdr->e_shoff);           /* Section header table file offset */
18    bswaps(&ehdr->e_flags);           /* Processor-specific flags */
19    bswaps(&ehdr->e_ehsize);          /* ELF header size in bytes */
20    bswaps(&ehdr->e_phentsize);       /* Program header table entry size */
21    bswaps(&ehdr->e_phnum);           /* Program header table entry count */
22    bswaps(&ehdr->e_shentsize);       /* Section header table entry size */
23    bswaps(&ehdr->e_shnum);           /* Section header table entry count */
24    bswaps(&ehdr->e_shstrndx);        /* Section header string table index */
25}
26
27static void elfN(bswap_phdr)(ElfN(Phdr) *phdr)
28{
29    bswaps(&phdr->p_type);            /* Segment type */
30    bswaps(&phdr->p_flags);           /* Segment flags */
31    bswaps(&phdr->p_offset);          /* Segment file offset */
32    bswaps(&phdr->p_vaddr);           /* Segment virtual address */
33    bswaps(&phdr->p_paddr);           /* Segment physical address */
34    bswaps(&phdr->p_filesz);          /* Segment size in file */
35    bswaps(&phdr->p_memsz);           /* Segment size in memory */
36    bswaps(&phdr->p_align);           /* Segment alignment */
37}
38
39static void elfN(bswap_shdr)(ElfN(Shdr) *shdr)
40{
41    bswaps(&shdr->sh_name);
42    bswaps(&shdr->sh_type);
43    bswaps(&shdr->sh_flags);
44    bswaps(&shdr->sh_addr);
45    bswaps(&shdr->sh_offset);
46    bswaps(&shdr->sh_size);
47    bswaps(&shdr->sh_link);
48    bswaps(&shdr->sh_info);
49    bswaps(&shdr->sh_addralign);
50    bswaps(&shdr->sh_entsize);
51}
52
53static void elfN(bswap_sym)(ElfN(Sym) *sym)
54{
55    bswaps(&sym->st_name);
56    bswaps(&sym->st_value);
57    bswaps(&sym->st_size);
58    bswaps(&sym->st_shndx);
59}
60
61static void elfN(bswap_dyn)(ElfN(Dyn) *dyn)
62{
63    bswaps(&dyn->d_tag);              /* Dynamic type tag */
64    bswaps(&dyn->d_un.d_ptr);         /* Dynamic ptr or val, in union */
65}
66
67static void elfN(search_symtab)(ElfN(Shdr) *shdr, unsigned sym_idx,
68                                void *buf, bool need_bswap)
69{
70    unsigned str_idx = shdr[sym_idx].sh_link;
71    ElfN(Sym) *target_sym = buf + shdr[sym_idx].sh_offset;
72    unsigned sym_n = shdr[sym_idx].sh_size / sizeof(*target_sym);
73    const char *str = buf + shdr[str_idx].sh_offset;
74
75    for (unsigned i = 0; i < sym_n; ++i) {
76        const char *name;
77        ElfN(Sym) sym;
78
79        memcpy(&sym, &target_sym[i], sizeof(sym));
80        if (need_bswap) {
81            elfN(bswap_sym)(&sym);
82        }
83        name = str + sym.st_name;
84
85        if (sigreturn_sym && strcmp(sigreturn_sym, name) == 0) {
86            sigreturn_addr = sym.st_value;
87        }
88        if (rt_sigreturn_sym && strcmp(rt_sigreturn_sym, name) == 0) {
89            rt_sigreturn_addr = sym.st_value;
90        }
91    }
92}
93
94static void elfN(bswap_ps_hdrs)(ElfN(Ehdr) *ehdr)
95{
96    ElfN(Phdr) *phdr = (void *)ehdr + ehdr->e_phoff;
97    ElfN(Shdr) *shdr = (void *)ehdr + ehdr->e_shoff;
98    ElfN(Half) i;
99
100    for (i = 0; i < ehdr->e_phnum; ++i) {
101        elfN(bswap_phdr)(&phdr[i]);
102    }
103
104    for (i = 0; i < ehdr->e_shnum; ++i) {
105        elfN(bswap_shdr)(&shdr[i]);
106    }
107}
108
109static void elfN(process)(FILE *outf, void *buf, long len, bool need_bswap)
110{
111    ElfN(Ehdr) *ehdr = buf;
112    ElfN(Phdr) *phdr;
113    ElfN(Shdr) *shdr;
114    unsigned phnum, shnum;
115    unsigned dynamic_ofs = 0;
116    unsigned dynamic_addr = 0;
117    unsigned symtab_idx = 0;
118    unsigned dynsym_idx = 0;
119    unsigned first_segsz = 0;
120    int errors = 0;
121
122    if (need_bswap) {
123        elfN(bswap_ehdr)(buf);
124        elfN(bswap_ps_hdrs)(buf);
125    }
126
127    phnum = ehdr->e_phnum;
128    phdr = buf + ehdr->e_phoff;
129    shnum = ehdr->e_shnum;
130    shdr = buf + ehdr->e_shoff;
131    for (unsigned i = 0; i < shnum; ++i) {
132        switch (shdr[i].sh_type) {
133        case SHT_SYMTAB:
134            symtab_idx = i;
135            break;
136        case SHT_DYNSYM:
137            dynsym_idx = i;
138            break;
139        }
140    }
141
142    /*
143     * Validate the VDSO is created as we expect: that PT_PHDR,
144     * PT_DYNAMIC, and PT_NOTE located in a writable data segment.
145     * PHDR and DYNAMIC require relocation, and NOTE will get the
146     * linux version number.
147     */
148    for (unsigned i = 0; i < phnum; ++i) {
149        if (phdr[i].p_type != PT_LOAD) {
150            continue;
151        }
152        if (first_segsz != 0) {
153            fprintf(stderr, "Multiple LOAD segments\n");
154            errors++;
155        }
156        if (phdr[i].p_offset != 0) {
157            fprintf(stderr, "LOAD segment does not cover EHDR\n");
158            errors++;
159        }
160        if (phdr[i].p_vaddr != 0) {
161            fprintf(stderr, "LOAD segment not loaded at address 0\n");
162            errors++;
163        }
164        /*
165         * Extend the program header to cover the entire VDSO, so that
166         * load_elf_vdso() loads everything, including section headers.
167         *
168         * Require that there is no .bss, since it would break this
169         * approach.
170         */
171        if (phdr[i].p_filesz != phdr[i].p_memsz) {
172            fprintf(stderr, "LOAD segment's filesz and memsz differ\n");
173            errors++;
174        }
175        if (phdr[i].p_filesz > len) {
176            fprintf(stderr, "LOAD segment is larger than the whole VDSO\n");
177            errors++;
178        }
179        phdr[i].p_filesz = len;
180        phdr[i].p_memsz = len;
181        first_segsz = len;
182        if (first_segsz < ehdr->e_phoff + phnum * sizeof(*phdr)) {
183            fprintf(stderr, "LOAD segment does not cover PHDRs\n");
184            errors++;
185        }
186        if ((phdr[i].p_flags & (PF_R | PF_W)) != (PF_R | PF_W)) {
187            fprintf(stderr, "LOAD segment is not read-write\n");
188            errors++;
189        }
190    }
191    for (unsigned i = 0; i < phnum; ++i) {
192        const char *which;
193
194        switch (phdr[i].p_type) {
195        case PT_PHDR:
196            which = "PT_PHDR";
197            break;
198        case PT_NOTE:
199            which = "PT_NOTE";
200            break;
201        case PT_DYNAMIC:
202            dynamic_ofs = phdr[i].p_offset;
203            dynamic_addr = phdr[i].p_vaddr;
204            which = "PT_DYNAMIC";
205            break;
206        default:
207            continue;
208        }
209        if (first_segsz < phdr[i].p_vaddr + phdr[i].p_filesz) {
210            fprintf(stderr, "LOAD segment does not cover %s\n", which);
211            errors++;
212        }
213    }
214    if (errors) {
215        exit(EXIT_FAILURE);
216    }
217
218    /* Relocate the program headers. */
219    for (unsigned i = 0; i < phnum; ++i) {
220        output_reloc(outf, buf, &phdr[i].p_vaddr);
221        output_reloc(outf, buf, &phdr[i].p_paddr);
222    }
223
224    /* Relocate the section headers. */
225    for (unsigned i = 0; i < shnum; ++i) {
226        output_reloc(outf, buf, &shdr[i].sh_addr);
227    }
228
229    /* Relocate the DYNAMIC entries. */
230    if (dynamic_addr) {
231        ElfN(Dyn) *target_dyn = buf + dynamic_ofs;
232        __typeof(((ElfN(Dyn) *)target_dyn)->d_tag) tag;
233
234        do {
235            ElfN(Dyn) dyn;
236
237            memcpy(&dyn, target_dyn, sizeof(dyn));
238            if (need_bswap) {
239                elfN(bswap_dyn)(&dyn);
240            }
241            tag = dyn.d_tag;
242
243            switch (tag) {
244            case DT_HASH:
245            case DT_SYMTAB:
246            case DT_STRTAB:
247            case DT_VERDEF:
248            case DT_VERSYM:
249            case DT_PLTGOT:
250            case DT_ADDRRNGLO ... DT_ADDRRNGHI:
251                /* These entries store an address in the entry. */
252                output_reloc(outf, buf, &target_dyn->d_un.d_val);
253                break;
254
255            case DT_NULL:
256            case DT_STRSZ:
257            case DT_SONAME:
258            case DT_DEBUG:
259            case DT_FLAGS:
260            case DT_FLAGS_1:
261            case DT_SYMBOLIC:
262            case DT_BIND_NOW:
263            case DT_VERDEFNUM:
264            case DT_VALRNGLO ... DT_VALRNGHI:
265                /* These entries store an integer in the entry. */
266                break;
267
268            case DT_SYMENT:
269                if (dyn.d_un.d_val != sizeof(ElfN(Sym))) {
270                    fprintf(stderr, "VDSO has incorrect dynamic symbol size\n");
271                    errors++;
272                }
273                break;
274
275            case DT_REL:
276            case DT_RELSZ:
277            case DT_RELA:
278            case DT_RELASZ:
279                /*
280                 * These entries indicate that the VDSO was built incorrectly.
281                 * It should not have any real relocations.
282                 * ??? The RISC-V toolchain will emit these even when there
283                 * are no relocations.  Validate zeros.
284                 */
285                if (dyn.d_un.d_val != 0) {
286                    fprintf(stderr, "VDSO has dynamic relocations\n");
287                    errors++;
288                }
289                break;
290            case DT_RELENT:
291            case DT_RELAENT:
292            case DT_TEXTREL:
293                /* These entries store an integer in the entry. */
294                /* Should not be required; see above. */
295                break;
296
297            case DT_NEEDED:
298            case DT_VERNEED:
299            case DT_PLTREL:
300            case DT_JMPREL:
301            case DT_RPATH:
302            case DT_RUNPATH:
303                fprintf(stderr, "VDSO has external dependencies\n");
304                errors++;
305                break;
306
307            case PT_LOPROC + 3:
308                if (ehdr->e_machine == EM_PPC64) {
309                    break;  /* DT_PPC64_OPT: integer bitmask */
310                }
311                goto do_default;
312
313            default:
314            do_default:
315                /* This is probably something target specific. */
316                fprintf(stderr, "VDSO has unknown DYNAMIC entry (%lx)\n",
317                        (unsigned long)tag);
318                errors++;
319                break;
320            }
321            target_dyn++;
322        } while (tag != DT_NULL);
323        if (errors) {
324            exit(EXIT_FAILURE);
325        }
326    }
327
328    /* Relocate the dynamic symbol table. */
329    if (dynsym_idx) {
330        ElfN(Sym) *target_sym = buf + shdr[dynsym_idx].sh_offset;
331        unsigned sym_n = shdr[dynsym_idx].sh_size / sizeof(*target_sym);
332
333        for (unsigned i = 0; i < sym_n; ++i) {
334            output_reloc(outf, buf, &target_sym[i].st_value);
335        }
336    }
337
338    /* Search both dynsym and symtab for the signal return symbols. */
339    if (dynsym_idx) {
340        elfN(search_symtab)(shdr, dynsym_idx, buf, need_bswap);
341    }
342    if (symtab_idx) {
343        elfN(search_symtab)(shdr, symtab_idx, buf, need_bswap);
344    }
345
346    if (need_bswap) {
347        elfN(bswap_ps_hdrs)(buf);
348        elfN(bswap_ehdr)(buf);
349    }
350}
351