1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
From f6c412a240312a2be28b85905a0866288db9ffc8 Mon Sep 17 00:00:00 2001
From: Lans Zhang <jia.zhang@windriver.com>
Date: Sun, 24 Apr 2016 19:02:28 +0800
Subject: [PATCH] chainloader: Actually find the relocations correctly and
process them that way.
Upstream-Status: Pending
Refer to a846aedd0e9dfe26ca6afaf6a1db8a54c20363c1 in shim.
Find the relocations based on the *file* address in the old binary,
because it's only the same as the virtual address some of the time.
Also perform some extra validation before processing it, and don't bail
out in /error/ if both ReloceBase and RelocEnd are null - that condition
is fine.
Signed-off-by: Lans Zhang <jia.zhang@windriver.com>
---
grub-core/loader/efi/chainloader.c | 97 +++++++++++++++++++++++++++++++-------
1 file changed, 81 insertions(+), 16 deletions(-)
diff --git a/grub-core/loader/efi/chainloader.c b/grub-core/loader/efi/chainloader.c
index 0e84100..83769a2 100644
--- a/grub-core/loader/efi/chainloader.c
+++ b/grub-core/loader/efi/chainloader.c
@@ -162,6 +162,7 @@ grub_shim_image_address (grub_addr_t image, grub_uint32_t size, grub_uint32_t ad
*/
static grub_err_t
grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
+ struct grub_pe32_section_table *section,
void *orig, void *data)
{
struct grub_image_base_relocation *reloc_base, *reloc_base_end;
@@ -173,19 +174,53 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
grub_efi_uint64_t *fixup64;
grub_int32_t size = context->image_size;
void *image_end = (char *)orig + size;
+ int n = 0;
if (grub_shim_image_is_64_bit(context->pe_hdr))
context->pe_hdr->pe32plus.opt_hdr.image_base = (grub_efi_uint64_t)(unsigned long)data;
else
context->pe_hdr->pe32.opt_hdr.image_base = (grub_efi_uint32_t)(unsigned long)data;
+
+ /* Alright, so here's how this works:
+ *
+ * context->RelocDir gives us two things:
+ * - the VA the table of base relocation blocks are (maybe) to be
+ * mapped at (RelocDir->VirtualAddress)
+ * - the virtual size (RelocDir->Size)
+ *
+ * The .reloc section (Section here) gives us some other things:
+ * - the name! kind of. (Section->Name)
+ * - the virtual size (Section->VirtualSize), which should be the same
+ * as RelocDir->Size
+ * - the virtual address (Section->VirtualAddress)
+ * - the file section size (Section->SizeOfRawData), which is
+ * a multiple of OptHdr->FileAlignment. Only useful for image
+ * validation, not really useful for iteration bounds.
+ * - the file address (Section->PointerToRawData)
+ * - a bunch of stuff we don't use that's 0 in our binaries usually
+ * - Flags (Section->Characteristics)
+ *
+ * and then the thing that's actually at the file address is an array
+ * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind
+ * them. The SizeOfBlock field of this structure includes the
+ * structure itself, and adding it to that structure's address will
+ * yield the next entry in the array.
+ */
reloc_base = (struct grub_image_base_relocation *)
grub_shim_image_address ((grub_efi_uint64_t)orig, size,
- context->reloc_dir->rva);
+ section->raw_data_offset);
+ /* reloc_base_end is the address of the first entry /past/ the
+ * table. */
reloc_base_end = (struct grub_image_base_relocation *)
grub_shim_image_address ((grub_efi_uint64_t)orig, size,
- context->reloc_dir->rva
- + context->reloc_dir->size - 1);
+ section->raw_data_offset
+ + section->virtual_size - 1);
+
+ if (!reloc_base && !reloc_base_end)
+ {
+ return GRUB_EFI_SUCCESS;
+ }
if (!reloc_base || !reloc_base_end)
{
@@ -206,7 +241,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
if ((reloc_base->block_size == 0)
|| (reloc_base->block_size > context->reloc_dir->size))
{
- grub_printf("Reloc block size %d is invalid\n", reloc_base->block_size);
+ grub_printf("Reloc %d block size %d is invalid\n", n, reloc_base->block_size);
return GRUB_ERR_FILE_READ_ERROR;
}
@@ -214,7 +249,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
((grub_uint8_t *) reloc_base + reloc_base->block_size);
if ((void *)reloc_end < orig || (void *)reloc_end > image_end)
{
- grub_printf("Reloc entry overflows binary\n");
+ grub_printf("Reloc %d entry overflows binary\n", n);
return GRUB_ERR_FILE_READ_ERROR;
}
@@ -224,7 +259,7 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
reloc_base->virtual_address);
if (!fixup_base)
{
- grub_printf("Invalid fixup_base\n");
+ grub_printf("Reloc %d invalid fixup_base\n", n);
return GRUB_ERR_FILE_READ_ERROR;
}
@@ -282,12 +317,13 @@ grub_shim_relocate_coff (struct grub_shim_pe_coff_loader_image_context *context,
break;
default:
- grub_printf("Unknown relocation\n");
+ grub_printf("Reloc %d unknown relocation\n", n);
return GRUB_ERR_FILE_READ_ERROR;
}
reloc += 1;
}
reloc_base = (struct grub_image_base_relocation *) reloc_end;
+ n++;
}
return GRUB_EFI_SUCCESS;
@@ -458,9 +494,9 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
grub_efi_status_t efi_status;
grub_uint32_t sect_size;
/* TODO: can they be unsigned? */
- grub_int8_t *base, *end;
+ grub_int8_t *base, *end, *reloc_base, *reloc_base_end;
grub_int32_t i;
- struct grub_pe32_section_table *section;
+ struct grub_pe32_section_table *section, *reloc_section;
grub_efi_boot_services_t *b;
shim_used = 0;
@@ -496,16 +532,21 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
/* TODO: do we need the double cast? */
grub_memcpy ((void *) ((grub_efi_physical_address_t) shim_buffer),
(void *) ((grub_addr_t) addr), context->header_size);
+
+ reloc_base = (grub_int8_t *) grub_shim_image_address (shim_buffer, size,
+ context->reloc_dir->rva);
+ /* reloc_base_end here is the address of the last byte of the table */
+ reloc_base_end = (grub_int8_t *) grub_shim_image_address (shim_buffer, size,
+ context->reloc_dir->rva +
+ context->reloc_dir->size - 1);
+ reloc_section = NULL;
+
/*
* Copy the executable's sections to their desired offsets
*/
section = context->first_section;
for (i = 0; i < context->num_sections; i++, section++)
{
- if (section->characteristics & 0x02000000)
- /* section has EFI_IMAGE_SCN_MEM_DISCARDABLE attr set */
- continue;
-
sect_size = section->virtual_size;
if (sect_size > section->raw_data_size)
@@ -518,6 +559,30 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
grub_shim_image_address (shim_buffer, context->image_size,
section->virtual_address
+ sect_size - 1);
+
+ /* We do want to process .reloc, but it's often marked
+ * discardable, so we don't want to memcpy it. */
+ if (grub_memcmp (section->name, ".reloc\0\0", 8) == 0) {
+ if (reloc_section) {
+ grub_printf("Image has multiple relocation sections\n");
+ status = GRUB_ERR_BAD_FILE_TYPE;
+ goto fail;
+ }
+ /* If it has nonzero sizes, and our bounds check
+ * made sense, and the VA and size match RelocDir's
+ * versions, then we believe in this section table. */
+ if (section->raw_data_size && section->virtual_size &&
+ base && end &&
+ reloc_base == base &&
+ reloc_base_end == end) {
+ reloc_section = section;
+ }
+ }
+
+ if (section->characteristics & 0x02000000)
+ /* section has EFI_IMAGE_SCN_MEM_DISCARDABLE attr set */
+ continue;
+
if (!base || !end)
{
grub_printf("Invalid section base\n");
@@ -551,10 +616,10 @@ grub_shim_load_image(grub_addr_t addr, grub_ssize_t size,
goto fail;
}
- if (context->reloc_dir->size)
+ if (context->reloc_dir->size && reloc_section)
{
- status = grub_shim_relocate_coff (context, (void *) addr,
- (void *) shim_buffer);
+ status = grub_shim_relocate_coff (context, reloc_section,
+ (void *) addr, (void *) shim_buffer);
if (status != GRUB_ERR_NONE)
{
grub_printf("Relocation failed: [%u]\n", status);
--
1.9.1
|