Merge tag 'v5.9' into next
[linux-2.6-microblaze.git] / tools / objtool / special.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
4  */
5
6 /*
7  * This file reads all the special sections which have alternate instructions
8  * which can be patched in or redirected to at runtime.
9  */
10
11 #include <stdlib.h>
12 #include <string.h>
13
14 #include "builtin.h"
15 #include "special.h"
16 #include "warn.h"
17
18 #define EX_ENTRY_SIZE           12
19 #define EX_ORIG_OFFSET          0
20 #define EX_NEW_OFFSET           4
21
22 #define JUMP_ENTRY_SIZE         16
23 #define JUMP_ORIG_OFFSET        0
24 #define JUMP_NEW_OFFSET         4
25
26 #define ALT_ENTRY_SIZE          13
27 #define ALT_ORIG_OFFSET         0
28 #define ALT_NEW_OFFSET          4
29 #define ALT_FEATURE_OFFSET      8
30 #define ALT_ORIG_LEN_OFFSET     10
31 #define ALT_NEW_LEN_OFFSET      11
32
33 #define X86_FEATURE_POPCNT (4*32+23)
34 #define X86_FEATURE_SMAP   (9*32+20)
35
36 struct special_entry {
37         const char *sec;
38         bool group, jump_or_nop;
39         unsigned char size, orig, new;
40         unsigned char orig_len, new_len; /* group only */
41         unsigned char feature; /* ALTERNATIVE macro CPU feature */
42 };
43
44 struct special_entry entries[] = {
45         {
46                 .sec = ".altinstructions",
47                 .group = true,
48                 .size = ALT_ENTRY_SIZE,
49                 .orig = ALT_ORIG_OFFSET,
50                 .orig_len = ALT_ORIG_LEN_OFFSET,
51                 .new = ALT_NEW_OFFSET,
52                 .new_len = ALT_NEW_LEN_OFFSET,
53                 .feature = ALT_FEATURE_OFFSET,
54         },
55         {
56                 .sec = "__jump_table",
57                 .jump_or_nop = true,
58                 .size = JUMP_ENTRY_SIZE,
59                 .orig = JUMP_ORIG_OFFSET,
60                 .new = JUMP_NEW_OFFSET,
61         },
62         {
63                 .sec = "__ex_table",
64                 .size = EX_ENTRY_SIZE,
65                 .orig = EX_ORIG_OFFSET,
66                 .new = EX_NEW_OFFSET,
67         },
68         {},
69 };
70
71 static int get_alt_entry(struct elf *elf, struct special_entry *entry,
72                          struct section *sec, int idx,
73                          struct special_alt *alt)
74 {
75         struct reloc *orig_reloc, *new_reloc;
76         unsigned long offset;
77
78         offset = idx * entry->size;
79
80         alt->group = entry->group;
81         alt->jump_or_nop = entry->jump_or_nop;
82
83         if (alt->group) {
84                 alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset +
85                                                    entry->orig_len);
86                 alt->new_len = *(unsigned char *)(sec->data->d_buf + offset +
87                                                   entry->new_len);
88         }
89
90         if (entry->feature) {
91                 unsigned short feature;
92
93                 feature = *(unsigned short *)(sec->data->d_buf + offset +
94                                               entry->feature);
95
96                 /*
97                  * It has been requested that we don't validate the !POPCNT
98                  * feature path which is a "very very small percentage of
99                  * machines".
100                  */
101                 if (feature == X86_FEATURE_POPCNT)
102                         alt->skip_orig = true;
103
104                 /*
105                  * If UACCESS validation is enabled; force that alternative;
106                  * otherwise force it the other way.
107                  *
108                  * What we want to avoid is having both the original and the
109                  * alternative code flow at the same time, in that case we can
110                  * find paths that see the STAC but take the NOP instead of
111                  * CLAC and the other way around.
112                  */
113                 if (feature == X86_FEATURE_SMAP) {
114                         if (uaccess)
115                                 alt->skip_orig = true;
116                         else
117                                 alt->skip_alt = true;
118                 }
119         }
120
121         orig_reloc = find_reloc_by_dest(elf, sec, offset + entry->orig);
122         if (!orig_reloc) {
123                 WARN_FUNC("can't find orig reloc", sec, offset + entry->orig);
124                 return -1;
125         }
126         if (orig_reloc->sym->type != STT_SECTION) {
127                 WARN_FUNC("don't know how to handle non-section reloc symbol %s",
128                            sec, offset + entry->orig, orig_reloc->sym->name);
129                 return -1;
130         }
131
132         alt->orig_sec = orig_reloc->sym->sec;
133         alt->orig_off = orig_reloc->addend;
134
135         if (!entry->group || alt->new_len) {
136                 new_reloc = find_reloc_by_dest(elf, sec, offset + entry->new);
137                 if (!new_reloc) {
138                         WARN_FUNC("can't find new reloc",
139                                   sec, offset + entry->new);
140                         return -1;
141                 }
142
143                 alt->new_sec = new_reloc->sym->sec;
144                 alt->new_off = (unsigned int)new_reloc->addend;
145
146                 /* _ASM_EXTABLE_EX hack */
147                 if (alt->new_off >= 0x7ffffff0)
148                         alt->new_off -= 0x7ffffff0;
149         }
150
151         return 0;
152 }
153
154 /*
155  * Read all the special sections and create a list of special_alt structs which
156  * describe all the alternate instructions which can be patched in or
157  * redirected to at runtime.
158  */
159 int special_get_alts(struct elf *elf, struct list_head *alts)
160 {
161         struct special_entry *entry;
162         struct section *sec;
163         unsigned int nr_entries;
164         struct special_alt *alt;
165         int idx, ret;
166
167         INIT_LIST_HEAD(alts);
168
169         for (entry = entries; entry->sec; entry++) {
170                 sec = find_section_by_name(elf, entry->sec);
171                 if (!sec)
172                         continue;
173
174                 if (sec->len % entry->size != 0) {
175                         WARN("%s size not a multiple of %d",
176                              sec->name, entry->size);
177                         return -1;
178                 }
179
180                 nr_entries = sec->len / entry->size;
181
182                 for (idx = 0; idx < nr_entries; idx++) {
183                         alt = malloc(sizeof(*alt));
184                         if (!alt) {
185                                 WARN("malloc failed");
186                                 return -1;
187                         }
188                         memset(alt, 0, sizeof(*alt));
189
190                         ret = get_alt_entry(elf, entry, sec, idx, alt);
191                         if (ret)
192                                 return ret;
193
194                         list_add_tail(&alt->list, alts);
195                 }
196         }
197
198         return 0;
199 }