diff options
Diffstat (limited to 'kernel/livepatch/core.c')
-rw-r--r-- | kernel/livepatch/core.c | 176 |
1 files changed, 66 insertions, 110 deletions
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index db545cbcdb89..bc2c85c064c1 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include <linux/list.h> | 28 | #include <linux/list.h> |
29 | #include <linux/kallsyms.h> | 29 | #include <linux/kallsyms.h> |
30 | #include <linux/livepatch.h> | 30 | #include <linux/livepatch.h> |
31 | #include <asm/cacheflush.h> | ||
31 | 32 | ||
32 | /** | 33 | /** |
33 | * struct klp_ops - structure for tracking registered ftrace ops structs | 34 | * struct klp_ops - structure for tracking registered ftrace ops structs |
@@ -135,13 +136,8 @@ struct klp_find_arg { | |||
135 | const char *objname; | 136 | const char *objname; |
136 | const char *name; | 137 | const char *name; |
137 | unsigned long addr; | 138 | unsigned long addr; |
138 | /* | ||
139 | * If count == 0, the symbol was not found. If count == 1, a unique | ||
140 | * match was found and addr is set. If count > 1, there is | ||
141 | * unresolvable ambiguity among "count" number of symbols with the same | ||
142 | * name in the same object. | ||
143 | */ | ||
144 | unsigned long count; | 139 | unsigned long count; |
140 | unsigned long pos; | ||
145 | }; | 141 | }; |
146 | 142 | ||
147 | static int klp_find_callback(void *data, const char *name, | 143 | static int klp_find_callback(void *data, const char *name, |
@@ -158,37 +154,48 @@ static int klp_find_callback(void *data, const char *name, | |||
158 | if (args->objname && strcmp(args->objname, mod->name)) | 154 | if (args->objname && strcmp(args->objname, mod->name)) |
159 | return 0; | 155 | return 0; |
160 | 156 | ||
161 | /* | ||
162 | * args->addr might be overwritten if another match is found | ||
163 | * but klp_find_object_symbol() handles this and only returns the | ||
164 | * addr if count == 1. | ||
165 | */ | ||
166 | args->addr = addr; | 157 | args->addr = addr; |
167 | args->count++; | 158 | args->count++; |
168 | 159 | ||
160 | /* | ||
161 | * Finish the search when the symbol is found for the desired position | ||
162 | * or the position is not defined for a non-unique symbol. | ||
163 | */ | ||
164 | if ((args->pos && (args->count == args->pos)) || | ||
165 | (!args->pos && (args->count > 1))) | ||
166 | return 1; | ||
167 | |||
169 | return 0; | 168 | return 0; |
170 | } | 169 | } |
171 | 170 | ||
172 | static int klp_find_object_symbol(const char *objname, const char *name, | 171 | static int klp_find_object_symbol(const char *objname, const char *name, |
173 | unsigned long *addr) | 172 | unsigned long sympos, unsigned long *addr) |
174 | { | 173 | { |
175 | struct klp_find_arg args = { | 174 | struct klp_find_arg args = { |
176 | .objname = objname, | 175 | .objname = objname, |
177 | .name = name, | 176 | .name = name, |
178 | .addr = 0, | 177 | .addr = 0, |
179 | .count = 0 | 178 | .count = 0, |
179 | .pos = sympos, | ||
180 | }; | 180 | }; |
181 | 181 | ||
182 | mutex_lock(&module_mutex); | 182 | mutex_lock(&module_mutex); |
183 | kallsyms_on_each_symbol(klp_find_callback, &args); | 183 | kallsyms_on_each_symbol(klp_find_callback, &args); |
184 | mutex_unlock(&module_mutex); | 184 | mutex_unlock(&module_mutex); |
185 | 185 | ||
186 | if (args.count == 0) | 186 | /* |
187 | * Ensure an address was found. If sympos is 0, ensure symbol is unique; | ||
188 | * otherwise ensure the symbol position count matches sympos. | ||
189 | */ | ||
190 | if (args.addr == 0) | ||
187 | pr_err("symbol '%s' not found in symbol table\n", name); | 191 | pr_err("symbol '%s' not found in symbol table\n", name); |
188 | else if (args.count > 1) | 192 | else if (args.count > 1 && sympos == 0) { |
189 | pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n", | 193 | pr_err("unresolvable ambiguity (%lu matches) on symbol '%s' in object '%s'\n", |
190 | args.count, name, objname); | 194 | args.count, name, objname); |
191 | else { | 195 | } else if (sympos != args.count && sympos > 0) { |
196 | pr_err("symbol position %lu for symbol '%s' in object '%s' not found\n", | ||
197 | sympos, name, objname ? objname : "vmlinux"); | ||
198 | } else { | ||
192 | *addr = args.addr; | 199 | *addr = args.addr; |
193 | return 0; | 200 | return 0; |
194 | } | 201 | } |
@@ -197,66 +204,6 @@ static int klp_find_object_symbol(const char *objname, const char *name, | |||
197 | return -EINVAL; | 204 | return -EINVAL; |
198 | } | 205 | } |
199 | 206 | ||
200 | struct klp_verify_args { | ||
201 | const char *name; | ||
202 | const unsigned long addr; | ||
203 | }; | ||
204 | |||
205 | static int klp_verify_callback(void *data, const char *name, | ||
206 | struct module *mod, unsigned long addr) | ||
207 | { | ||
208 | struct klp_verify_args *args = data; | ||
209 | |||
210 | if (!mod && | ||
211 | !strcmp(args->name, name) && | ||
212 | args->addr == addr) | ||
213 | return 1; | ||
214 | |||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static int klp_verify_vmlinux_symbol(const char *name, unsigned long addr) | ||
219 | { | ||
220 | struct klp_verify_args args = { | ||
221 | .name = name, | ||
222 | .addr = addr, | ||
223 | }; | ||
224 | int ret; | ||
225 | |||
226 | mutex_lock(&module_mutex); | ||
227 | ret = kallsyms_on_each_symbol(klp_verify_callback, &args); | ||
228 | mutex_unlock(&module_mutex); | ||
229 | |||
230 | if (!ret) { | ||
231 | pr_err("symbol '%s' not found at specified address 0x%016lx, kernel mismatch?\n", | ||
232 | name, addr); | ||
233 | return -EINVAL; | ||
234 | } | ||
235 | |||
236 | return 0; | ||
237 | } | ||
238 | |||
239 | static int klp_find_verify_func_addr(struct klp_object *obj, | ||
240 | struct klp_func *func) | ||
241 | { | ||
242 | int ret; | ||
243 | |||
244 | #if defined(CONFIG_RANDOMIZE_BASE) | ||
245 | /* If KASLR has been enabled, adjust old_addr accordingly */ | ||
246 | if (kaslr_enabled() && func->old_addr) | ||
247 | func->old_addr += kaslr_offset(); | ||
248 | #endif | ||
249 | |||
250 | if (!func->old_addr || klp_is_module(obj)) | ||
251 | ret = klp_find_object_symbol(obj->name, func->old_name, | ||
252 | &func->old_addr); | ||
253 | else | ||
254 | ret = klp_verify_vmlinux_symbol(func->old_name, | ||
255 | func->old_addr); | ||
256 | |||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | /* | 207 | /* |
261 | * external symbols are located outside the parent object (where the parent | 208 | * external symbols are located outside the parent object (where the parent |
262 | * object is either vmlinux or the kmod being patched). | 209 | * object is either vmlinux or the kmod being patched). |
@@ -276,14 +223,18 @@ static int klp_find_external_symbol(struct module *pmod, const char *name, | |||
276 | } | 223 | } |
277 | preempt_enable(); | 224 | preempt_enable(); |
278 | 225 | ||
279 | /* otherwise check if it's in another .o within the patch module */ | 226 | /* |
280 | return klp_find_object_symbol(pmod->name, name, addr); | 227 | * Check if it's in another .o within the patch module. This also |
228 | * checks that the external symbol is unique. | ||
229 | */ | ||
230 | return klp_find_object_symbol(pmod->name, name, 0, addr); | ||
281 | } | 231 | } |
282 | 232 | ||
283 | static int klp_write_object_relocations(struct module *pmod, | 233 | static int klp_write_object_relocations(struct module *pmod, |
284 | struct klp_object *obj) | 234 | struct klp_object *obj) |
285 | { | 235 | { |
286 | int ret; | 236 | int ret = 0; |
237 | unsigned long val; | ||
287 | struct klp_reloc *reloc; | 238 | struct klp_reloc *reloc; |
288 | 239 | ||
289 | if (WARN_ON(!klp_is_object_loaded(obj))) | 240 | if (WARN_ON(!klp_is_object_loaded(obj))) |
@@ -292,41 +243,38 @@ static int klp_write_object_relocations(struct module *pmod, | |||
292 | if (WARN_ON(!obj->relocs)) | 243 | if (WARN_ON(!obj->relocs)) |
293 | return -EINVAL; | 244 | return -EINVAL; |
294 | 245 | ||
246 | module_disable_ro(pmod); | ||
247 | |||
295 | for (reloc = obj->relocs; reloc->name; reloc++) { | 248 | for (reloc = obj->relocs; reloc->name; reloc++) { |
296 | if (!klp_is_module(obj)) { | 249 | /* discover the address of the referenced symbol */ |
297 | 250 | if (reloc->external) { | |
298 | #if defined(CONFIG_RANDOMIZE_BASE) | 251 | if (reloc->sympos > 0) { |
299 | /* If KASLR has been enabled, adjust old value accordingly */ | 252 | pr_err("non-zero sympos for external reloc symbol '%s' is not supported\n", |
300 | if (kaslr_enabled()) | 253 | reloc->name); |
301 | reloc->val += kaslr_offset(); | 254 | ret = -EINVAL; |
302 | #endif | 255 | goto out; |
303 | ret = klp_verify_vmlinux_symbol(reloc->name, | 256 | } |
304 | reloc->val); | 257 | ret = klp_find_external_symbol(pmod, reloc->name, &val); |
305 | if (ret) | 258 | } else |
306 | return ret; | 259 | ret = klp_find_object_symbol(obj->name, |
307 | } else { | 260 | reloc->name, |
308 | /* module, reloc->val needs to be discovered */ | 261 | reloc->sympos, |
309 | if (reloc->external) | 262 | &val); |
310 | ret = klp_find_external_symbol(pmod, | 263 | if (ret) |
311 | reloc->name, | 264 | goto out; |
312 | &reloc->val); | 265 | |
313 | else | ||
314 | ret = klp_find_object_symbol(obj->mod->name, | ||
315 | reloc->name, | ||
316 | &reloc->val); | ||
317 | if (ret) | ||
318 | return ret; | ||
319 | } | ||
320 | ret = klp_write_module_reloc(pmod, reloc->type, reloc->loc, | 266 | ret = klp_write_module_reloc(pmod, reloc->type, reloc->loc, |
321 | reloc->val + reloc->addend); | 267 | val + reloc->addend); |
322 | if (ret) { | 268 | if (ret) { |
323 | pr_err("relocation failed for symbol '%s' at 0x%016lx (%d)\n", | 269 | pr_err("relocation failed for symbol '%s' at 0x%016lx (%d)\n", |
324 | reloc->name, reloc->val, ret); | 270 | reloc->name, val, ret); |
325 | return ret; | 271 | goto out; |
326 | } | 272 | } |
327 | } | 273 | } |
328 | 274 | ||
329 | return 0; | 275 | out: |
276 | module_enable_ro(pmod); | ||
277 | return ret; | ||
330 | } | 278 | } |
331 | 279 | ||
332 | static void notrace klp_ftrace_handler(unsigned long ip, | 280 | static void notrace klp_ftrace_handler(unsigned long ip, |
@@ -593,7 +541,7 @@ EXPORT_SYMBOL_GPL(klp_enable_patch); | |||
593 | * /sys/kernel/livepatch/<patch> | 541 | * /sys/kernel/livepatch/<patch> |
594 | * /sys/kernel/livepatch/<patch>/enabled | 542 | * /sys/kernel/livepatch/<patch>/enabled |
595 | * /sys/kernel/livepatch/<patch>/<object> | 543 | * /sys/kernel/livepatch/<patch>/<object> |
596 | * /sys/kernel/livepatch/<patch>/<object>/<func> | 544 | * /sys/kernel/livepatch/<patch>/<object>/<function,sympos> |
597 | */ | 545 | */ |
598 | 546 | ||
599 | static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, | 547 | static ssize_t enabled_store(struct kobject *kobj, struct kobj_attribute *attr, |
@@ -738,8 +686,14 @@ static int klp_init_func(struct klp_object *obj, struct klp_func *func) | |||
738 | INIT_LIST_HEAD(&func->stack_node); | 686 | INIT_LIST_HEAD(&func->stack_node); |
739 | func->state = KLP_DISABLED; | 687 | func->state = KLP_DISABLED; |
740 | 688 | ||
689 | /* The format for the sysfs directory is <function,sympos> where sympos | ||
690 | * is the nth occurrence of this symbol in kallsyms for the patched | ||
691 | * object. If the user selects 0 for old_sympos, then 1 will be used | ||
692 | * since a unique symbol will be the first occurrence. | ||
693 | */ | ||
741 | return kobject_init_and_add(&func->kobj, &klp_ktype_func, | 694 | return kobject_init_and_add(&func->kobj, &klp_ktype_func, |
742 | &obj->kobj, "%s", func->old_name); | 695 | &obj->kobj, "%s,%lu", func->old_name, |
696 | func->old_sympos ? func->old_sympos : 1); | ||
743 | } | 697 | } |
744 | 698 | ||
745 | /* parts of the initialization that is done only when the object is loaded */ | 699 | /* parts of the initialization that is done only when the object is loaded */ |
@@ -756,7 +710,9 @@ static int klp_init_object_loaded(struct klp_patch *patch, | |||
756 | } | 710 | } |
757 | 711 | ||
758 | klp_for_each_func(obj, func) { | 712 | klp_for_each_func(obj, func) { |
759 | ret = klp_find_verify_func_addr(obj, func); | 713 | ret = klp_find_object_symbol(obj->name, func->old_name, |
714 | func->old_sympos, | ||
715 | &func->old_addr); | ||
760 | if (ret) | 716 | if (ret) |
761 | return ret; | 717 | return ret; |
762 | } | 718 | } |