1/* ----------------------------------------------------------------------- *
2 *
3 *   Copyright 2003-2009 H. Peter Anvin - All Rights Reserved
4 *   Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin
5 *   Copyright 2010 Shao Miller
6 *   Copyright 2010-2012 Michal Soltys
7 *
8 *   Permission is hereby granted, free of charge, to any person
9 *   obtaining a copy of this software and associated documentation
10 *   files (the "Software"), to deal in the Software without
11 *   restriction, including without limitation the rights to use,
12 *   copy, modify, merge, publish, distribute, sublicense, and/or
13 *   sell copies of the Software, and to permit persons to whom
14 *   the Software is furnished to do so, subject to the following
15 *   conditions:
16 *
17 *   The above copyright notice and this permission notice shall
18 *   be included in all copies or substantial portions of the Software.
19 *
20 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 *   OTHER DEALINGS IN THE SOFTWARE.
28 *
29 * ----------------------------------------------------------------------- */
30
31#include <com32.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <stdint.h>
36#include <dprintf.h>
37#include <syslinux/config.h>
38#include "chain.h"
39#include "options.h"
40#include "utility.h"
41#include "partiter.h"
42#include "mangle.h"
43
44static const char cmldr_signature[8] = "cmdcons";
45
46/* Create boot info table: needed when you want to chainload
47 * another version of ISOLINUX (or another bootlaoder that needs
48 * the -boot-info-table switch of mkisofs)
49 * (will only work when run from ISOLINUX)
50 */
51int manglef_isolinux(struct data_area *data)
52{
53    const union syslinux_derivative_info *sdi;
54    unsigned char *isolinux_bin;
55    uint32_t *checksum, *chkhead, *chktail;
56    uint32_t file_lba = 0;
57
58    if (!(opt.file && opt.isolinux))
59	return 0;
60
61    sdi = syslinux_derivative_info();
62
63    if (sdi->c.filesystem != SYSLINUX_FS_ISOLINUX) {
64	error("The isolinux= option is only valid when run from ISOLINUX.");
65	goto bail;
66    }
67
68    /* Boot info table info (integers in little endian format)
69
70       Offset Name         Size      Meaning
71       8      bi_pvd       4 bytes   LBA of primary volume descriptor
72       12     bi_file      4 bytes   LBA of boot file
73       16     bi_length    4 bytes   Boot file length in bytes
74       20     bi_csum      4 bytes   32-bit checksum
75       24     bi_reserved  40 bytes  Reserved
76
77       The 32-bit checksum is the sum of all the 32-bit words in the
78       boot file starting at byte offset 64. All linear block
79       addresses (LBAs) are given in CD sectors (normally 2048 bytes).
80
81       LBA of primary volume descriptor should already be set to 16.
82       */
83
84    isolinux_bin = (unsigned char *)data->data;
85
86    /* Get LBA address of bootfile */
87    file_lba = get_file_lba(opt.file);
88
89    if (file_lba == 0) {
90	error("Failed to find LBA offset of the boot file.");
91	goto bail;
92    }
93    /* Set it */
94    *((uint32_t *) & isolinux_bin[12]) = file_lba;
95
96    /* Set boot file length */
97    *((uint32_t *) & isolinux_bin[16]) = data->size;
98
99    /* Calculate checksum */
100    checksum = (uint32_t *) & isolinux_bin[20];
101    chkhead = (uint32_t *) & isolinux_bin[64];
102    chktail = (uint32_t *) & isolinux_bin[data->size & ~3u];
103    *checksum = 0;
104    while (chkhead < chktail)
105	*checksum += *chkhead++;
106
107    /*
108     * Deal with possible fractional dword at the end;
109     * this *should* never happen...
110     */
111    if (data->size & 3) {
112	uint32_t xword = 0;
113	memcpy(&xword, chkhead, data->size & 3);
114	*checksum += xword;
115    }
116    return 0;
117bail:
118    return -1;
119}
120
121/*
122 * Legacy grub's stage2 chainloading
123 */
124int manglef_grub(const struct part_iter *iter, struct data_area *data)
125{
126    /* Layout of stage2 file (from byte 0x0 to 0x270) */
127    struct grub_stage2_patch_area {
128	/* 0x0 to 0x205 */
129	char unknown[0x206];
130	/* 0x206: compatibility version number major */
131	uint8_t compat_version_major;
132	/* 0x207: compatibility version number minor */
133	uint8_t compat_version_minor;
134
135	/* 0x208: install_partition variable */
136	struct {
137	    /* 0x208: sub-partition in sub-partition part2 */
138	    uint8_t part3;
139	    /* 0x209: sub-partition in top-level partition */
140	    uint8_t part2;
141	    /* 0x20a: top-level partiton number */
142	    uint8_t part1;
143	    /* 0x20b: BIOS drive number (must be 0) */
144	    uint8_t drive;
145	} __attribute__ ((packed)) install_partition;
146
147	/* 0x20c: deprecated (historical reason only) */
148	uint32_t saved_entryno;
149	/* 0x210: stage2_ID: will always be STAGE2_ID_STAGE2 = 0 in stage2 */
150	uint8_t stage2_id;
151	/* 0x211: force LBA */
152	uint8_t force_lba;
153	/* 0x212: version string (will probably be 0.97) */
154	char version_string[5];
155	/* 0x217: config filename */
156	char config_file[89];
157	/* 0x270: start of code (after jump from 0x200) */
158	char codestart[1];
159    } __attribute__ ((packed)) *stage2;
160
161    if (!(opt.file && opt.grub))
162	return 0;
163
164    if (data->size < sizeof *stage2) {
165	error("The file specified by grub=<loader> is too small to be stage2 of GRUB Legacy.");
166	goto bail;
167    }
168    stage2 = data->data;
169
170    /*
171     * Check the compatibility version number to see if we loaded a real
172     * stage2 file or a stage2 file that we support.
173     */
174    if (stage2->compat_version_major != 3
175	    || stage2->compat_version_minor != 2) {
176	error("The file specified by grub=<loader> is not a supported stage2 GRUB Legacy binary.");
177	goto bail;
178    }
179
180    /*
181     * GRUB Legacy wants the partition number in the install_partition
182     * variable, located at offset 0x208 of stage2.
183     * When GRUB Legacy is loaded, it is located at memory address 0x8208.
184     *
185     * It looks very similar to the "boot information format" of the
186     * Multiboot specification:
187     *   http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
188     *
189     *   0x208 = part3: sub-partition in sub-partition part2
190     *   0x209 = part2: sub-partition in top-level partition
191     *   0x20a = part1: top-level partition number
192     *   0x20b = drive: BIOS drive number (must be 0)
193     *
194     * GRUB Legacy doesn't store the BIOS drive number at 0x20b, but at
195     * another location.
196     *
197     * Partition numbers always start from zero.
198     * Unused partition bytes must be set to 0xFF.
199     *
200     * We only care about top-level partition, so we only need to change
201     * "part1" to the appropriate value:
202     *   -1:   whole drive (default) (-1 = 0xFF)
203     *   0-3:  primary partitions
204     *   4-*:  logical partitions
205     */
206    stage2->install_partition.part1 = iter->index - 1;
207
208    /*
209     * Grub Legacy reserves 89 bytes (from 0x8217 to 0x826f) for the
210     * config filename. The filename passed via grubcfg= will overwrite
211     * the default config filename "/boot/grub/menu.lst".
212     */
213    if (opt.grubcfg) {
214	if (strlen(opt.grubcfg) >= sizeof stage2->config_file) {
215	    error("The config filename length can't exceed 88 characters.");
216	    goto bail;
217	}
218
219	strcpy((char *)stage2->config_file, opt.grubcfg);
220    }
221
222    return 0;
223bail:
224    return -1;
225}
226#if 0
227/*
228 * Dell's DRMK chainloading.
229 */
230int manglef_drmk(struct data_area *data)
231{
232    /*
233     * DRMK entry is different than MS-DOS/PC-DOS
234     * A new size, aligned to 16 bytes to ease use of ds:[bp+28].
235     * We only really need 4 new, usable bytes at the end.
236     */
237
238    if (!(opt.file && opt.drmk))
239	return 0;
240
241    uint32_t tsize = (data->size + 19) & 0xfffffff0;
242    const union syslinux_derivative_info *sdi;
243    uint64_t fs_lba;
244
245    sdi = syslinux_derivative_info();
246    /* We should lookup the Syslinux partition offset and use it */
247    fs_lba = *sdi->disk.partoffset;
248
249    /*
250     * fs_lba should be verified against the disk as some DRMK
251     * variants will check and fail if it does not match
252     */
253    dprintf("  fs_lba offset is %d\n", fs_lba);
254    /* DRMK only uses a DWORD */
255    if (fs_lba > 0xffffffff) {
256	error("LBA very large; Only using lower 32 bits; DRMK will probably fail.");
257    }
258    opt.regs.ss = opt.regs.fs = opt.regs.gs = 0;	/* Used before initialized */
259    if (!realloc(data->data, tsize)) {
260	error("Failed to realloc for DRMK.");
261	goto bail;
262    }
263    data->size = tsize;
264    /* ds:bp is assumed by DRMK to be the boot sector */
265    /* offset 28 is the FAT HiddenSectors value */
266    opt.regs.ds = (tsize >> 4) + (opt.fseg - 2);
267    /* "Patch" into tail of the new space */
268    *(uint32_t *)((char*)data->data + tsize - 4) = fs_lba;
269
270    return 0;
271bail:
272    return -1;
273}
274#endif
275/* Adjust BPB common function */
276static int mangle_bpb(const struct part_iter *iter, struct data_area *data, const char *tag)
277{
278    int type = bpb_detect(data->data, tag);
279    int off = drvoff_detect(type);
280
281    /* BPB: hidden sectors 64bit - exFAT only for now */
282    if (type == bpbEXF)
283	    *(uint64_t *) ((char *)data->data + 0x40) = iter->abs_lba;
284    /* BPB: hidden sectors 32bit*/
285    else if (bpbV34 <= type && type <= bpbV70) {
286	if (iter->abs_lba < ~0u)
287	    *(uint32_t *) ((char *)data->data + 0x1c) = iter->abs_lba;
288	else
289	    /* won't really help much, but ... */
290	    *(uint32_t *) ((char *)data->data + 0x1c) = ~0u;
291    /* BPB: hidden sectors 16bit*/
292    } else if (bpbV30 <= type && type <= bpbV32) {
293	if (iter->abs_lba < 0xFFFF)
294	    *(uint16_t *) ((char *)data->data + 0x1c) = iter->abs_lba;
295	else
296	    /* won't really help much, but ... */
297	    *(uint16_t *) ((char *)data->data + 0x1c) = (uint16_t)~0u;
298    }
299
300    /* BPB: legacy geometry */
301    if (bpbV30 <= type && type <= bpbV70) {
302	if (iter->di.cbios)
303	    *(uint32_t *)((char *)data->data + 0x18) = (iter->di.head << 16) | iter->di.spt;
304	else {
305	    if (iter->di.disk & 0x80)
306		*(uint32_t *)((char *)data->data + 0x18) = 0x00FF003F;
307	    else
308		*(uint32_t *)((char *)data->data + 0x18) = 0x00020012;
309	}
310    }
311    /* BPB: drive */
312    if (off >= 0) {
313	*(uint8_t *)((char *)data->data + off) = (opt.swap ? iter->di.disk & 0x80 : iter->di.disk);
314    }
315
316    return 0;
317}
318
319/*
320 * Adjust BPB of a BPB-compatible file
321 */
322int manglef_bpb(const struct part_iter *iter, struct data_area *data)
323{
324    if (!(opt.file && opt.filebpb))
325	return 0;
326
327    return mangle_bpb(iter, data, "file");
328}
329
330/*
331 * Adjust BPB of a sector
332 */
333int mangles_bpb(const struct part_iter *iter, struct data_area *data)
334{
335    if (!(opt.sect && opt.setbpb))
336	return 0;
337
338    return mangle_bpb(iter, data, "sect");
339}
340
341/*
342 * This function performs full BPB patching, analogously to syslinux's
343 * native BSS.
344 */
345int manglesf_bss(struct data_area *sec, struct data_area *fil)
346{
347    int type1, type2;
348    size_t cnt = 0;
349
350    if (!(opt.sect && opt.file && opt.bss))
351	return 0;
352
353    type1 = bpb_detect(fil->data, "bss/file");
354    type2 = bpb_detect(sec->data, "bss/sect");
355
356    if (!type1 || !type2) {
357	error("Couldn't determine the BPB type for option 'bss'.");
358	goto bail;
359    }
360    if (type1 != type2) {
361	error("Option 'bss' can't be used,\n"
362		"when a sector and a file have incompatible BPBs.");
363	goto bail;
364    }
365
366    /* Copy common 2.0 data */
367    memcpy((char *)fil->data + 0x0B, (char *)sec->data + 0x0B, 0x0D);
368
369    /* Copy 3.0+ data */
370    if (type1 <= bpbV30) {
371	cnt = 0x06;
372    } else if (type1 <= bpbV32) {
373	cnt = 0x08;
374    } else if (type1 <= bpbV34) {
375	cnt = 0x0C;
376    } else if (type1 <= bpbV40) {
377	cnt = 0x2E;
378    } else if (type1 <= bpbVNT) {
379	cnt = 0x3C;
380    } else if (type1 <= bpbV70) {
381	cnt = 0x42;
382    } else if (type1 <= bpbEXF) {
383	cnt = 0x60;
384    }
385    memcpy((char *)fil->data + 0x18, (char *)sec->data + 0x18, cnt);
386
387    return 0;
388bail:
389    return -1;
390}
391
392/*
393 * Save sector.
394 */
395int mangles_save(const struct part_iter *iter, const struct data_area *data, void *org)
396{
397    if (!(opt.sect && opt.save))
398	return 0;
399
400    if (memcmp(org, data->data, data->size)) {
401	if (disk_write_sectors(&iter->di, iter->abs_lba, data->data, 1)) {
402	    error("Cannot write the updated sector.");
403	    goto bail;
404	}
405	/* function can be called again */
406	memcpy(org, data->data, data->size);
407    }
408
409    return 0;
410bail:
411    return -1;
412}
413
414/*
415 * To boot the Recovery Console of Windows NT/2K/XP we need to write
416 * the string "cmdcons\0" to memory location 0000:7C03.
417 * Memory location 0000:7C00 contains the bootsector of the partition.
418 */
419int mangles_cmldr(struct data_area *data)
420{
421    if (!(opt.sect && opt.cmldr))
422	return 0;
423
424    memcpy((char *)data->data + 3, cmldr_signature, sizeof cmldr_signature);
425    return 0;
426}
427
428/* Set common registers */
429int mangler_init(const struct part_iter *iter)
430{
431    /* Set initial registry values */
432    if (opt.file) {
433	opt.regs.cs = opt.regs.ds = opt.regs.ss = opt.fseg;
434	opt.regs.ip = opt.fip;
435    } else {
436	opt.regs.cs = opt.regs.ds = opt.regs.ss = opt.sseg;
437	opt.regs.ip = opt.sip;
438    }
439
440    if (opt.regs.ip == 0x7C00 && !opt.regs.cs)
441	opt.regs.esp.l = 0x7C00;
442
443    /* DOS kernels want the drive number in BL instead of DL. Indulge them. */
444    opt.regs.ebx.b[0] = opt.regs.edx.b[0] = iter->di.disk;
445
446    return 0;
447}
448
449/* ds:si & ds:bp */
450int mangler_handover(const struct part_iter *iter, const struct data_area *data)
451{
452    if (opt.file && opt.maps && !opt.hptr) {
453	opt.regs.esi.l = opt.regs.ebp.l = opt.soff;
454	opt.regs.ds = opt.sseg;
455	opt.regs.eax.l = 0;
456    } else if (opt.hand) {
457	/* base is really 0x7be */
458	opt.regs.esi.l = opt.regs.ebp.l = data->base;
459	opt.regs.ds = 0;
460	if (iter->index && iter->type == typegpt)   /* must be iterated and GPT */
461	    opt.regs.eax.l = 0x54504721;	/* '!GPT' */
462	else
463	    opt.regs.eax.l = 0;
464    }
465
466    return 0;
467}
468
469/*
470 * GRLDR of GRUB4DOS wants the partition number in DH:
471 * -1:   whole drive (default)
472 * 0-3:  primary partitions
473 * 4-*:  logical partitions
474 */
475int mangler_grldr(const struct part_iter *iter)
476{
477    if (opt.grldr)
478	opt.regs.edx.b[1] = iter->index - 1;
479
480    return 0;
481}
482
483/*
484 * try to copy values from temporary iterator, if positions match
485 */
486static void mbrcpy(struct part_iter *diter, struct part_iter *siter)
487{
488    if (diter->dos.cebr_lba == siter->dos.cebr_lba &&
489	    diter->di.disk == siter->di.disk) {
490	memcpy(diter->data, siter->data, sizeof(struct disk_dos_mbr));
491    }
492}
493
494static int fliphide(struct part_iter *iter, struct part_iter *miter)
495{
496    struct disk_dos_part_entry *dp;
497    static const uint16_t mask =
498	(1 << 0x01) | (1 << 0x04) | (1 << 0x06) |
499	(1 << 0x07) | (1 << 0x0b) | (1 << 0x0c) | (1 << 0x0e);
500    uint8_t t;
501
502    dp = (struct disk_dos_part_entry *)iter->record;
503    t = dp->ostype;
504
505    if ((t <= 0x1f) && ((mask >> (t & ~0x10u)) & 1)) {
506	/* It's a hideable partition type */
507	if (miter->index == iter->index || opt.hide & HIDE_REV)
508	    t &= ~0x10u;	/* unhide */
509	else
510	    t |= 0x10u;	/* hide */
511    }
512    if (dp->ostype != t) {
513	dp->ostype = t;
514	return -1;
515    }
516    return 0;
517}
518
519/*
520 * miter - iterator we match against
521 * hide bits meaning:
522 * ..| - enable (1) / disable (0)
523 * .|. - all (1) / pri (0)
524 * |.. - unhide (1) / hide (0)
525 */
526int manglepe_hide(struct part_iter *miter)
527{
528    int wb = 0, werr = 0;
529    struct part_iter *iter = NULL;
530    int ridx;
531
532    if (!(opt.hide & HIDE_ON))
533	return 0;
534
535    if (miter->type != typedos) {
536	error("Option '[un]hide[all]' works only for legacy (DOS) partition scheme.");
537	return -1;
538    }
539
540    if (miter->index > 4 && !(opt.hide & HIDE_EXT))
541	warn("Specified partition is logical, so it can't be unhidden without 'unhideall'.");
542
543    if (!(iter = pi_begin(&miter->di, PIF_STEPALL | opt.piflags)))
544	return -1;
545
546    while (!pi_next(iter) && !werr) {
547	ridx = iter->index0;
548	if (!(opt.hide & HIDE_EXT) && ridx > 3)
549	    break;  /* skip when we're constrained to pri only */
550
551	if (iter->index != -1)
552	    wb |= fliphide(iter, miter);
553
554	/*
555	 * we have to update mbr and each extended partition, but only if
556	 * changes (wb) were detected and there was no prior write error (werr)
557	 */
558	if (ridx >= 3 && wb && !werr) {
559	    mbrcpy(miter, iter);
560	    werr |= disk_write_sectors(&iter->di, iter->dos.cebr_lba, iter->data, 1);
561	    wb = 0;
562	}
563    }
564
565    if (iter->status < 0)
566	goto bail;
567
568    /* last update */
569    if (wb && !werr) {
570	mbrcpy(miter, iter);
571	werr |= disk_write_sectors(&iter->di, iter->dos.cebr_lba, iter->data, 1);
572    }
573    if (werr)
574	warn("Failed to write E/MBR during '[un]hide[all]'.");
575
576bail:
577    pi_del(&iter);
578    return 0;
579}
580
581static int updchs(struct part_iter *iter, int ext)
582{
583    struct disk_dos_part_entry *dp;
584    uint32_t ochs1, ochs2, lba;
585
586    dp = (struct disk_dos_part_entry *)iter->record;
587    if (!ext) {
588	/* primary or logical */
589	lba = (uint32_t)iter->abs_lba;
590    } else {
591	/* extended */
592	dp += 1;
593	lba = iter->dos.nebr_lba;
594    }
595    ochs1 = *(uint32_t *)dp->start;
596    ochs2 = *(uint32_t *)dp->end;
597
598    /*
599     * We have to be a bit more careful here in case of 0 start and/or length;
600     * start = 0 would be converted to the beginning of the disk (C/H/S =
601     * 0/0/1) or the [B]EBR, length = 0 would actually set the end CHS to be
602     * lower than the start CHS.
603     *
604     * Both are harmless in case of a hole (and in non-hole case will make
605     * partiter complain about corrupt layout if PIF_STRICT is set), but it
606     * makes everything look silly and not really correct.
607     *
608     * Thus the approach as seen below.
609     */
610
611    if (dp->start_lba || iter->index != -1) {
612	lba2chs(&dp->start, &iter->di, lba, L2C_CADD);
613    } else {
614	memset(&dp->start, 0, sizeof dp->start);
615    }
616
617    if ((dp->start_lba || iter->index != -1) && dp->length) {
618	lba2chs(&dp->end, &iter->di, lba + dp->length - 1, L2C_CADD);
619    } else {
620	memset(&dp->end, 0, sizeof dp->end);
621    }
622
623    return
624	*(uint32_t *)dp->start != ochs1 ||
625	*(uint32_t *)dp->end != ochs2;
626}
627
628/*
629 * miter - iterator we match against
630 */
631int manglepe_fixchs(struct part_iter *miter)
632{
633    int wb = 0, werr = 0;
634    struct part_iter *iter = NULL;
635    int ridx;
636
637    if (!opt.fixchs)
638	return 0;
639
640    if (miter->type != typedos) {
641	error("Option 'fixchs' works only for legacy (DOS) partition scheme.");
642	return -1;
643    }
644
645    if (!(iter = pi_begin(&miter->di, PIF_STEPALL | opt.piflags)))
646	return -1;
647
648    while (!pi_next(iter) && !werr) {
649	ridx = iter->index0;
650
651	wb |= updchs(iter, 0);
652	if (ridx > 3)
653	    wb |= updchs(iter, 1);
654
655	/*
656	 * we have to update mbr and each extended partition, but only if
657	 * changes (wb) were detected and there was no prior write error (werr)
658	 */
659	if (ridx >= 3 && wb && !werr) {
660	    mbrcpy(miter, iter);
661	    werr |= disk_write_sectors(&iter->di, iter->dos.cebr_lba, iter->data, 1);
662	    wb = 0;
663	}
664    }
665
666    if (iter->status < 0)
667	goto bail;
668
669    /* last update */
670    if (wb && !werr) {
671	mbrcpy(miter, iter);
672	werr |= disk_write_sectors(&iter->di, iter->dos.cebr_lba, iter->data, 1);
673    }
674    if (werr)
675	warn("Failed to write E/MBR during 'fixchs'.");
676
677bail:
678    pi_del(&iter);
679    return 0;
680}
681
682/* vim: set ts=8 sts=4 sw=4 noet: */
683