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) *sym = buf + shdr[sym_idx].sh_offset;
72    unsigned sym_n = shdr[sym_idx].sh_size / sizeof(*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
78        if (need_bswap) {
79            elfN(bswap_sym)(sym + i);
80        }
81        name = str + sym[i].st_name;
82
83        if (sigreturn_sym && strcmp(sigreturn_sym, name) == 0) {
84            sigreturn_addr = sym[i].st_value;
85        }
86        if (rt_sigreturn_sym && strcmp(rt_sigreturn_sym, name) == 0) {
87            rt_sigreturn_addr = sym[i].st_value;
88        }
89    }
90}
91
92static void elfN(process)(FILE *outf, void *buf, bool need_bswap)
93{
94    ElfN(Ehdr) *ehdr = buf;
95    ElfN(Phdr) *phdr;
96    ElfN(Shdr) *shdr;
97    unsigned phnum, shnum;
98    unsigned dynamic_ofs = 0;
99    unsigned dynamic_addr = 0;
100    unsigned symtab_idx = 0;
101    unsigned dynsym_idx = 0;
102    unsigned first_segsz = 0;
103    int errors = 0;
104
105    if (need_bswap) {
106        elfN(bswap_ehdr)(ehdr);
107    }
108
109    phnum = ehdr->e_phnum;
110    phdr = buf + ehdr->e_phoff;
111    if (need_bswap) {
112        for (unsigned i = 0; i < phnum; ++i) {
113            elfN(bswap_phdr)(phdr + i);
114        }
115    }
116
117    shnum = ehdr->e_shnum;
118    shdr = buf + ehdr->e_shoff;
119    if (need_bswap) {
120        for (unsigned i = 0; i < shnum; ++i) {
121            elfN(bswap_shdr)(shdr + i);
122        }
123    }
124    for (unsigned i = 0; i < shnum; ++i) {
125        switch (shdr[i].sh_type) {
126        case SHT_SYMTAB:
127            symtab_idx = i;
128            break;
129        case SHT_DYNSYM:
130            dynsym_idx = i;
131            break;
132        }
133    }
134
135    /*
136     * Validate the VDSO is created as we expect: that PT_PHDR,
137     * PT_DYNAMIC, and PT_NOTE located in a writable data segment.
138     * PHDR and DYNAMIC require relocation, and NOTE will get the
139     * linux version number.
140     */
141    for (unsigned i = 0; i < phnum; ++i) {
142        if (phdr[i].p_type != PT_LOAD) {
143            continue;
144        }
145        if (first_segsz != 0) {
146            fprintf(stderr, "Multiple LOAD segments\n");
147            errors++;
148        }
149        if (phdr[i].p_offset != 0) {
150            fprintf(stderr, "LOAD segment does not cover EHDR\n");
151            errors++;
152        }
153        if (phdr[i].p_vaddr != 0) {
154            fprintf(stderr, "LOAD segment not loaded at address 0\n");
155            errors++;
156        }
157        first_segsz = phdr[i].p_filesz;
158        if (first_segsz < ehdr->e_phoff + phnum * sizeof(*phdr)) {
159            fprintf(stderr, "LOAD segment does not cover PHDRs\n");
160            errors++;
161        }
162        if ((phdr[i].p_flags & (PF_R | PF_W)) != (PF_R | PF_W)) {
163            fprintf(stderr, "LOAD segment is not read-write\n");
164            errors++;
165        }
166    }
167    for (unsigned i = 0; i < phnum; ++i) {
168        const char *which;
169
170        switch (phdr[i].p_type) {
171        case PT_PHDR:
172            which = "PT_PHDR";
173            break;
174        case PT_NOTE:
175            which = "PT_NOTE";
176            break;
177        case PT_DYNAMIC:
178            dynamic_ofs = phdr[i].p_offset;
179            dynamic_addr = phdr[i].p_vaddr;
180            which = "PT_DYNAMIC";
181            break;
182        default:
183            continue;
184        }
185        if (first_segsz < phdr[i].p_vaddr + phdr[i].p_filesz) {
186            fprintf(stderr, "LOAD segment does not cover %s\n", which);
187            errors++;
188        }
189    }
190    if (errors) {
191        exit(EXIT_FAILURE);
192    }
193
194    /* Relocate the program headers. */
195    for (unsigned i = 0; i < phnum; ++i) {
196        output_reloc(outf, buf, &phdr[i].p_vaddr);
197        output_reloc(outf, buf, &phdr[i].p_paddr);
198    }
199
200    /* Relocate the DYNAMIC entries. */
201    if (dynamic_addr) {
202        ElfN(Dyn) *dyn = buf + dynamic_ofs;
203        __typeof(dyn->d_tag) tag;
204
205        do {
206
207            if (need_bswap) {
208                elfN(bswap_dyn)(dyn);
209            }
210            tag = dyn->d_tag;
211
212            switch (tag) {
213            case DT_HASH:
214            case DT_SYMTAB:
215            case DT_STRTAB:
216            case DT_VERDEF:
217            case DT_VERSYM:
218            case DT_PLTGOT:
219            case DT_ADDRRNGLO ... DT_ADDRRNGHI:
220                /* These entries store an address in the entry. */
221                output_reloc(outf, buf, &dyn->d_un.d_val);
222                break;
223
224            case DT_NULL:
225            case DT_STRSZ:
226            case DT_SONAME:
227            case DT_DEBUG:
228            case DT_FLAGS:
229            case DT_FLAGS_1:
230            case DT_SYMBOLIC:
231            case DT_BIND_NOW:
232            case DT_VERDEFNUM:
233            case DT_VALRNGLO ... DT_VALRNGHI:
234                /* These entries store an integer in the entry. */
235                break;
236
237            case DT_SYMENT:
238                if (dyn->d_un.d_val != sizeof(ElfN(Sym))) {
239                    fprintf(stderr, "VDSO has incorrect dynamic symbol size\n");
240                    errors++;
241                }
242                break;
243
244            case DT_REL:
245            case DT_RELSZ:
246            case DT_RELA:
247            case DT_RELASZ:
248                /*
249                 * These entries indicate that the VDSO was built incorrectly.
250                 * It should not have any real relocations.
251                 * ??? The RISC-V toolchain will emit these even when there
252                 * are no relocations.  Validate zeros.
253                 */
254                if (dyn->d_un.d_val != 0) {
255                    fprintf(stderr, "VDSO has dynamic relocations\n");
256                    errors++;
257                }
258                break;
259            case DT_RELENT:
260            case DT_RELAENT:
261            case DT_TEXTREL:
262                /* These entries store an integer in the entry. */
263                /* Should not be required; see above. */
264                break;
265
266            case DT_NEEDED:
267            case DT_VERNEED:
268            case DT_PLTREL:
269            case DT_JMPREL:
270            case DT_RPATH:
271            case DT_RUNPATH:
272                fprintf(stderr, "VDSO has external dependencies\n");
273                errors++;
274                break;
275
276            case PT_LOPROC + 3:
277                if (ehdr->e_machine == EM_PPC64) {
278                    break;  /* DT_PPC64_OPT: integer bitmask */
279                }
280                goto do_default;
281
282            default:
283            do_default:
284                /* This is probably something target specific. */
285                fprintf(stderr, "VDSO has unknown DYNAMIC entry (%lx)\n",
286                        (unsigned long)tag);
287                errors++;
288                break;
289            }
290            dyn++;
291        } while (tag != DT_NULL);
292        if (errors) {
293            exit(EXIT_FAILURE);
294        }
295    }
296
297    /* Relocate the dynamic symbol table. */
298    if (dynsym_idx) {
299        ElfN(Sym) *sym = buf + shdr[dynsym_idx].sh_offset;
300        unsigned sym_n = shdr[dynsym_idx].sh_size / sizeof(*sym);
301
302        for (unsigned i = 0; i < sym_n; ++i) {
303            output_reloc(outf, buf, &sym[i].st_value);
304        }
305    }
306
307    /* Search both dynsym and symtab for the signal return symbols. */
308    if (dynsym_idx) {
309        elfN(search_symtab)(shdr, dynsym_idx, buf, need_bswap);
310    }
311    if (symtab_idx) {
312        elfN(search_symtab)(shdr, symtab_idx, buf, need_bswap);
313    }
314}
315