xref: /openbsd-src/usr.sbin/installboot/i386_installboot.c (revision e5157e49389faebcb42b7237d55fbf096d9c2523)
1 /*	$OpenBSD: i386_installboot.c,v 1.6 2014/10/08 04:26:54 deraadt Exp $	*/
2 /*	$NetBSD: installboot.c,v 1.5 1995/11/17 23:23:50 gwr Exp $ */
3 
4 /*
5  * Copyright (c) 2011 Joel Sing <jsing@openbsd.org>
6  * Copyright (c) 2010 Otto Moerbeek <otto@openbsd.org>
7  * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
8  * Copyright (c) 1997 Michael Shalayeff
9  * Copyright (c) 1994 Paul Kranenburg
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  *      This product includes software developed by Paul Kranenburg.
23  * 4. The name of the author may not be used to endorse or promote products
24  *    derived from this software without specific prior written permission
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37 
38 #define ELFSIZE 32
39 
40 #include <sys/param.h>
41 #include <sys/disklabel.h>
42 #include <sys/dkio.h>
43 #include <sys/ioctl.h>
44 #include <sys/mount.h>
45 #include <sys/reboot.h>
46 #include <sys/stat.h>
47 #include <sys/sysctl.h>
48 #include <sys/time.h>
49 
50 #include <ufs/ufs/dinode.h>
51 #include <ufs/ufs/dir.h>
52 #include <ufs/ffs/fs.h>
53 
54 #include <machine/cpu.h>
55 #include <machine/biosvar.h>
56 
57 #include <elf_abi.h>
58 #include <err.h>
59 #include <fcntl.h>
60 #include <nlist.h>
61 #include <stdlib.h>
62 #include <stdio.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <util.h>
66 
67 #include "installboot.h"
68 #include "i386_installboot.h"
69 
70 char	*bootldr;
71 
72 char	*blkstore;
73 size_t	blksize;
74 
75 struct sym_data pbr_symbols[] = {
76 	{"_fs_bsize_p",	2},
77 	{"_fs_bsize_s",	2},
78 	{"_fsbtodb",	1},
79 	{"_p_offset",	4},
80 	{"_inodeblk",	4},
81 	{"_inodedbl",	4},
82 	{"_nblocks",	2},
83 	{NULL}
84 };
85 
86 static void	devread(int, void *, daddr_t, size_t, char *);
87 static u_int	findopenbsd(int, struct disklabel *);
88 static int	getbootparams(char *, int, struct disklabel *);
89 static char	*loadproto(char *, long *);
90 
91 /*
92  * Read information about /boot's inode and filesystem parameters, then
93  * put biosboot (partition boot record) on the target drive with these
94  * parameters patched in.
95  */
96 
97 void
98 md_init(void)
99 {
100 	stages = 2;
101 	stage1 = "/usr/mdec/biosboot";
102 	stage2 = "/usr/mdec/boot";
103 
104 	bootldr = "/boot";
105 }
106 
107 void
108 md_loadboot(void)
109 {
110 	/* Load prototype boot blocks. */
111 	if ((blkstore = loadproto(stage1, &blksize)) == NULL)
112 		exit(1);
113 
114 	/* XXX - Paranoia: Make sure size is aligned! */
115 	if (blksize & (DEV_BSIZE - 1))
116 		errx(1, "proto %s bad size=%ld", stage1, blksize);
117 
118 	if (blksize > SBSIZE - DEV_BSIZE)
119 		errx(1, "proto bootblocks too big");
120 }
121 
122 void
123 md_installboot(int devfd, char *dev)
124 {
125 	struct disklabel dl;
126 
127 	/* Get and check disklabel. */
128 	if (ioctl(devfd, DIOCGDINFO, &dl) != 0)
129 		err(1, "disklabel: %s", dev);
130 	if (dl.d_magic != DISKMAGIC)
131 		errx(1, "bad disklabel magic=0x%08x", dl.d_magic);
132 
133 	/* Warn on unknown disklabel types. */
134 	if (dl.d_type == 0)
135 		warnx("disklabel type unknown");
136 
137 	bootldr = fileprefix(root, bootldr);
138 	if (verbose)
139 		fprintf(stderr, "%s %s to %s\n",
140 		    (nowrite ? "would copy" : "copying"), stage2, bootldr);
141 	if (!nowrite)
142 		filecopy(stage2, bootldr);
143 
144 	/* Get bootstrap parameters to patch into proto. */
145 	if (getbootparams(bootldr, devfd, &dl) != 0)
146 		exit(1);
147 
148 	/* Write boot blocks to device. */
149 	write_bootblocks(devfd, dev, &dl);
150 }
151 
152 void
153 write_bootblocks(int devfd, char *dev, struct disklabel *dl)
154 {
155 	struct stat	sb;
156 	u_int8_t	*secbuf;
157 	u_int		start = 0;
158 
159 	/* Write patched proto bootblock(s) into the superblock. */
160 	if (fstat(devfd, &sb) < 0)
161 		err(1, "fstat: %s", dev);
162 
163 	if (!S_ISCHR(sb.st_mode))
164 		errx(1, "%s: not a character device", dev);
165 
166 	/* Patch the parameters into the proto bootstrap sector. */
167 	pbr_set_symbols(stage1, blkstore, pbr_symbols);
168 
169 	if (!nowrite) {
170 		/* Sync filesystems (to clean in-memory superblock?). */
171 		sync(); sleep(1);
172 	}
173 
174 	/*
175 	 * Find OpenBSD partition. Floppies are special, getting an
176 	 * everything-in-one /boot starting at sector 0.
177 	 */
178 	if (dl->d_type != DTYPE_FLOPPY) {
179 		start = findopenbsd(devfd, dl);
180 		if (start == (u_int)-1)
181  			errx(1, "no OpenBSD partition");
182 	}
183 
184 	if (verbose)
185 		fprintf(stderr, "%s will be written at sector %u\n",
186 		    stage1, start);
187 
188 	if (start + (blksize / dl->d_secsize) > BOOTBIOS_MAXSEC)
189 		warnx("%s extends beyond sector %u. OpenBSD might not boot.",
190 		    stage1, BOOTBIOS_MAXSEC);
191 
192 	if (!nowrite) {
193 		if (lseek(devfd, (off_t)start * dl->d_secsize, SEEK_SET) < 0)
194 			err(1, "seek boot sector");
195 		secbuf = calloc(1, dl->d_secsize);
196 		if (read(devfd, secbuf, dl->d_secsize) != dl->d_secsize)
197 			err(1, "read boot sector");
198 		bcopy(blkstore, secbuf, blksize);
199 		if (lseek(devfd, (off_t)start * dl->d_secsize, SEEK_SET) < 0)
200 			err(1, "seek bootstrap");
201 		if (write(devfd, secbuf, dl->d_secsize) != dl->d_secsize)
202 			err(1, "write bootstrap");
203 		free(secbuf);
204 	}
205 }
206 
207 u_int
208 findopenbsd(int devfd, struct disklabel *dl)
209 {
210 	struct		dos_mbr mbr;
211 	u_int		mbroff = DOSBBSECTOR;
212 	u_int		mbr_eoff = DOSBBSECTOR; /* Offset of extended part. */
213 	struct		dos_partition *dp;
214 	u_int8_t	*secbuf;
215 	u_int		maxebr = DOS_MAXEBR, nextebr;
216 	int		i;
217 
218 again:
219 	if (!maxebr--) {
220 		if (verbose)
221 			fprintf(stderr, "Traversed more than %d Extended Boot "
222 			    "Records (EBRs)\n", DOS_MAXEBR);
223 		return ((u_int)-1);
224 	}
225 
226 	if (verbose)
227 		fprintf(stderr, "%s boot record (%cBR) at sector %u\n",
228 		    (mbroff == DOSBBSECTOR) ? "master" : "extended",
229 		    (mbroff == DOSBBSECTOR) ? 'M' : 'E', mbroff);
230 
231 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
232 		err(1, NULL);
233 	if (lseek(devfd, (off_t)mbroff * dl->d_secsize, SEEK_SET) < 0 ||
234 	    read(devfd, secbuf, dl->d_secsize) < (ssize_t)sizeof(mbr))
235 		err(4, "can't read boot record");
236 	bcopy(secbuf, &mbr, sizeof(mbr));
237 	free(secbuf);
238 
239 	if (mbr.dmbr_sign != DOSMBR_SIGNATURE)
240 		errx(1, "invalid boot record signature (0x%04X) @ sector %u",
241 		    mbr.dmbr_sign, mbroff);
242 
243 	nextebr = 0;
244 	for (i = 0; i < NDOSPART; i++) {
245 		dp = &mbr.dmbr_parts[i];
246 		if (!dp->dp_size)
247 			continue;
248 
249 		if (verbose)
250 			fprintf(stderr,
251 			    "\tpartition %d: type 0x%02X offset %u size %u\n",
252 			    i, dp->dp_typ, dp->dp_start, dp->dp_size);
253 
254 		if (dp->dp_typ == DOSPTYP_OPENBSD) {
255 			if (dp->dp_start > (dp->dp_start + mbroff))
256 				continue;
257 			return (dp->dp_start + mbroff);
258 		}
259 
260 		if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND ||
261 		    dp->dp_typ == DOSPTYP_EXTENDL)) {
262 			nextebr = dp->dp_start + mbr_eoff;
263 			if (nextebr < dp->dp_start)
264 				nextebr = (u_int)-1;
265 			if (mbr_eoff == DOSBBSECTOR)
266 				mbr_eoff = dp->dp_start;
267 		}
268 	}
269 
270 	if (nextebr && nextebr != (u_int)-1) {
271 		mbroff = nextebr;
272 		goto again;
273 	}
274 
275 	return ((u_int)-1);
276 }
277 
278 /*
279  * Load the prototype boot sector (biosboot) into memory.
280  */
281 static char *
282 loadproto(char *fname, long *size)
283 {
284 	int	fd;
285 	size_t	tdsize;		/* text+data size */
286 	char	*bp;
287 	Elf_Ehdr eh;
288 	Elf_Word phsize;
289 	Elf_Phdr *ph;
290 
291 	if ((fd = open(fname, O_RDONLY)) < 0)
292 		err(1, "%s", fname);
293 
294 	if (read(fd, &eh, sizeof(eh)) != sizeof(eh))
295 		errx(1, "%s: read failed", fname);
296 
297 	if (!IS_ELF(eh))
298 		errx(1, "%s: bad magic: 0x%02x%02x%02x%02x", fname,
299 		    eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
300 		    eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
301 
302 	/*
303 	 * We have to include the exec header in the beginning of
304 	 * the buffer, and leave extra space at the end in case
305 	 * the actual write to disk wants to skip the header.
306 	 */
307 
308 	/* Program load header. */
309 	if (eh.e_phnum != 1)
310 		errx(1, "%s: %u ELF load sections (only support 1)",
311 		    fname, eh.e_phnum);
312 
313 	ph = reallocarray(NULL, eh.e_phnum, sizeof(Elf_Phdr));
314 	if (ph == NULL)
315 		err(1, NULL);
316 	phsize = eh.e_phnum * sizeof(Elf_Phdr);
317 	lseek(fd, eh.e_phoff, SEEK_SET);
318 
319 	if (read(fd, ph, phsize) != phsize)
320 		errx(1, "%s: can't read header", fname);
321 
322 	tdsize = ph->p_filesz;
323 
324 	/*
325 	 * Allocate extra space here because the caller may copy
326 	 * the boot block starting at the end of the exec header.
327 	 * This prevents reading beyond the end of the buffer.
328 	 */
329 	if ((bp = calloc(tdsize, 1)) == NULL)
330 		err(1, NULL);
331 
332 	/* Read the rest of the file. */
333 	lseek(fd, ph->p_offset, SEEK_SET);
334 	if (read(fd, bp, tdsize) != (ssize_t)tdsize)
335 		errx(1, "%s: read failed", fname);
336 
337 	*size = tdsize;	/* not aligned to DEV_BSIZE */
338 
339 	close(fd);
340 	return bp;
341 }
342 
343 static void
344 devread(int fd, void *buf, daddr_t blk, size_t size, char *msg)
345 {
346 	if (lseek(fd, dbtob((off_t)blk), SEEK_SET) != dbtob((off_t)blk))
347 		err(1, "%s: devread: lseek", msg);
348 
349 	if (read(fd, buf, size) != (ssize_t)size)
350 		err(1, "%s: devread: read", msg);
351 }
352 
353 static char sblock[SBSIZE];
354 
355 /*
356  * Read information about /boot's inode, then put this and filesystem
357  * parameters from the superblock into pbr_symbols.
358  */
359 static int
360 getbootparams(char *boot, int devfd, struct disklabel *dl)
361 {
362 	int		fd;
363 	struct stat	dsb, fsb;
364 	struct statfs	fssb;
365 	struct partition *pp;
366 	struct fs	*fs;
367 	char		*buf;
368 	u_int		blk, *ap;
369 	struct ufs1_dinode *ip;
370 	int		ndb;
371 	int		mib[3];
372 	size_t		size;
373 	dev_t		dev;
374 
375 	/*
376 	 * Open 2nd-level boot program and record enough details about
377 	 * where it is on the filesystem represented by `devfd'
378 	 * (inode block, offset within that block, and various filesystem
379 	 * parameters essentially taken from the superblock) for biosboot
380 	 * to be able to load it later.
381 	 */
382 
383 	/* Make sure the (probably new) boot file is on disk. */
384 	sync(); sleep(1);
385 
386 	if ((fd = open(boot, O_RDONLY)) < 0)
387 		err(1, "open: %s", boot);
388 
389 	if (fstatfs(fd, &fssb) != 0)
390 		err(1, "statfs: %s", boot);
391 
392 	if (strncmp(fssb.f_fstypename, "ffs", MFSNAMELEN) &&
393 	    strncmp(fssb.f_fstypename, "ufs", MFSNAMELEN) )
394 		errx(1, "%s: not on an FFS filesystem", boot);
395 
396 #if 0
397 	if (read(fd, &eh, sizeof(eh)) != sizeof(eh))
398 		errx(1, "read: %s", boot);
399 
400 	if (!IS_ELF(eh)) {
401 		errx(1, "%s: bad magic: 0x%02x%02x%02x%02x",
402 		    boot,
403 		    eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
404 		    eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
405 	}
406 #endif
407 
408 	if (fsync(fd) != 0)
409 		err(1, "fsync: %s", boot);
410 
411 	if (fstat(fd, &fsb) != 0)
412 		err(1, "fstat: %s", boot);
413 
414 	if (fstat(devfd, &dsb) != 0)
415 		err(1, "fstat: %d", devfd);
416 
417 	/* Check devices. */
418 	mib[0] = CTL_MACHDEP;
419 	mib[1] = CPU_CHR2BLK;
420 	mib[2] = dsb.st_rdev;
421 	size = sizeof(dev);
422 	if (sysctl(mib, 3, &dev, &size, NULL, 0) >= 0) {
423 		if (fsb.st_dev / MAXPARTITIONS != dev / MAXPARTITIONS)
424 			errx(1, "cross-device install");
425 	}
426 
427 	pp = &dl->d_partitions[DISKPART(fsb.st_dev)];
428 	close(fd);
429 
430 	/* Read superblock. */
431 	devread(devfd, sblock, DL_SECTOBLK(dl, pp->p_offset) + SBLOCK,
432 	    SBSIZE, "superblock");
433 	fs = (struct fs *)sblock;
434 
435 	/* Sanity-check super-block. */
436 	if (fs->fs_magic != FS_MAGIC)
437 		errx(1, "Bad magic number in superblock");
438 	if (fs->fs_inopb <= 0)
439 		err(1, "Bad inopb=%d in superblock", fs->fs_inopb);
440 
441 	/* Read inode. */
442 	if ((buf = malloc(fs->fs_bsize)) == NULL)
443 		err(1, NULL);
444 
445 	blk = fsbtodb(fs, ino_to_fsba(fs, fsb.st_ino));
446 
447 	devread(devfd, buf, DL_SECTOBLK(dl, pp->p_offset) + blk,
448 	    fs->fs_bsize, "inode");
449 	ip = (struct ufs1_dinode *)(buf) + ino_to_fsbo(fs, fsb.st_ino);
450 
451 	/*
452 	 * Have the inode.  Figure out how many filesystem blocks (not disk
453 	 * sectors) there are for biosboot to load.
454 	 */
455 	ndb = howmany(ip->di_size, fs->fs_bsize);
456 	if (ndb <= 0)
457 		errx(1, "No blocks to load");
458 
459 	/*
460 	 * Now set the values that will need to go into biosboot
461 	 * (the partition boot record, a.k.a. the PBR).
462 	 */
463 	sym_set_value(pbr_symbols, "_fs_bsize_p", (fs->fs_bsize / 16));
464 	sym_set_value(pbr_symbols, "_fs_bsize_s", (fs->fs_bsize /
465 	    dl->d_secsize));
466 
467 	/*
468 	 * fs_fsbtodb is the shift to convert fs_fsize to DEV_BSIZE. The
469 	 * ino_to_fsba() return value is the number of fs_fsize units.
470 	 * Calculate the shift to convert fs_fsize into physical sectors,
471 	 * which are added to p_offset to get the sector address BIOS
472 	 * will use.
473 	 *
474 	 * N.B.: ASSUMES fs_fsize is a power of 2 of d_secsize.
475 	 */
476 	sym_set_value(pbr_symbols, "_fsbtodb",
477 	    ffs(fs->fs_fsize / dl->d_secsize) - 1);
478 
479 	sym_set_value(pbr_symbols, "_p_offset", pp->p_offset);
480 	sym_set_value(pbr_symbols, "_inodeblk",
481 	    ino_to_fsba(fs, fsb.st_ino));
482 	ap = ip->di_db;
483 	sym_set_value(pbr_symbols, "_inodedbl",
484 	    ((((char *)ap) - buf) + INODEOFF));
485 	sym_set_value(pbr_symbols, "_nblocks", ndb);
486 
487 	if (verbose) {
488 		fprintf(stderr, "%s is %d blocks x %d bytes\n",
489 		    boot, ndb, fs->fs_bsize);
490 		fprintf(stderr, "fs block shift %u; part offset %u; "
491 		    "inode block %lld, offset %u\n",
492 		    ffs(fs->fs_fsize / dl->d_secsize) - 1,
493 		    pp->p_offset,
494 		    ino_to_fsba(fs, fsb.st_ino),
495 		    (unsigned int)((((char *)ap) - buf) + INODEOFF));
496 	}
497 
498 	return 0;
499 }
500 
501 void
502 sym_set_value(struct sym_data *sym_list, char *sym, u_int32_t value)
503 {
504 	struct sym_data *p;
505 
506 	for (p = sym_list; p->sym_name != NULL; p++) {
507 		if (strcmp(p->sym_name, sym) == 0)
508 			break;
509 	}
510 
511 	if (p->sym_name == NULL)
512 		errx(1, "%s: no such symbol", sym);
513 
514 	p->sym_value = value;
515 	p->sym_set = 1;
516 }
517 
518 /*
519  * Write the parameters stored in sym_list into the in-memory copy of
520  * the prototype biosboot (proto), ready for it to be written to disk.
521  */
522 void
523 pbr_set_symbols(char *fname, char *proto, struct sym_data *sym_list)
524 {
525 	struct sym_data *sym;
526 	struct nlist	*nl;
527 	char		*vp;
528 	u_int32_t	*lp;
529 	u_int16_t	*wp;
530 	u_int8_t	*bp;
531 
532 	for (sym = sym_list; sym->sym_name != NULL; sym++) {
533 		if (!sym->sym_set)
534 			errx(1, "%s not set", sym->sym_name);
535 
536 		/* Allocate space for 2; second is null-terminator for list. */
537 		nl = calloc(2, sizeof(struct nlist));
538 		if (nl == NULL)
539 			err(1, NULL);
540 
541 		nl->n_name = sym->sym_name;
542 
543 		if (nlist_elf32(fname, nl) != 0)
544 			errx(1, "%s: symbol %s not found",
545 			    fname, sym->sym_name);
546 
547 		if (nl->n_type != (N_TEXT))
548 			errx(1, "%s: %s: wrong type (%x)",
549 			    fname, sym->sym_name, nl->n_type);
550 
551 		/* Get a pointer to where the symbol's value needs to go. */
552 		vp = proto + nl->n_value;
553 
554 		switch (sym->sym_size) {
555 		case 4:					/* u_int32_t */
556 			lp = (u_int32_t *) vp;
557 			*lp = sym->sym_value;
558 			break;
559 		case 2:					/* u_int16_t */
560 			if (sym->sym_value >= 0x10000)	/* out of range */
561 				errx(1, "%s: symbol out of range (%u)",
562 				    sym->sym_name, sym->sym_value);
563 			wp = (u_int16_t *) vp;
564 			*wp = (u_int16_t) sym->sym_value;
565 			break;
566 		case 1:					/* u_int16_t */
567 			if (sym->sym_value >= 0x100)	/* out of range */
568 				errx(1, "%s: symbol out of range (%u)",
569 				    sym->sym_name, sym->sym_value);
570 			bp = (u_int8_t *) vp;
571 			*bp = (u_int8_t) sym->sym_value;
572 			break;
573 		default:
574 			errx(1, "%s: bad symbol size %d",
575 			    sym->sym_name, sym->sym_size);
576 			/* NOTREACHED */
577 		}
578 
579 		free(nl);
580 	}
581 }
582