rewrite.c (12703B)
1 #include "obj/rewrite.h" 2 3 #include <limits.h> 4 #include <string.h> 5 6 #include "obj/coff/coff.h" 7 8 #define PE_DEBUG_DIRECTORY_SIZE 28u 9 10 typedef struct CoffRewriteView { 11 ObjRewrite rw; 12 u8* file_header; 13 u8* optional_header; 14 u8* section_headers; 15 u16 section_count; 16 u16 optional_size; 17 u32 directory_count; 18 u32 size_of_headers; 19 u32 symbol_table_off; 20 u32 symbol_count; 21 u8* string_table; 22 u32 string_table_size; 23 } CoffRewriteView; 24 25 static KitStatus coff_bad(CoffRewriteView* v, const char* detail) { 26 kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked PE image (%s)", 27 KIT_SLICE_ARG(v->rw.name), detail); 28 return KIT_MALFORMED; 29 } 30 31 static u8* coff_directory(CoffRewriteView* v, u32 index) { 32 u64 off = 112u + (u64)index * COFF_DATA_DIRECTORY_SIZE; 33 if (index >= v->directory_count || off + COFF_DATA_DIRECTORY_SIZE > 34 v->optional_size) 35 return NULL; 36 return v->optional_header + (size_t)off; 37 } 38 39 static u8* coff_section(CoffRewriteView* v, u32 index) { 40 if (index >= v->section_count) return NULL; 41 return v->section_headers + (size_t)index * COFF_SECTION_HEADER_SIZE; 42 } 43 44 /* Translate a PE RVA range to its complete on-disk representation. Header 45 * RVAs are file offsets; section RVAs are bounded by SizeOfRawData because 46 * zero-fill tails have no bytes to rewrite. */ 47 static int coff_rva_file_range(CoffRewriteView* v, u32 rva, u32 size, 48 u64* off_out) { 49 u32 i; 50 if ((u64)rva + size > UINT32_MAX + 1ull) return 0; 51 if (rva < v->size_of_headers) { 52 if ((u64)rva + size > v->size_of_headers || 53 !obj_rw_range(v->rw.len, rva, size)) 54 return 0; 55 *off_out = rva; 56 return 1; 57 } 58 for (i = 0; i < v->section_count; ++i) { 59 const u8* sec = coff_section(v, i); 60 u32 va = obj_rw_u32(sec + 12, 0); 61 u32 raw_size = obj_rw_u32(sec + 16, 0); 62 u32 raw_off = obj_rw_u32(sec + 20, 0); 63 u64 delta; 64 if (rva < va) continue; 65 delta = (u64)rva - va; 66 if (delta > raw_size || size > (u64)raw_size - delta) continue; 67 if (!obj_rw_range(v->rw.len, (u64)raw_off + delta, size)) return 0; 68 *off_out = (u64)raw_off + delta; 69 return 1; 70 } 71 return 0; 72 } 73 74 static int coff_name_prefix(const u8* name, size_t name_len, 75 const char* prefix) { 76 size_t n = strlen(prefix); 77 return n <= name_len && memcmp(name, prefix, n) == 0; 78 } 79 80 static KitStatus coff_section_name(CoffRewriteView* v, const u8* sec, 81 const u8** name_out, 82 size_t* name_len_out) { 83 const u8* raw = sec; 84 size_t len = 0; 85 u32 off = 0; 86 u32 i; 87 if (raw[0] != '/') { 88 while (len < 8u && raw[len]) ++len; 89 *name_out = raw; 90 *name_len_out = len; 91 return KIT_OK; 92 } 93 94 /* Linked PE images produced by LLVM use COFF's /<decimal> string-table 95 * convention for DWARF section names even though the PE specification only 96 * blesses it for relocatable objects. Resolve it here so a real external 97 * producer's .debug_* sections are not mistaken for ordinary load data. */ 98 if (raw[1] < '0' || raw[1] > '9') return coff_bad(v, "section name"); 99 for (i = 1; i < 8u && raw[i]; ++i) { 100 u32 digit; 101 if (raw[i] < '0' || raw[i] > '9') return coff_bad(v, "section name"); 102 digit = (u32)(raw[i] - '0'); 103 if (off > (UINT32_MAX - digit) / 10u) 104 return coff_bad(v, "section name"); 105 off = off * 10u + digit; 106 } 107 if (!v->string_table || off < 4u || off >= v->string_table_size) 108 return coff_bad(v, "section name"); 109 while (off + len < v->string_table_size && v->string_table[off + len]) 110 ++len; 111 if (off + len == v->string_table_size) 112 return coff_bad(v, "section name"); 113 *name_out = v->string_table + off; 114 *name_len_out = len; 115 return KIT_OK; 116 } 117 118 static KitStatus coff_debug_section(CoffRewriteView* v, const u8* sec, 119 int* debug_out) { 120 const u8* name; 121 size_t name_len; 122 KitStatus st = coff_section_name(v, sec, &name, &name_len); 123 if (st != KIT_OK) return st; 124 *debug_out = coff_name_prefix(name, name_len, ".debug") || 125 coff_name_prefix(name, name_len, ".zdebug") || 126 coff_name_prefix(name, name_len, ".stab"); 127 return KIT_OK; 128 } 129 130 static KitStatus coff_validate(CoffRewriteView* v, const KitContext* ctx, 131 KitSlice name, const KitSlice* input) { 132 KitStatus st; 133 u32 peoff, i, strings_size; 134 u64 sections_size, symbols_size, strings_off; 135 memset(v, 0, sizeof *v); 136 st = obj_rewrite_init(&v->rw, ctx, name, input); 137 if (st != KIT_OK) return st; 138 if (v->rw.len < COFF_DOS_HEADER_SIZE || 139 obj_rw_u16(v->rw.bytes, 0) != IMAGE_DOS_SIGNATURE) 140 return coff_bad(v, "DOS header"); 141 peoff = obj_rw_u32(v->rw.bytes + 0x3cu, 0); 142 if (!obj_rw_range(v->rw.len, peoff, 143 4u + COFF_FILE_HEADER_SIZE) || 144 obj_rw_u32(v->rw.bytes + peoff, 0) != IMAGE_NT_SIGNATURE) 145 return coff_bad(v, "PE header"); 146 v->file_header = v->rw.bytes + peoff + 4u; 147 v->section_count = obj_rw_u16(v->file_header + 2, 0); 148 v->optional_size = obj_rw_u16(v->file_header + 16, 0); 149 if (v->optional_size == 0) { 150 kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image", 151 KIT_SLICE_ARG(name)); 152 return KIT_INVALID; 153 } 154 if (v->optional_size < 112u || 155 !obj_rw_range(v->rw.len, (u64)peoff + 24u, v->optional_size)) 156 return coff_bad(v, "optional header"); 157 v->optional_header = v->file_header + COFF_FILE_HEADER_SIZE; 158 if (obj_rw_u16(v->optional_header, 0) != 159 IMAGE_NT_OPTIONAL_HDR64_MAGIC) 160 return coff_bad(v, "PE32+ optional header"); 161 v->size_of_headers = obj_rw_u32(v->optional_header + 60, 0); 162 v->directory_count = obj_rw_u32(v->optional_header + 108, 0); 163 if (v->directory_count > COFF_NUM_DATA_DIRECTORIES) 164 v->directory_count = COFF_NUM_DATA_DIRECTORIES; 165 if ((u64)112u + (u64)v->directory_count * COFF_DATA_DIRECTORY_SIZE > 166 v->optional_size) 167 return coff_bad(v, "data directories"); 168 v->section_headers = v->optional_header + v->optional_size; 169 sections_size = (u64)v->section_count * COFF_SECTION_HEADER_SIZE; 170 if (!obj_rw_range(v->rw.len, 171 (u64)(v->section_headers - v->rw.bytes), sections_size)) 172 return coff_bad(v, "section table"); 173 for (i = 0; i < v->section_count; ++i) { 174 const u8* sec = coff_section(v, i); 175 u32 raw_size = obj_rw_u32(sec + 16, 0); 176 u32 raw_off = obj_rw_u32(sec + 20, 0); 177 if (raw_size && !obj_rw_range(v->rw.len, raw_off, raw_size)) 178 return coff_bad(v, "section contents"); 179 } 180 v->symbol_table_off = obj_rw_u32(v->file_header + 8, 0); 181 v->symbol_count = obj_rw_u32(v->file_header + 12, 0); 182 if (!v->symbol_table_off && !v->symbol_count) return KIT_OK; 183 if (!v->symbol_table_off || 184 v->symbol_count > UINT32_MAX / COFF_SYMBOL_SIZE) 185 return coff_bad(v, "COFF symbol table"); 186 symbols_size = (u64)v->symbol_count * COFF_SYMBOL_SIZE; 187 strings_off = (u64)v->symbol_table_off + symbols_size; 188 if (!obj_rw_range(v->rw.len, v->symbol_table_off, symbols_size) || 189 !obj_rw_range(v->rw.len, strings_off, 4u)) 190 return coff_bad(v, "COFF symbol table"); 191 strings_size = obj_rw_u32(v->rw.bytes + (size_t)strings_off, 0); 192 if (strings_size < 4u || 193 !obj_rw_range(v->rw.len, strings_off, strings_size)) 194 return coff_bad(v, "COFF string table"); 195 v->string_table = v->rw.bytes + (size_t)strings_off; 196 v->string_table_size = strings_size; 197 return KIT_OK; 198 } 199 200 static KitStatus coff_check_signature(CoffRewriteView* v, 201 const KitLinkedRewriteOptions* opts) { 202 u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_SECURITY); 203 u32 off, size; 204 if (!dir) return KIT_OK; 205 off = obj_rw_u32(dir, 0); 206 size = obj_rw_u32(dir + 4, 0); 207 if (!off && !size) return KIT_OK; 208 if (!off || !size || !obj_rw_range(v->rw.len, off, size)) 209 return coff_bad(v, "certificate table"); 210 v->rw.report.had_signature = true; 211 if (!opts->remove_signature) { 212 kit_ctx_diagf(v->rw.ctx, 213 "%.*s: signed PE image is unchanged; pass " 214 "--remove-signature and sign the result again", 215 KIT_SLICE_ARG(v->rw.name)); 216 return KIT_UNSUPPORTED; 217 } 218 memset(v->rw.bytes + off, 0, size); 219 memset(dir, 0, COFF_DATA_DIRECTORY_SIZE); 220 v->rw.report.removed_signature = true; 221 return KIT_OK; 222 } 223 224 static KitStatus coff_strip_debug_directory(CoffRewriteView* v) { 225 u8* dir = coff_directory(v, IMAGE_DIRECTORY_ENTRY_DEBUG); 226 u32 rva, size, i; 227 u64 off; 228 if (!dir) return KIT_OK; 229 rva = obj_rw_u32(dir, 0); 230 size = obj_rw_u32(dir + 4, 0); 231 if (!rva && !size) return KIT_OK; 232 if (!rva || !size || size % PE_DEBUG_DIRECTORY_SIZE != 0 || 233 !coff_rva_file_range(v, rva, size, &off)) 234 return coff_bad(v, "debug directory"); 235 for (i = 0; i < size / PE_DEBUG_DIRECTORY_SIZE; ++i) { 236 const u8* entry = v->rw.input + (size_t)off + 237 (size_t)i * PE_DEBUG_DIRECTORY_SIZE; 238 u32 data_size = obj_rw_u32(entry + 16, 0); 239 u32 data_off = obj_rw_u32(entry + 24, 0); 240 if (data_size) { 241 if (!data_off || !obj_rw_range(v->rw.len, data_off, data_size)) 242 return coff_bad(v, "debug payload"); 243 memset(v->rw.bytes + data_off, 0, data_size); 244 } 245 } 246 memset(v->rw.bytes + (size_t)off, 0, size); 247 memset(dir, 0, COFF_DATA_DIRECTORY_SIZE); 248 ++v->rw.report.removed_debug_sections; 249 return KIT_OK; 250 } 251 252 static KitStatus coff_strip_debug_sections(CoffRewriteView* v) { 253 u32 i; 254 for (i = 0; i < v->section_count; ++i) { 255 u8* sec = coff_section(v, i); 256 u32 raw_size, raw_off; 257 int debug; 258 KitStatus st = coff_debug_section(v, sec, &debug); 259 if (st != KIT_OK) return st; 260 if (!debug) continue; 261 raw_size = obj_rw_u32(sec + 16, 0); 262 raw_off = obj_rw_u32(sec + 20, 0); 263 if (raw_size) memset(v->rw.bytes + raw_off, 0, raw_size); 264 /* PE loaders validate the complete mapped section table. Removing a 265 * header in place (or leaving a zero-sized hole before later sections) 266 * makes otherwise-valid images unloadable. Keep the ordinal, RVA, raw 267 * span, and permissions intact, but zero the payload and replace any 268 * short or string-table-backed debug name with an inline neutral name. */ 269 memcpy(sec, ".discard", 8u); 270 ++v->rw.report.removed_debug_sections; 271 } 272 return KIT_OK; 273 } 274 275 static KitStatus coff_strip_symbol_table(CoffRewriteView* v) { 276 u32 off = v->symbol_table_off; 277 u32 count = v->symbol_count; 278 u64 symbols_size, total; 279 u32 i; 280 int needs_strings = 0; 281 if (!off && !count) return KIT_OK; 282 symbols_size = (u64)count * COFF_SYMBOL_SIZE; 283 total = symbols_size + v->string_table_size; 284 for (i = 0; i < v->section_count; ++i) { 285 const u8* sec = coff_section(v, i); 286 if (sec[0] == '/') { 287 const u8* resolved; 288 size_t resolved_len; 289 KitStatus st = coff_section_name(v, sec, &resolved, &resolved_len); 290 if (st != KIT_OK) return st; 291 (void)resolved; 292 (void)resolved_len; 293 needs_strings = 1; 294 } 295 } 296 if (needs_strings) { 297 /* NumberOfSymbols controls where readers locate the string table. Move it 298 * over the removed symbol records so /offset section names keep resolving 299 * with a zero symbol count. The fixed file extent remains transactional. */ 300 memmove(v->rw.bytes + off, v->string_table, v->string_table_size); 301 memset(v->rw.bytes + off + v->string_table_size, 0, 302 (size_t)total - v->string_table_size); 303 v->string_table = v->rw.bytes + off; 304 } else { 305 memset(v->rw.bytes + off, 0, (size_t)total); 306 obj_rw_put_u32(v->file_header + 8, 0, 0); 307 v->string_table = NULL; 308 v->string_table_size = 0; 309 } 310 obj_rw_put_u32(v->file_header + 12, 0, 0); 311 v->rw.report.removed_symbols += count; 312 return KIT_OK; 313 } 314 315 KitStatus obj_rewrite_coff(const KitContext* ctx, KitSlice name, 316 const KitSlice* input, 317 const KitLinkedRewriteOptions* opts, 318 KitWriter* out, 319 KitLinkedRewriteReport* report_out) { 320 CoffRewriteView v; 321 KitStatus st = coff_validate(&v, ctx, name, input); 322 if (st == KIT_OK) st = coff_check_signature(&v, opts); 323 if (st == KIT_OK) st = coff_strip_debug_directory(&v); 324 if (st == KIT_OK) st = coff_strip_debug_sections(&v); 325 if (st == KIT_OK && opts->strip_level == KIT_LINKED_STRIP_ALL) 326 st = coff_strip_symbol_table(&v); 327 if (st == KIT_OK) { 328 u16 characteristics = obj_rw_u16(v.file_header + 18, 0); 329 obj_rw_put_u16(v.file_header + 18, 330 (u16)(characteristics | IMAGE_FILE_DEBUG_STRIPPED), 0); 331 st = obj_rewrite_commit(&v.rw, out, report_out); 332 } 333 if (st != KIT_OK) obj_rewrite_discard(&v.rw); 334 return st; 335 }