1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/buildid.h> |
4 | #include <linux/cache.h> |
5 | #include <linux/elf.h> |
6 | #include <linux/kernel.h> |
7 | #include <linux/pagemap.h> |
8 | |
9 | #define BUILD_ID 3 |
10 | |
11 | /* |
12 | * Parse build id from the note segment. This logic can be shared between |
13 | * 32-bit and 64-bit system, because Elf32_Nhdr and Elf64_Nhdr are |
14 | * identical. |
15 | */ |
16 | static int parse_build_id_buf(unsigned char *build_id, |
17 | __u32 *size, |
18 | const void *note_start, |
19 | Elf32_Word note_size) |
20 | { |
21 | Elf32_Word note_offs = 0, new_offs; |
22 | |
23 | while (note_offs + sizeof(Elf32_Nhdr) < note_size) { |
24 | Elf32_Nhdr *nhdr = (Elf32_Nhdr *)(note_start + note_offs); |
25 | |
26 | if (nhdr->n_type == BUILD_ID && |
27 | nhdr->n_namesz == sizeof("GNU" ) && |
28 | !strcmp((char *)(nhdr + 1), "GNU" ) && |
29 | nhdr->n_descsz > 0 && |
30 | nhdr->n_descsz <= BUILD_ID_SIZE_MAX) { |
31 | memcpy(build_id, |
32 | note_start + note_offs + |
33 | ALIGN(sizeof("GNU" ), 4) + sizeof(Elf32_Nhdr), |
34 | nhdr->n_descsz); |
35 | memset(build_id + nhdr->n_descsz, 0, |
36 | BUILD_ID_SIZE_MAX - nhdr->n_descsz); |
37 | if (size) |
38 | *size = nhdr->n_descsz; |
39 | return 0; |
40 | } |
41 | new_offs = note_offs + sizeof(Elf32_Nhdr) + |
42 | ALIGN(nhdr->n_namesz, 4) + ALIGN(nhdr->n_descsz, 4); |
43 | if (new_offs <= note_offs) /* overflow */ |
44 | break; |
45 | note_offs = new_offs; |
46 | } |
47 | |
48 | return -EINVAL; |
49 | } |
50 | |
51 | static inline int parse_build_id(const void *page_addr, |
52 | unsigned char *build_id, |
53 | __u32 *size, |
54 | const void *note_start, |
55 | Elf32_Word note_size) |
56 | { |
57 | /* check for overflow */ |
58 | if (note_start < page_addr || note_start + note_size < note_start) |
59 | return -EINVAL; |
60 | |
61 | /* only supports note that fits in the first page */ |
62 | if (note_start + note_size > page_addr + PAGE_SIZE) |
63 | return -EINVAL; |
64 | |
65 | return parse_build_id_buf(build_id, size, note_start, note_size); |
66 | } |
67 | |
68 | /* Parse build ID from 32-bit ELF */ |
69 | static int get_build_id_32(const void *page_addr, unsigned char *build_id, |
70 | __u32 *size) |
71 | { |
72 | Elf32_Ehdr *ehdr = (Elf32_Ehdr *)page_addr; |
73 | Elf32_Phdr *phdr; |
74 | int i; |
75 | |
76 | /* only supports phdr that fits in one page */ |
77 | if (ehdr->e_phnum > |
78 | (PAGE_SIZE - sizeof(Elf32_Ehdr)) / sizeof(Elf32_Phdr)) |
79 | return -EINVAL; |
80 | |
81 | phdr = (Elf32_Phdr *)(page_addr + sizeof(Elf32_Ehdr)); |
82 | |
83 | for (i = 0; i < ehdr->e_phnum; ++i) { |
84 | if (phdr[i].p_type == PT_NOTE && |
85 | !parse_build_id(page_addr, build_id, size, |
86 | note_start: page_addr + phdr[i].p_offset, |
87 | note_size: phdr[i].p_filesz)) |
88 | return 0; |
89 | } |
90 | return -EINVAL; |
91 | } |
92 | |
93 | /* Parse build ID from 64-bit ELF */ |
94 | static int get_build_id_64(const void *page_addr, unsigned char *build_id, |
95 | __u32 *size) |
96 | { |
97 | Elf64_Ehdr *ehdr = (Elf64_Ehdr *)page_addr; |
98 | Elf64_Phdr *phdr; |
99 | int i; |
100 | |
101 | /* only supports phdr that fits in one page */ |
102 | if (ehdr->e_phnum > |
103 | (PAGE_SIZE - sizeof(Elf64_Ehdr)) / sizeof(Elf64_Phdr)) |
104 | return -EINVAL; |
105 | |
106 | phdr = (Elf64_Phdr *)(page_addr + sizeof(Elf64_Ehdr)); |
107 | |
108 | for (i = 0; i < ehdr->e_phnum; ++i) { |
109 | if (phdr[i].p_type == PT_NOTE && |
110 | !parse_build_id(page_addr, build_id, size, |
111 | note_start: page_addr + phdr[i].p_offset, |
112 | note_size: phdr[i].p_filesz)) |
113 | return 0; |
114 | } |
115 | return -EINVAL; |
116 | } |
117 | |
118 | /* |
119 | * Parse build ID of ELF file mapped to vma |
120 | * @vma: vma object |
121 | * @build_id: buffer to store build id, at least BUILD_ID_SIZE long |
122 | * @size: returns actual build id size in case of success |
123 | * |
124 | * Return: 0 on success, -EINVAL otherwise |
125 | */ |
126 | int build_id_parse(struct vm_area_struct *vma, unsigned char *build_id, |
127 | __u32 *size) |
128 | { |
129 | Elf32_Ehdr *ehdr; |
130 | struct page *page; |
131 | void *page_addr; |
132 | int ret; |
133 | |
134 | /* only works for page backed storage */ |
135 | if (!vma->vm_file) |
136 | return -EINVAL; |
137 | |
138 | page = find_get_page(mapping: vma->vm_file->f_mapping, offset: 0); |
139 | if (!page) |
140 | return -EFAULT; /* page not mapped */ |
141 | |
142 | ret = -EINVAL; |
143 | page_addr = kmap_atomic(page); |
144 | ehdr = (Elf32_Ehdr *)page_addr; |
145 | |
146 | /* compare magic x7f "ELF" */ |
147 | if (memcmp(p: ehdr->e_ident, ELFMAG, SELFMAG) != 0) |
148 | goto out; |
149 | |
150 | /* only support executable file and shared object file */ |
151 | if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) |
152 | goto out; |
153 | |
154 | if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) |
155 | ret = get_build_id_32(page_addr, build_id, size); |
156 | else if (ehdr->e_ident[EI_CLASS] == ELFCLASS64) |
157 | ret = get_build_id_64(page_addr, build_id, size); |
158 | out: |
159 | kunmap_atomic(page_addr); |
160 | put_page(page); |
161 | return ret; |
162 | } |
163 | |
164 | /** |
165 | * build_id_parse_buf - Get build ID from a buffer |
166 | * @buf: ELF note section(s) to parse |
167 | * @buf_size: Size of @buf in bytes |
168 | * @build_id: Build ID parsed from @buf, at least BUILD_ID_SIZE_MAX long |
169 | * |
170 | * Return: 0 on success, -EINVAL otherwise |
171 | */ |
172 | int build_id_parse_buf(const void *buf, unsigned char *build_id, u32 buf_size) |
173 | { |
174 | return parse_build_id_buf(build_id, NULL, note_start: buf, note_size: buf_size); |
175 | } |
176 | |
177 | #if IS_ENABLED(CONFIG_STACKTRACE_BUILD_ID) || IS_ENABLED(CONFIG_CRASH_CORE) |
178 | unsigned char vmlinux_build_id[BUILD_ID_SIZE_MAX] __ro_after_init; |
179 | |
180 | /** |
181 | * init_vmlinux_build_id - Compute and stash the running kernel's build ID |
182 | */ |
183 | void __init init_vmlinux_build_id(void) |
184 | { |
185 | extern const void __start_notes __weak; |
186 | extern const void __stop_notes __weak; |
187 | unsigned int size = &__stop_notes - &__start_notes; |
188 | |
189 | build_id_parse_buf(buf: &__start_notes, build_id: vmlinux_build_id, buf_size: size); |
190 | } |
191 | #endif |
192 | |