summaryrefslogtreecommitdiffstats
path: root/sys/kern/subr_disk.c
diff options
context:
space:
mode:
authormiod <miod@openbsd.org>2014-07-13 15:32:28 +0000
committermiod <miod@openbsd.org>2014-07-13 15:32:28 +0000
commit26e78ed6574a439d4ec35dcffcbb12dac6e7abcb (patch)
tree3b40bbabf0a2c3b578975782253dada6bbad81f8 /sys/kern/subr_disk.c
parentStop using old n_long and n_short types. (diff)
downloadwireguard-openbsd-26e78ed6574a439d4ec35dcffcbb12dac6e7abcb.tar.xz
wireguard-openbsd-26e78ed6574a439d4ec35dcffcbb12dac6e7abcb.zip
Initial support to read GPT partition tables in the kernel, if option GPT.
Contributed by Markus Mueller; code based upon Bitrig's GPT support, with stricter GPT structures validation and support for alternate header places. ok deraadt@ jsing@ krw@
Diffstat (limited to 'sys/kern/subr_disk.c')
-rw-r--r--sys/kern/subr_disk.c324
1 files changed, 323 insertions, 1 deletions
diff --git a/sys/kern/subr_disk.c b/sys/kern/subr_disk.c
index 95fc03e0e91..3262da52107 100644
--- a/sys/kern/subr_disk.c
+++ b/sys/kern/subr_disk.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: subr_disk.c,v 1.167 2014/07/12 18:43:32 tedu Exp $ */
+/* $OpenBSD: subr_disk.c,v 1.168 2014/07/13 15:32:28 miod Exp $ */
/* $NetBSD: subr_disk.c,v 1.17 1996/03/16 23:17:08 christos Exp $ */
/*
@@ -66,8 +66,16 @@
#include <dev/rndvar.h>
#include <dev/cons.h>
+#include <lib/libz/zlib.h>
+
#include "softraid.h"
+#ifdef DEBUG
+#define DPRINTF(x...) printf(x)
+#else
+#define DPRINTF(x...)
+#endif
+
/*
* A global list of all disks attached to the system. May grow or
* shrink over time.
@@ -530,6 +538,320 @@ notfat:
return (error);
}
+#ifdef GPT
+
+int gpt_chk_hdr(struct gpt_header *);
+int gpt_chk_parts(struct gpt_header *, struct gpt_partition *);
+int get_fstype(struct uuid *);
+
+int
+gpt_chk_hdr(struct gpt_header *gh)
+{
+ u_int32_t orig_gh_csum = gh->gh_csum;
+ gh->gh_csum = 0;
+ gh->gh_csum = crc32(0, (unsigned char *)gh, gh->gh_size);
+
+ if (orig_gh_csum != gh->gh_csum)
+ return (EINVAL);
+
+ return 0;
+}
+
+int
+gpt_chk_parts(struct gpt_header *gh, struct gpt_partition *gp)
+{
+ u_int32_t checksum;
+ checksum = crc32(0, (unsigned char *)gp,
+ gh->gh_part_num * gh->gh_part_size);
+
+ if (checksum != gh->gh_part_csum)
+ return (EINVAL);
+
+ return 0;
+}
+
+int
+get_fstype(struct uuid *uuid_part)
+{
+ static int init = 0;
+ static struct uuid uuid_openbsd, uuid_msdos, uuid_chromefs,
+ uuid_linux, uuid_hfs, uuid_unused;
+ static const uint8_t gpt_uuid_openbsd[] = GPT_UUID_OPENBSD;
+ static const uint8_t gpt_uuid_msdos[] = GPT_UUID_MSDOS;
+ static const uint8_t gpt_uuid_chromerootfs[] = GPT_UUID_CHROMEROOTFS;
+ static const uint8_t gpt_uuid_linux[] = GPT_UUID_LINUX;
+ static const uint8_t gpt_uuid_hfs[] = GPT_UUID_APPLE_HFS;
+ static const uint8_t gpt_uuid_unused[] = GPT_UUID_UNUSED;
+
+ if (init == 0) {
+ uuid_dec_be(gpt_uuid_openbsd, &uuid_openbsd);
+ uuid_dec_be(gpt_uuid_msdos, &uuid_msdos);
+ uuid_dec_be(gpt_uuid_chromerootfs, &uuid_chromefs);
+ uuid_dec_be(gpt_uuid_linux, &uuid_linux);
+ uuid_dec_be(gpt_uuid_hfs, &uuid_hfs);
+ uuid_dec_be(gpt_uuid_unused, &uuid_unused);
+ init = 1;
+ }
+
+ if (!memcmp(uuid_part, &uuid_unused, sizeof(struct uuid)))
+ return FS_UNUSED;
+ else if (!memcmp(uuid_part, &uuid_openbsd, sizeof(struct uuid)))
+ return FS_BSDFFS;
+ else if (!memcmp(uuid_part, &uuid_msdos, sizeof(struct uuid)))
+ return FS_MSDOS;
+ else if (!memcmp(uuid_part, &uuid_chromefs, sizeof(struct uuid)))
+ return FS_EXT2FS;
+ else if (!memcmp(uuid_part, &uuid_linux, sizeof(struct uuid)))
+ return FS_EXT2FS;
+ else if (!memcmp(uuid_part, &uuid_hfs, sizeof(struct uuid)))
+ return FS_HFS;
+ else
+ return FS_OTHER;
+}
+
+/*
+ * If gpt partition table requested, attempt to load it and
+ * find disklabel inside a GPT partition. Return buffer
+ * for use in signalling errors if requested.
+ *
+ * XXX: readgptlabel() is based on readdoslabel(), so they should be merged
+ */
+int
+readgptlabel(struct buf *bp, void (*strat)(struct buf *),
+ struct disklabel *lp, daddr_t *partoffp, int spoofonly)
+{
+ struct gpt_header gh;
+ struct gpt_partition *gp, *gp_tmp;
+ size_t gpsz;
+ struct uuid uuid_part, uuid_openbsd;
+ struct partition *pp;
+
+ daddr_t part_blkno;
+ u_int64_t gptpartoff = 0, gptpartend = DL_GETBEND(lp);
+ int i, altheader = 0, error, n=0, ourpart = -1, offset;
+
+ static const u_int8_t gpt_uuid_openbsd[] = GPT_UUID_OPENBSD;
+ u_int8_t fstype;
+
+ uuid_dec_be(gpt_uuid_openbsd, &uuid_openbsd);
+
+ if (lp->d_secpercyl == 0)
+ return (EINVAL); /* invalid label */
+ if (lp->d_secsize == 0)
+ return (ENOSPC); /* disk too small */
+
+ /*
+ * XXX: We should not trust the primary header and instead
+ * use the last LBA of the disk, as defined in the standard.
+ */
+ for (part_blkno = GPTSECTOR; ; part_blkno = gh.gh_lba_alt,
+ altheader = 1) {
+ /* read header record */
+ bp->b_blkno = DL_BLKTOSEC(lp, part_blkno) * DL_BLKSPERSEC(lp);
+ offset = DL_BLKOFFSET(lp, part_blkno);
+ bp->b_bcount = lp->d_secsize;
+ bp->b_error = 0; /* B_ERROR and b_error may have stale data. */
+ CLR(bp->b_flags, B_READ | B_WRITE | B_DONE | B_ERROR);
+ SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
+ (*strat)(bp);
+ error = biowait(bp);
+
+ if (error) {
+ DPRINTF("error reading from disk\n");
+ /*wrong*/ if (partoffp)
+ /*wrong*/ *partoffp = -1;
+ return (error);
+ }
+
+ bcopy(bp->b_data + offset, &gh, sizeof(gh));
+
+ if (letoh64(gh.gh_sig) != GPTSIGNATURE)
+ return (EINVAL);
+
+ /* we only support version 1.0 */
+ if (letoh32(gh.gh_rev) != GPTREVISION)
+ return (EINVAL);
+
+ if (gpt_chk_hdr(&gh)) {
+ /* header broken, using alternate header */
+ if (altheader) {
+ DPRINTF("alternate header also broken\n");
+ return (EINVAL);
+ }
+
+ if (gh.gh_lba_alt >= DL_GETDSIZE(lp)) {
+ DPRINTF("alternate header's position is "
+ "bogous\n");
+ return (EINVAL);
+ }
+
+ continue;
+ }
+
+ /*
+ * Header size must be greater than or equal to 92 and less
+ * than or equal to the logical block size.
+ */
+ if (letoh32(gh.gh_size) < GPTMINHDRSIZE
+ && letoh32(gh.gh_size) > DEV_BSIZE)
+ return (EINVAL);
+
+ if (letoh64(gh.gh_lba_start) >= DL_GETDSIZE(lp) ||
+ letoh64(gh.gh_lba_end) >= DL_GETDSIZE(lp) ||
+ letoh64(gh.gh_part_lba) >= DL_GETDSIZE(lp))
+ return (EINVAL);
+
+ /*
+ * Size per partition entry shall be 128*(2**n) with n >= 0.
+ * We don't support partition entries larger than block size.
+ */
+ if (letoh32(gh.gh_part_size) % GPTMINPARTSIZE
+ || letoh32(gh.gh_part_size) > DEV_BSIZE
+ || GPT_PARTSPERSEC(&gh) == 0) {
+ DPRINTF("invalid partition size\n");
+ return (EINVAL);
+ }
+
+ /* XXX: we don't support multiples of GPTMINPARTSIZE yet */
+ if (letoh32(gh.gh_part_size) != GPTMINPARTSIZE) {
+ DPRINTF("partition sizes larger than %d bytes are not "
+ "supported", GPTMINPARTSIZE);
+ return (EINVAL);
+ }
+
+ /* read GPT partition entry array */
+ gpsz = letoh32(gh.gh_part_num) * sizeof(struct gpt_partition);
+ gp = malloc(gpsz, M_DEVBUF, M_NOWAIT|M_ZERO);
+ if (gp == NULL)
+ return (ENOMEM);
+
+ /*
+ * XXX: Fails if # of partition entries is no multiple of
+ * GPT_PARTSPERSEC(&gh)
+ */
+ for (i = 0; i < letoh32(gh.gh_part_num) / GPT_PARTSPERSEC(&gh);
+ i++) {
+ part_blkno = letoh64(gh.gh_part_lba) + i;
+ /* read partition record */
+ bp->b_blkno = DL_BLKTOSEC(lp, part_blkno) *
+ DL_BLKSPERSEC(lp);
+ offset = DL_BLKOFFSET(lp, part_blkno);
+ bp->b_bcount = lp->d_secsize;
+ /* B_ERROR and b_error may have stale data. */
+ bp->b_error = 0;
+ CLR(bp->b_flags, B_READ | B_WRITE | B_DONE | B_ERROR);
+ SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
+ (*strat)(bp);
+ error = biowait(bp);
+ if (error) {
+ /*wrong*/ if (partoffp)
+ /*wrong*/ *partoffp = -1;
+ free(gp, M_DEVBUF, gpsz);
+ return (error);
+ }
+
+ bcopy(bp->b_data + offset, gp +
+ i * GPT_PARTSPERSEC(&gh), GPT_PARTSPERSEC(&gh) *
+ sizeof(struct gpt_partition));
+ }
+
+ if (gpt_chk_parts(&gh, gp)) {
+ DPRINTF("partition entries broken, using alternate "
+ "header\n");
+ free(gp, M_DEVBUF, gpsz);
+
+ if (altheader) {
+ DPRINTF("alternate partition entries are also "
+ "broken\n");
+ return (EINVAL);
+ }
+
+ continue;
+ }
+ break;
+ }
+
+ /* find OpenBSD partition */
+ for (gp_tmp = gp, i = 0; i < letoh32(gh.gh_part_num) && ourpart == -1;
+ gp_tmp++, i++) {
+ if (letoh64(gp_tmp->gp_lba_start) > letoh64(gp_tmp->gp_lba_end)
+ || letoh64(gp_tmp->gp_lba_start) < letoh64(gh.gh_lba_start)
+ || letoh64(gp_tmp->gp_lba_end) > letoh64(gh.gh_lba_end))
+ continue; /* entry invalid */
+
+ uuid_dec_le(&gp_tmp->gp_type, &uuid_part);
+ if (!memcmp(&uuid_part, &uuid_openbsd, sizeof(struct uuid))) {
+ ourpart = i; /* found it */
+ }
+
+ /*
+ * In case the disklabel read below fails, we want to
+ * provide a fake label in i-p.
+ */
+ fstype = get_fstype(&uuid_part);
+
+ /*
+ * Don't set fstype/offset/size when just looking for
+ * the offset of the OpenBSD partition. It would
+ * invalidate the disklabel checksum!
+ *
+ * Don't try to spoof more than 8 partitions, i.e.
+ * 'i' -'p'.
+ */
+ if (partoffp || n >= 8)
+ continue;
+
+ pp = &lp->d_partitions[8+n];
+ n++;
+ pp->p_fstype = fstype;
+ DL_SETPOFFSET(pp, letoh64(gp_tmp->gp_lba_start));
+ DL_SETPSIZE(pp, letoh64(gp_tmp->gp_lba_end)
+ - letoh64(gp_tmp->gp_lba_start) + 1);
+ }
+
+ if (ourpart != -1) {
+ /* found our OpenBSD partition, so use it */
+ gp_tmp = &gp[ourpart];
+ gptpartoff = letoh64(gp_tmp->gp_lba_start);
+ gptpartend = letoh64(gp_tmp->gp_lba_end) + 1;
+ } else
+ spoofonly = 1; /* No disklabel to read from disk. */
+
+ if (!partoffp)
+ /* Must not modify *lp when partoffp is set. */
+ lp->d_npartitions = MAXPARTITIONS;
+
+ free(gp, M_DEVBUF, gpsz);
+
+ /* record the OpenBSD partition's placement for the caller */
+ if (partoffp)
+ *partoffp = gptpartoff;
+ else {
+ DL_SETBSTART(lp, gptpartoff);
+ DL_SETBEND(lp, (gptpartend < DL_GETDSIZE(lp)) ? gptpartend :
+ DL_GETDSIZE(lp));
+ }
+
+ /* don't read the on-disk label if we are in spoofed-only mode */
+ if (spoofonly)
+ return (0);
+
+ bp->b_blkno = DL_BLKTOSEC(lp, gptpartoff + DOS_LABELSECTOR) *
+ DL_BLKSPERSEC(lp);
+ offset = DL_BLKOFFSET(lp, gptpartoff + DOS_LABELSECTOR);
+ bp->b_bcount = lp->d_secsize;
+ CLR(bp->b_flags, B_READ | B_WRITE | B_DONE);
+ SET(bp->b_flags, B_BUSY | B_READ | B_RAW);
+ (*strat)(bp);
+ if (biowait(bp))
+ return (bp->b_error);
+
+ /* sub-GPT disklabels are always at a LABELOFFSET of 0 */
+ return checkdisklabel(bp->b_data + offset, lp, gptpartoff, gptpartend);
+}
+
+#endif
+
/*
* Check new disk label for sensibility before setting it.
*/