]> git.ipfire.org Git - thirdparty/kmod.git/blob - libkmod/libkmod-elf.c
Optimize kmod_elf_get_strings() by reducing calls to memcpy
[thirdparty/kmod.git] / libkmod / libkmod-elf.c
1 /*
2 * libkmod - interface to kernel module operations
3 *
4 * Copyright (C) 2011 ProFUSION embedded systems
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <elf.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <assert.h>
25 #include <errno.h>
26
27 #include "libkmod.h"
28 #include "libkmod-private.h"
29
30 enum kmod_elf_class {
31 KMOD_ELF_32 = (1 << 1),
32 KMOD_ELF_64 = (1 << 2),
33 KMOD_ELF_LSB = (1 << 3),
34 KMOD_ELF_MSB = (1 << 4)
35 };
36
37 #ifdef WORDS_BIGENDIAN
38 static const enum kmod_elf_class native_endianess = KMOD_ELF_MSB;
39 #else
40 static const enum kmod_elf_class native_endianess = KMOD_ELF_LSB;
41 #endif
42
43 struct kmod_elf {
44 const uint8_t *memory;
45 uint8_t *changed;
46 uint64_t size;
47 enum kmod_elf_class class;
48 struct kmod_elf_header {
49 struct {
50 uint64_t offset;
51 uint16_t count;
52 uint16_t entry_size;
53 } section;
54 struct {
55 uint16_t section; /* index of the strings section */
56 uint64_t size;
57 uint64_t offset;
58 uint32_t nameoff; /* offset in strings itself */
59 } strings;
60 } header;
61 };
62
63 //#define ENABLE_ELFDBG 1
64
65 #if defined(ENABLE_LOGGING) && defined(ENABLE_ELFDBG)
66 #define ELFDBG(elf, ...) \
67 _elf_dbg(elf, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);
68
69 static inline void _elf_dbg(const struct kmod_elf *elf, const char *fname, unsigned line, const char *func, const char *fmt, ...)
70 {
71 va_list args;
72
73 fprintf(stderr, "ELFDBG-%d%c: %s:%u %s() ",
74 (elf->class & KMOD_ELF_32) ? 32 : 64,
75 (elf->class & KMOD_ELF_MSB) ? 'M' : 'L',
76 fname, line, func);
77 va_start(args, fmt);
78 vfprintf(stderr, fmt, args);
79 va_end(args);
80 }
81 #else
82 #define ELFDBG(elf, ...)
83 #endif
84
85
86 static int elf_identify(const void *memory, uint64_t size)
87 {
88 const uint8_t *p = memory;
89 int class = 0;
90
91 if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0)
92 return -ENOEXEC;
93
94 switch (p[EI_CLASS]) {
95 case ELFCLASS32:
96 if (size <= sizeof(Elf32_Ehdr))
97 return -EINVAL;
98 class |= KMOD_ELF_32;
99 break;
100 case ELFCLASS64:
101 if (size <= sizeof(Elf64_Ehdr))
102 return -EINVAL;
103 class |= KMOD_ELF_64;
104 break;
105 default:
106 return -EINVAL;
107 }
108
109 switch (p[EI_DATA]) {
110 case ELFDATA2LSB:
111 class |= KMOD_ELF_LSB;
112 break;
113 case ELFDATA2MSB:
114 class |= KMOD_ELF_MSB;
115 break;
116 default:
117 return -EINVAL;
118 }
119
120 return class;
121 }
122
123 static inline uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, uint16_t size)
124 {
125 const uint8_t *p;
126 uint64_t ret = 0;
127 size_t i;
128
129 assert(size <= sizeof(uint64_t));
130 assert(offset + size <= elf->size);
131 if (offset + size > elf->size) {
132 ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n",
133 offset, size, offset + size, elf->size);
134 return (uint64_t)-1;
135 }
136
137 p = elf->memory + offset;
138 if (elf->class & KMOD_ELF_MSB) {
139 for (i = 0; i < size; i++)
140 ret = (ret << 8) | p[i];
141 } else {
142 for (i = 1; i <= size; i++)
143 ret = (ret << 8) | p[size - i];
144 }
145
146 ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64"\n",
147 size, offset, ret);
148
149 return ret;
150 }
151
152 static inline int elf_set_uint(struct kmod_elf *elf, uint64_t offset, uint64_t size, uint64_t value)
153 {
154 uint8_t *p;
155 size_t i;
156
157 ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64" write memory=%p\n",
158 size, offset, value, elf->changed);
159
160 assert(size <= sizeof(uint64_t));
161 assert(offset + size <= elf->size);
162 if (offset + size > elf->size) {
163 ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n",
164 offset, size, offset + size, elf->size);
165 return -1;
166 }
167
168 if (elf->changed == NULL) {
169 elf->changed = malloc(elf->size);
170 if (elf->changed == NULL)
171 return -errno;
172 memcpy(elf->changed, elf->memory, elf->size);
173 elf->memory = elf->changed;
174 ELFDBG(elf, "copied memory to allow writing.\n");
175 }
176
177 p = elf->changed + offset;
178 if (elf->class & KMOD_ELF_MSB) {
179 for (i = 1; i <= size; i++) {
180 p[size - i] = value & 0xff;
181 value = (value & 0xffffffffffffff00) >> 8;
182 }
183 } else {
184 for (i = 0; i < size; i++) {
185 p[i] = value & 0xff;
186 value = (value & 0xffffffffffffff00) >> 8;
187 }
188 }
189
190 return 0;
191 }
192
193 static inline const void *elf_get_mem(const struct kmod_elf *elf, uint64_t offset)
194 {
195 assert(offset < elf->size);
196 if (offset >= elf->size) {
197 ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n",
198 offset, elf->size);
199 return NULL;
200 }
201 return elf->memory + offset;
202 }
203
204 static inline const void *elf_get_section_header(const struct kmod_elf *elf, uint16_t idx)
205 {
206 assert(idx != SHN_UNDEF);
207 assert(idx < elf->header.section.count);
208 if (idx == SHN_UNDEF || idx >= elf->header.section.count) {
209 ELFDBG(elf, "invalid section number: %"PRIu16", last=%"PRIu16"\n",
210 idx, elf->header.section.count);
211 return NULL;
212 }
213 return elf_get_mem(elf, elf->header.section.offset +
214 idx * elf->header.section.entry_size);
215 }
216
217 static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, uint32_t *nameoff)
218 {
219 const uint8_t *p = elf_get_section_header(elf, idx);
220 uint64_t min_size, off = p - elf->memory;
221
222 if (p == NULL) {
223 ELFDBG(elf, "no section at %"PRIu16"\n", idx);
224 *offset = 0;
225 *size = 0;
226 *nameoff = 0;
227 return -EINVAL;
228 }
229
230 #define READV(field) \
231 elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field))
232
233 if (elf->class & KMOD_ELF_32) {
234 const Elf32_Shdr *hdr = (const Elf32_Shdr *)p;
235 *size = READV(sh_size);
236 *offset = READV(sh_offset);
237 *nameoff = READV(sh_name);
238 } else {
239 const Elf64_Shdr *hdr = (const Elf64_Shdr *)p;
240 *size = READV(sh_size);
241 *offset = READV(sh_offset);
242 *nameoff = READV(sh_name);
243 }
244 #undef READV
245
246 min_size = *offset + *size;
247 if (min_size >= elf->size) {
248 ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n",
249 min_size, elf->size);
250 return -EINVAL;
251 }
252
253 ELFDBG(elf, "section=%"PRIu16" is: offset=%"PRIu64" size=%"PRIu64" nameoff=%"PRIu32"\n",
254 idx, *offset, *size, *nameoff);
255
256 return 0;
257 }
258
259 static const char *elf_get_strings_section(const struct kmod_elf *elf, uint64_t *size)
260 {
261 *size = elf->header.strings.size;
262 return elf_get_mem(elf, elf->header.strings.offset);
263 }
264
265 struct kmod_elf *kmod_elf_new(const void *memory, off_t size)
266 {
267 struct kmod_elf *elf;
268 size_t hdr_size, shdr_size, min_size;
269 int class;
270
271 assert(sizeof(uint16_t) == sizeof(Elf32_Half));
272 assert(sizeof(uint16_t) == sizeof(Elf64_Half));
273 assert(sizeof(uint32_t) == sizeof(Elf32_Word));
274 assert(sizeof(uint32_t) == sizeof(Elf64_Word));
275
276 class = elf_identify(memory, size);
277 if (class < 0) {
278 errno = -class;
279 return NULL;
280 }
281
282 elf = malloc(sizeof(struct kmod_elf));
283 if (elf == NULL) {
284 return NULL;
285 }
286
287 elf->memory = memory;
288 elf->changed = NULL;
289 elf->size = size;
290 elf->class = class;
291
292 #define READV(field) \
293 elf_get_uint(elf, offsetof(typeof(*hdr), field), sizeof(hdr->field))
294
295 #define LOAD_HEADER \
296 elf->header.section.offset = READV(e_shoff); \
297 elf->header.section.count = READV(e_shnum); \
298 elf->header.section.entry_size = READV(e_shentsize); \
299 elf->header.strings.section = READV(e_shstrndx)
300 if (elf->class & KMOD_ELF_32) {
301 const Elf32_Ehdr *hdr = elf_get_mem(elf, 0);
302 LOAD_HEADER;
303 hdr_size = sizeof(Elf32_Ehdr);
304 shdr_size = sizeof(Elf32_Shdr);
305 } else {
306 const Elf64_Ehdr *hdr = elf_get_mem(elf, 0);
307 LOAD_HEADER;
308 hdr_size = sizeof(Elf64_Ehdr);
309 shdr_size = sizeof(Elf64_Shdr);
310 }
311 #undef LOAD_HEADER
312 #undef READV
313
314 ELFDBG(elf, "section: offset=%"PRIu64" count=%"PRIu16" entry_size=%"PRIu16" strings index=%"PRIu16"\n",
315 elf->header.section.offset,
316 elf->header.section.count,
317 elf->header.section.entry_size,
318 elf->header.strings.section);
319
320 if (elf->header.section.entry_size != shdr_size) {
321 ELFDBG(elf, "unexpected section entry size: %"PRIu16", expected %"PRIu16"\n",
322 elf->header.section.entry_size, shdr_size);
323 goto invalid;
324 }
325 min_size = hdr_size + shdr_size * elf->header.section.count;
326 if (min_size >= elf->size) {
327 ELFDBG(elf, "file is too short to hold sections\n");
328 goto invalid;
329 }
330
331 if (elf_get_section_info(elf, elf->header.strings.section,
332 &elf->header.strings.offset,
333 &elf->header.strings.size,
334 &elf->header.strings.nameoff) < 0) {
335 ELFDBG(elf, "could not get strings section\n");
336 goto invalid;
337 } else {
338 uint64_t slen;
339 const char *s = elf_get_strings_section(elf, &slen);
340 if (slen == 0 || s[slen - 1] != '\0') {
341 ELFDBG(elf, "strings section does not ends with \\0\n");
342 goto invalid;
343 }
344 }
345
346 return elf;
347
348 invalid:
349 free(elf);
350 errno = EINVAL;
351 return NULL;
352 }
353
354 void kmod_elf_unref(struct kmod_elf *elf)
355 {
356 free(elf->changed);
357 free(elf);
358 }
359
360 const void *kmod_elf_get_memory(const struct kmod_elf *elf)
361 {
362 return elf->memory;
363 }
364
365 static int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, size_t *buf_size)
366 {
367 uint64_t nameslen;
368 const char *names = elf_get_strings_section(elf, &nameslen);
369 uint16_t i;
370
371 *buf = NULL;
372 *buf_size = 0;
373
374 for (i = 1; i < elf->header.section.count; i++) {
375 uint64_t off, size;
376 uint32_t nameoff;
377 const char *n;
378 int err = elf_get_section_info(elf, i, &off, &size, &nameoff);
379 if (err < 0)
380 continue;
381 if (nameoff >= nameslen)
382 continue;
383 n = names + nameoff;
384 if (!streq(section, n))
385 continue;
386
387 *buf = elf_get_mem(elf, off);
388 *buf_size = size;
389 return 0;
390 }
391
392 return -ENOENT;
393 }
394
395 /* array will be allocated with strings in a single malloc, just free *array */
396 int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array)
397 {
398 size_t i, j, size, count;
399 const void *buf;
400 const char *strings;
401 char *s, **a;
402 int err;
403
404 *array = NULL;
405
406 err = kmod_elf_get_section(elf, section, &buf, &size);
407 if (err < 0)
408 return err;
409
410 strings = buf;
411 if (strings == NULL || size == 0)
412 return 0;
413
414 /* skip zero padding */
415 while (strings[0] == '\0' && size > 1) {
416 strings++;
417 size--;
418 }
419
420 if (size <= 1)
421 return 0;
422
423 for (i = 0, count = 0; i < size; i++) {
424 if (strings[i] != '\0')
425 continue;
426
427 count++;
428 }
429
430 if (strings[i - 1] != '\0')
431 count++;
432
433 *array = a = malloc(size + 1 + sizeof(char *) * (count + 1));
434 if (*array == NULL)
435 return -errno;
436
437 s = (char *)(a + count + 1);
438 memcpy(s, strings, size);
439
440 /* make sure the last string is NULL-terminated */
441 s[size] = '\0';
442 a[count] = NULL;
443 a[0] = s;
444
445 for (i = 0, j = 1; j < count && i < size; i++) {
446 if (s[i] != '\0')
447 continue;
448
449 a[j] = &s[i + 1];
450 j++;
451 }
452
453 return count;
454 }
455
456 /* array will be allocated with strings in a single malloc, just free *array */
457 int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array)
458 {
459 uint64_t size, secsize, slen, off;
460 struct kmod_modversion *a;
461 const void *buf;
462 char *itr;
463 int i, count, err;
464 struct kmod_modversion32 {
465 uint32_t crc;
466 char name[64 - sizeof(uint32_t)];
467 };
468 struct kmod_modversion64 {
469 uint64_t crc;
470 char name[64 - sizeof(uint64_t)];
471 };
472
473 *array = NULL;
474
475 err = kmod_elf_get_section(elf, "__versions", &buf, &size);
476 if (err < 0)
477 return err;
478 if (buf == NULL || size == 0)
479 return 0;
480
481 if (elf->class & KMOD_ELF_32)
482 secsize = sizeof(struct kmod_modversion32);
483 else
484 secsize = sizeof(struct kmod_modversion64);
485
486 if (size % secsize != 0)
487 return -EINVAL;
488 count = size / secsize;
489
490 off = (const uint8_t *)buf - elf->memory;
491 slen = 0;
492 for (i = 0; i < count; i++, off += secsize) {
493 const char *symbol;
494 if (elf->class & KMOD_ELF_32) {
495 struct kmod_modversion32 *mv;
496 symbol = elf_get_mem(elf, off + sizeof(mv->crc));
497 } else {
498 struct kmod_modversion64 *mv;
499 symbol = elf_get_mem(elf, off + sizeof(mv->crc));
500 }
501 if (symbol[0] == '.')
502 symbol++;
503 slen += strlen(symbol) + 1;
504 }
505
506 *array = a = malloc(sizeof(struct kmod_modversion) * count + slen);
507 if (*array == NULL)
508 return -errno;
509
510 itr = (char *)(a + count);
511 off = (const uint8_t *)buf - elf->memory;
512 for (i = 0; i < count; i++, off += secsize) {
513 uint64_t crc;
514 const char *symbol;
515 size_t symbollen;
516 if (elf->class & KMOD_ELF_32) {
517 struct kmod_modversion32 *mv;
518 crc = elf_get_uint(elf, off, sizeof(mv->crc));
519 symbol = elf_get_mem(elf, off + sizeof(mv->crc));
520 } else {
521 struct kmod_modversion64 *mv;
522 crc = elf_get_uint(elf, off, sizeof(mv->crc));
523 symbol = elf_get_mem(elf, off + sizeof(mv->crc));
524 }
525 if (symbol[0] == '.')
526 symbol++;
527
528 a[i].crc = crc;
529 a[i].symbol = itr;
530 symbollen = strlen(symbol) + 1;
531 memcpy(itr, symbol, symbollen);
532 itr += symbollen;
533 }
534
535 return count;
536 }
537
538 int kmod_elf_strip_section(struct kmod_elf *elf, const char *section)
539 {
540 uint64_t size, off;
541 const void *buf;
542 int err = kmod_elf_get_section(elf, section, &buf, &size);
543 if (err < 0)
544 return err;
545
546 off = (const uint8_t *)buf - elf->memory;
547
548 #define WRITEV(field, value) \
549 elf_set_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field), value)
550 if (elf->class & KMOD_ELF_32) {
551 const Elf32_Shdr *hdr = buf;
552 uint32_t val = ~(uint32_t)SHF_ALLOC;
553 return WRITEV(sh_flags, val);
554 } else {
555 const Elf64_Shdr *hdr = buf;
556 uint64_t val = ~(uint64_t)SHF_ALLOC;
557 return WRITEV(sh_flags, val);
558 }
559 #undef WRITEV
560 }
561
562 int kmod_elf_strip_vermagic(struct kmod_elf *elf)
563 {
564 uint64_t i, size;
565 const void *buf;
566 const char *strings;
567 int err;
568
569 err = kmod_elf_get_section(elf, ".modinfo", &buf, &size);
570 if (err < 0)
571 return err;
572 strings = buf;
573 if (strings == NULL || size == 0)
574 return 0;
575
576 /* skip zero padding */
577 while (strings[0] == '\0' && size > 1) {
578 strings++;
579 size--;
580 }
581 if (size <= 1)
582 return 0;
583
584 for (i = 0; i < size; i++) {
585 const char *s;
586 size_t off, len;
587
588 if (strings[i] == '\0')
589 continue;
590 if (i + 1 >= size)
591 continue;
592
593 s = strings + i;
594 len = sizeof("vermagic=") - 1;
595 if (i + len >= size)
596 continue;
597 if (strncmp(s, "vermagic=", len) != 0) {
598 i += strlen(s);
599 continue;
600 }
601 s += len;
602 off = (const uint8_t *)s - elf->memory;
603
604 if (elf->changed == NULL) {
605 elf->changed = malloc(elf->size);
606 if (elf->changed == NULL)
607 return -errno;
608 memcpy(elf->changed, elf->memory, elf->size);
609 elf->memory = elf->changed;
610 ELFDBG(elf, "copied memory to allow writing.\n");
611 }
612
613 len = strlen(s);
614 ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zd bytes)\n",
615 s, len);
616 memset(elf->changed + off, '\0', len);
617 return 0;
618 }
619
620 ELFDBG(elf, "no vermagic found in .modinfo\n");
621 return -ENOENT;
622 }