1/* Write the contents of the <certfile> into kernel symbol system_extra_cert
2 *
3 * Copyright (C) IBM Corporation, 2015
4 *
5 * Author: Mehmet Kayaalp <mkayaalp@linux.vnet.ibm.com>
6 *
7 * This software may be used and distributed according to the terms
8 * of the GNU General Public License, incorporated herein by reference.
9 *
10 * Usage: insert-sys-cert [-s <System.map> -b <vmlinux> -c <certfile>
11 */
12
13#define _GNU_SOURCE
14#include <stdio.h>
15#include <ctype.h>
16#include <string.h>
17#include <limits.h>
18#include <stdbool.h>
19#include <errno.h>
20#include <stdlib.h>
21#include <stdarg.h>
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <sys/mman.h>
25#include <fcntl.h>
26#include <unistd.h>
27#include <elf.h>
28
29#define CERT_SYM  "system_extra_cert"
30#define USED_SYM  "system_extra_cert_used"
31#define LSIZE_SYM "system_certificate_list_size"
32
33#define info(format, args...) fprintf(stderr, "INFO:    " format, ## args)
34#define warn(format, args...) fprintf(stdout, "WARNING: " format, ## args)
35#define  err(format, args...) fprintf(stderr, "ERROR:   " format, ## args)
36
37#if UINTPTR_MAX == 0xffffffff
38#define CURRENT_ELFCLASS ELFCLASS32
39#define Elf_Ehdr	Elf32_Ehdr
40#define Elf_Shdr	Elf32_Shdr
41#define Elf_Sym		Elf32_Sym
42#else
43#define CURRENT_ELFCLASS ELFCLASS64
44#define Elf_Ehdr	Elf64_Ehdr
45#define Elf_Shdr	Elf64_Shdr
46#define Elf_Sym		Elf64_Sym
47#endif
48
49static unsigned char endianness(void)
50{
51	uint16_t two_byte = 0x00FF;
52	uint8_t low_address = *((uint8_t *)&two_byte);
53
54	if (low_address == 0)
55		return ELFDATA2MSB;
56	else
57		return ELFDATA2LSB;
58}
59
60struct sym {
61	char *name;
62	unsigned long address;
63	unsigned long offset;
64	void *content;
65	int size;
66};
67
68static unsigned long get_offset_from_address(Elf_Ehdr *hdr, unsigned long addr)
69{
70	Elf_Shdr *x;
71	unsigned int i, num_sections;
72
73	x = (void *)hdr + hdr->e_shoff;
74	if (hdr->e_shnum == SHN_UNDEF)
75		num_sections = x[0].sh_size;
76	else
77		num_sections = hdr->e_shnum;
78
79	for (i = 1; i < num_sections; i++) {
80		unsigned long start = x[i].sh_addr;
81		unsigned long end = start + x[i].sh_size;
82		unsigned long offset = x[i].sh_offset;
83
84		if (addr >= start && addr <= end)
85			return addr - start + offset;
86	}
87	return 0;
88}
89
90
91#define LINE_SIZE 100
92
93static void get_symbol_from_map(Elf_Ehdr *hdr, FILE *f, char *name,
94				struct sym *s)
95{
96	char l[LINE_SIZE];
97	char *w, *p, *n;
98
99	s->size = 0;
100	s->address = 0;
101	s->offset = 0;
102	if (fseek(f, 0, SEEK_SET) != 0) {
103		perror("File seek failed");
104		exit(EXIT_FAILURE);
105	}
106	while (fgets(l, LINE_SIZE, f)) {
107		p = strchr(l, '\n');
108		if (!p) {
109			err("Missing line ending.\n");
110			return;
111		}
112		n = strstr(l, name);
113		if (n)
114			break;
115	}
116	if (!n) {
117		err("Unable to find symbol: %s\n", name);
118		return;
119	}
120	w = strchr(l, ' ');
121	if (!w)
122		return;
123
124	*w = '\0';
125	s->address = strtoul(l, NULL, 16);
126	if (s->address == 0)
127		return;
128	s->offset = get_offset_from_address(hdr, s->address);
129	s->name = name;
130	s->content = (void *)hdr + s->offset;
131}
132
133static Elf_Sym *find_elf_symbol(Elf_Ehdr *hdr, Elf_Shdr *symtab, char *name)
134{
135	Elf_Sym *sym, *symtab_start;
136	char *strtab, *symname;
137	unsigned int link;
138	Elf_Shdr *x;
139	int i, n;
140
141	x = (void *)hdr + hdr->e_shoff;
142	link = symtab->sh_link;
143	symtab_start = (void *)hdr + symtab->sh_offset;
144	n = symtab->sh_size / symtab->sh_entsize;
145	strtab = (void *)hdr + x[link].sh_offset;
146
147	for (i = 0; i < n; i++) {
148		sym = &symtab_start[i];
149		symname = strtab + sym->st_name;
150		if (strcmp(symname, name) == 0)
151			return sym;
152	}
153	err("Unable to find symbol: %s\n", name);
154	return NULL;
155}
156
157static void get_symbol_from_table(Elf_Ehdr *hdr, Elf_Shdr *symtab,
158				  char *name, struct sym *s)
159{
160	Elf_Shdr *sec;
161	int secndx;
162	Elf_Sym *elf_sym;
163	Elf_Shdr *x;
164
165	x = (void *)hdr + hdr->e_shoff;
166	s->size = 0;
167	s->address = 0;
168	s->offset = 0;
169	elf_sym = find_elf_symbol(hdr, symtab, name);
170	if (!elf_sym)
171		return;
172	secndx = elf_sym->st_shndx;
173	if (!secndx)
174		return;
175	sec = &x[secndx];
176	s->size = elf_sym->st_size;
177	s->address = elf_sym->st_value;
178	s->offset = s->address - sec->sh_addr
179			       + sec->sh_offset;
180	s->name = name;
181	s->content = (void *)hdr + s->offset;
182}
183
184static Elf_Shdr *get_symbol_table(Elf_Ehdr *hdr)
185{
186	Elf_Shdr *x;
187	unsigned int i, num_sections;
188
189	x = (void *)hdr + hdr->e_shoff;
190	if (hdr->e_shnum == SHN_UNDEF)
191		num_sections = x[0].sh_size;
192	else
193		num_sections = hdr->e_shnum;
194
195	for (i = 1; i < num_sections; i++)
196		if (x[i].sh_type == SHT_SYMTAB)
197			return &x[i];
198	return NULL;
199}
200
201static void *map_file(char *file_name, int *size)
202{
203	struct stat st;
204	void *map;
205	int fd;
206
207	fd = open(file_name, O_RDWR);
208	if (fd < 0) {
209		perror(file_name);
210		return NULL;
211	}
212	if (fstat(fd, &st)) {
213		perror("Could not determine file size");
214		close(fd);
215		return NULL;
216	}
217	*size = st.st_size;
218	map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
219	if (map == MAP_FAILED) {
220		perror("Mapping to memory failed");
221		close(fd);
222		return NULL;
223	}
224	close(fd);
225	return map;
226}
227
228static char *read_file(char *file_name, int *size)
229{
230	struct stat st;
231	char *buf;
232	int fd;
233
234	fd = open(file_name, O_RDONLY);
235	if (fd < 0) {
236		perror(file_name);
237		return NULL;
238	}
239	if (fstat(fd, &st)) {
240		perror("Could not determine file size");
241		close(fd);
242		return NULL;
243	}
244	*size = st.st_size;
245	buf = malloc(*size);
246	if (!buf) {
247		perror("Allocating memory failed");
248		close(fd);
249		return NULL;
250	}
251	if (read(fd, buf, *size) != *size) {
252		perror("File read failed");
253		close(fd);
254		return NULL;
255	}
256	close(fd);
257	return buf;
258}
259
260static void print_sym(Elf_Ehdr *hdr, struct sym *s)
261{
262	info("sym:    %s\n", s->name);
263	info("addr:   0x%lx\n", s->address);
264	info("size:   %d\n", s->size);
265	info("offset: 0x%lx\n", (unsigned long)s->offset);
266}
267
268static void print_usage(char *e)
269{
270	printf("Usage %s [-s <System.map>] -b <vmlinux> -c <certfile>\n", e);
271}
272
273int main(int argc, char **argv)
274{
275	char *system_map_file = NULL;
276	char *vmlinux_file = NULL;
277	char *cert_file = NULL;
278	int vmlinux_size;
279	int cert_size;
280	Elf_Ehdr *hdr;
281	char *cert;
282	FILE *system_map;
283	unsigned long *lsize;
284	int *used;
285	int opt;
286	Elf_Shdr *symtab = NULL;
287	struct sym cert_sym, lsize_sym, used_sym;
288
289	while ((opt = getopt(argc, argv, "b:c:s:")) != -1) {
290		switch (opt) {
291		case 's':
292			system_map_file = optarg;
293			break;
294		case 'b':
295			vmlinux_file = optarg;
296			break;
297		case 'c':
298			cert_file = optarg;
299			break;
300		default:
301			break;
302		}
303	}
304
305	if (!vmlinux_file || !cert_file) {
306		print_usage(argv[0]);
307		exit(EXIT_FAILURE);
308	}
309
310	cert = read_file(cert_file, &cert_size);
311	if (!cert)
312		exit(EXIT_FAILURE);
313
314	hdr = map_file(vmlinux_file, &vmlinux_size);
315	if (!hdr)
316		exit(EXIT_FAILURE);
317
318	if (vmlinux_size < sizeof(*hdr)) {
319		err("Invalid ELF file.\n");
320		exit(EXIT_FAILURE);
321	}
322
323	if ((hdr->e_ident[EI_MAG0] != ELFMAG0) ||
324	    (hdr->e_ident[EI_MAG1] != ELFMAG1) ||
325	    (hdr->e_ident[EI_MAG2] != ELFMAG2) ||
326	    (hdr->e_ident[EI_MAG3] != ELFMAG3)) {
327		err("Invalid ELF magic.\n");
328		exit(EXIT_FAILURE);
329	}
330
331	if (hdr->e_ident[EI_CLASS] != CURRENT_ELFCLASS) {
332		err("ELF class mismatch.\n");
333		exit(EXIT_FAILURE);
334	}
335
336	if (hdr->e_ident[EI_DATA] != endianness()) {
337		err("ELF endian mismatch.\n");
338		exit(EXIT_FAILURE);
339	}
340
341	if (hdr->e_shoff > vmlinux_size) {
342		err("Could not find section header.\n");
343		exit(EXIT_FAILURE);
344	}
345
346	symtab = get_symbol_table(hdr);
347	if (!symtab) {
348		warn("Could not find the symbol table.\n");
349		if (!system_map_file) {
350			err("Please provide a System.map file.\n");
351			print_usage(argv[0]);
352			exit(EXIT_FAILURE);
353		}
354
355		system_map = fopen(system_map_file, "r");
356		if (!system_map) {
357			perror(system_map_file);
358			exit(EXIT_FAILURE);
359		}
360		get_symbol_from_map(hdr, system_map, CERT_SYM, &cert_sym);
361		get_symbol_from_map(hdr, system_map, USED_SYM, &used_sym);
362		get_symbol_from_map(hdr, system_map, LSIZE_SYM, &lsize_sym);
363		cert_sym.size = used_sym.address - cert_sym.address;
364	} else {
365		info("Symbol table found.\n");
366		if (system_map_file)
367			warn("System.map is ignored.\n");
368		get_symbol_from_table(hdr, symtab, CERT_SYM, &cert_sym);
369		get_symbol_from_table(hdr, symtab, USED_SYM, &used_sym);
370		get_symbol_from_table(hdr, symtab, LSIZE_SYM, &lsize_sym);
371	}
372
373	if (!cert_sym.offset || !lsize_sym.offset || !used_sym.offset)
374		exit(EXIT_FAILURE);
375
376	print_sym(hdr, &cert_sym);
377	print_sym(hdr, &used_sym);
378	print_sym(hdr, &lsize_sym);
379
380	lsize = (unsigned long *)lsize_sym.content;
381	used = (int *)used_sym.content;
382
383	if (cert_sym.size < cert_size) {
384		err("Certificate is larger than the reserved area!\n");
385		exit(EXIT_FAILURE);
386	}
387
388	/* If the existing cert is the same, don't overwrite */
389	if (cert_size == *used &&
390	    strncmp(cert_sym.content, cert, cert_size) == 0) {
391		warn("Certificate was already inserted.\n");
392		exit(EXIT_SUCCESS);
393	}
394
395	if (*used > 0)
396		warn("Replacing previously inserted certificate.\n");
397
398	memcpy(cert_sym.content, cert, cert_size);
399	if (cert_size < cert_sym.size)
400		memset(cert_sym.content + cert_size,
401			0, cert_sym.size - cert_size);
402
403	*lsize = *lsize + cert_size - *used;
404	*used = cert_size;
405	info("Inserted the contents of %s into %lx.\n", cert_file,
406						cert_sym.address);
407	info("Used %d bytes out of %d bytes reserved.\n", *used,
408						 cert_sym.size);
409	exit(EXIT_SUCCESS);
410}
411