1 /*        $NetBSD: dkwedge_gpt.c,v 1.26 2020/04/11 16:00:34 jdolecek Exp $      */
2 
3 /*-
4  * Copyright (c) 2004 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * EFI GUID Partition Table support for disk wedges
34  */
35 
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: dkwedge_gpt.c,v 1.26 2020/04/11 16:00:34 jdolecek Exp $");
38 
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/proc.h>
42 #include <sys/errno.h>
43 #include <sys/disk.h>
44 #include <sys/vnode.h>
45 #include <sys/buf.h>
46 
47 #include <sys/disklabel_gpt.h>
48 #include <sys/uuid.h>
49 
50 /* UTF-8 encoding stuff */
51 #include <fs/unicode.h>
52 
53 /*
54  * GUID to dkw_ptype mapping information.
55  *
56  * GPT_ENT_TYPE_MS_BASIC_DATA is not suited to mapping.  Aside from being
57  * used for multiple Microsoft file systems, Linux uses it for its own
58  * set of native file systems.  Treating this GUID as unknown seems best.
59  */
60 
61 static const struct {
62           struct uuid ptype_guid;
63           const char *ptype_str;
64 } gpt_ptype_guid_to_str_tab[] = {
65           { GPT_ENT_TYPE_EFI,                     DKW_PTYPE_FAT },
66           { GPT_ENT_TYPE_NETBSD_SWAP,             DKW_PTYPE_SWAP },
67           { GPT_ENT_TYPE_FREEBSD_SWAP,            DKW_PTYPE_SWAP },
68           { GPT_ENT_TYPE_NETBSD_FFS,              DKW_PTYPE_FFS },
69           { GPT_ENT_TYPE_FREEBSD_UFS,             DKW_PTYPE_FFS },
70           { GPT_ENT_TYPE_APPLE_UFS,               DKW_PTYPE_FFS },
71           { GPT_ENT_TYPE_NETBSD_LFS,              DKW_PTYPE_LFS },
72           { GPT_ENT_TYPE_NETBSD_RAIDFRAME,        DKW_PTYPE_RAIDFRAME },
73           { GPT_ENT_TYPE_NETBSD_CCD,              DKW_PTYPE_CCD },
74           { GPT_ENT_TYPE_NETBSD_CGD,              DKW_PTYPE_CGD },
75           { GPT_ENT_TYPE_APPLE_HFS,               DKW_PTYPE_APPLEHFS },
76           { GPT_ENT_TYPE_VMWARE_VMKCORE,                    DKW_PTYPE_VMKCORE },
77           { GPT_ENT_TYPE_VMWARE_VMFS,             DKW_PTYPE_VMFS },
78           { GPT_ENT_TYPE_VMWARE_RESERVED,                   DKW_PTYPE_VMWRESV },
79           { GPT_ENT_TYPE_MS_BASIC_DATA,           DKW_PTYPE_NTFS },
80           { GPT_ENT_TYPE_LINUX_DATA,              DKW_PTYPE_EXT2FS },
81           { GPT_ENT_TYPE_FREEBSD_ZFS,             DKW_PTYPE_ZFS },
82 };
83 
84 static const char *
gpt_ptype_guid_to_str(const struct uuid * guid)85 gpt_ptype_guid_to_str(const struct uuid *guid)
86 {
87           int i;
88 
89           for (i = 0; i < __arraycount(gpt_ptype_guid_to_str_tab); i++) {
90                     if (memcmp(&gpt_ptype_guid_to_str_tab[i].ptype_guid,
91                                  guid, sizeof(*guid)) == 0)
92                               return (gpt_ptype_guid_to_str_tab[i].ptype_str);
93           }
94 
95           return (DKW_PTYPE_UNKNOWN);
96 }
97 
98 static int
gpt_verify_header_crc(struct gpt_hdr * hdr)99 gpt_verify_header_crc(struct gpt_hdr *hdr)
100 {
101           uint32_t crc;
102           int rv;
103 
104           crc = hdr->hdr_crc_self;
105           hdr->hdr_crc_self = 0;
106           rv = le32toh(crc) == crc32(0, (void *)hdr, le32toh(hdr->hdr_size));
107           hdr->hdr_crc_self = crc;
108 
109           return (rv);
110 }
111 
112 static int
dkwedge_discover_gpt(struct disk * pdk,struct vnode * vp)113 dkwedge_discover_gpt(struct disk *pdk, struct vnode *vp)
114 {
115           static const struct uuid ent_type_unused = GPT_ENT_TYPE_UNUSED;
116           static const char gpt_hdr_sig[] = GPT_HDR_SIG;
117           struct dkwedge_info dkw;
118           struct buf *bp;
119           uint32_t secsize;
120           struct gpt_hdr *hdr;
121           struct gpt_ent *ent;
122           uint32_t entries, entsz;
123           daddr_t lba_start, lba_end, lba_table;
124           uint32_t gpe_crc;
125           int error;
126           u_int i;
127           size_t r, n, sz;
128           uint8_t *c;
129 
130           secsize = DEV_BSIZE << pdk->dk_blkshift;
131           bp = geteblk(secsize);
132 
133           /*
134            * Note: We don't bother with a Legacy or Protective MBR
135            * here.  If a GPT is found, then the search stops, and
136            * the GPT is authoritative.
137            */
138 
139           /* Read in the GPT Header. */
140           error = dkwedge_read(pdk, vp, GPT_HDR_BLKNO << pdk->dk_blkshift,
141               bp->b_data, secsize);
142           if (error)
143                     goto out;
144           hdr = bp->b_data;
145 
146           /* Validate it. */
147           if (memcmp(gpt_hdr_sig, hdr->hdr_sig, sizeof(hdr->hdr_sig)) != 0) {
148                     /* XXX Should check at end-of-disk. */
149                     error = ESRCH;
150                     goto out;
151           }
152           if (hdr->hdr_revision != htole32(GPT_HDR_REVISION)) {
153                     /* XXX Should check at end-of-disk. */
154                     error = ESRCH;
155                     goto out;
156           }
157           if (le32toh(hdr->hdr_size) > secsize) {
158                     /* XXX Should check at end-of-disk. */
159                     error = ESRCH;
160                     goto out;
161           }
162           if (gpt_verify_header_crc(hdr) == 0) {
163                     /* XXX Should check at end-of-disk. */
164                     error = ESRCH;
165                     goto out;
166           }
167 
168           /* XXX Now that we found it, should we validate the backup? */
169 
170           {
171                     struct uuid disk_guid;
172                     char guid_str[UUID_STR_LEN];
173                     uuid_dec_le(hdr->hdr_guid, &disk_guid);
174                     uuid_snprintf(guid_str, sizeof(guid_str), &disk_guid);
175                     aprint_verbose("%s: GPT GUID: %s\n", pdk->dk_name, guid_str);
176           }
177 
178           entries = le32toh(hdr->hdr_entries);
179           entsz = roundup(le32toh(hdr->hdr_entsz), 8);
180           if (entsz != sizeof(struct gpt_ent)) {
181                     aprint_error("%s: bogus GPT entry size: %u\n",
182                         pdk->dk_name, le32toh(hdr->hdr_entsz));
183                     error = EINVAL;
184                     goto out;
185           }
186           gpe_crc = le32toh(hdr->hdr_crc_table);
187 
188           /* XXX Clamp entries at 512 for now. */
189           if (entries > 512) {
190                     aprint_error("%s: WARNING: clamping number of GPT entries to "
191                         "512 (was %u)\n", pdk->dk_name, entries);
192                     entries = 512;
193           }
194 
195           lba_start = le64toh(hdr->hdr_lba_start);
196           lba_end = le64toh(hdr->hdr_lba_end);
197           lba_table = le64toh(hdr->hdr_lba_table);
198           if (lba_start < 0 || lba_end < 0 || lba_table < 0) {
199                     aprint_error("%s: GPT block numbers out of range\n",
200                         pdk->dk_name);
201                     error = EINVAL;
202                     goto out;
203           }
204 
205           brelse(bp, 0);
206 
207           sz = roundup(entries * entsz, secsize);
208           bp = geteblk(sz);
209           error = dkwedge_read(pdk, vp, lba_table << pdk->dk_blkshift,
210               bp->b_data, sz);
211           if (error) {
212                     /* XXX Should check alternate location. */
213                     aprint_error("%s: unable to read GPT partition array, "
214                         "error = %d\n", pdk->dk_name, error);
215                     goto out;
216           }
217 
218           if (crc32(0, bp->b_data, entries * entsz) != gpe_crc) {
219                     /* XXX Should check alternate location. */
220                     aprint_error("%s: bad GPT partition array CRC\n",
221                         pdk->dk_name);
222                     error = EINVAL;
223                     goto out;
224           }
225 
226           /*
227            * Walk the partitions, adding a wedge for each type we know about.
228            */
229           for (i = 0; i < entries; i++) {
230                     struct uuid ptype_guid, ent_guid;
231                     const char *ptype;
232                     int j;
233                     char ptype_guid_str[UUID_STR_LEN], ent_guid_str[UUID_STR_LEN];
234 
235                     ent = (struct gpt_ent *)((char *)bp->b_data + (i * entsz));
236 
237                     uuid_dec_le(ent->ent_type, &ptype_guid);
238                     if (memcmp(&ptype_guid, &ent_type_unused,
239                                  sizeof(ptype_guid)) == 0)
240                               continue;
241 
242                     uuid_dec_le(ent->ent_guid, &ent_guid);
243 
244                     uuid_snprintf(ptype_guid_str, sizeof(ptype_guid_str),
245                         &ptype_guid);
246                     uuid_snprintf(ent_guid_str, sizeof(ent_guid_str),
247                         &ent_guid);
248 
249                     memset(&dkw, 0, sizeof(dkw));
250 
251                     /* figure out the type */
252                     ptype = gpt_ptype_guid_to_str(&ptype_guid);
253                     strlcpy(dkw.dkw_ptype, ptype, sizeof(dkw.dkw_ptype));
254 
255                     strlcpy(dkw.dkw_parent, pdk->dk_name, sizeof(dkw.dkw_parent));
256                     dkw.dkw_offset = le64toh(ent->ent_lba_start);
257                     dkw.dkw_size = le64toh(ent->ent_lba_end) - dkw.dkw_offset + 1;
258 
259                     /* XXX Make sure it falls within the disk's data area. */
260 
261                     if (ent->ent_name[0] == 0x0000)
262                               strlcpy(dkw.dkw_wname, ent_guid_str, sizeof(dkw.dkw_wname));
263                     else {
264                               c = dkw.dkw_wname;
265                               r = sizeof(dkw.dkw_wname) - 1;
266                               for (j = 0; j < __arraycount(ent->ent_name)
267                                   && ent->ent_name[j] != 0x0000; j++) {
268                                         n = wput_utf8(c, r, le16toh(ent->ent_name[j]));
269                                         if (n == 0)
270                                                   break;
271                                         c += n; r -= n;
272                               }
273                               *c = '\0';
274                     }
275 
276                     /*
277                      * Try with the partition name first.  If that fails,
278                      * use the GUID string.  If that fails, punt.
279                      */
280                     if ((error = dkwedge_add(&dkw)) == EEXIST &&
281                         strcmp(dkw.dkw_wname, ent_guid_str) != 0) {
282                               char orig[sizeof(dkw.dkw_wname)];
283                               strlcpy(orig, dkw.dkw_wname, sizeof(orig));
284                               strlcpy(dkw.dkw_wname, ent_guid_str, sizeof(dkw.dkw_wname));
285                               error = dkwedge_add(&dkw);
286                               if (!error)
287                                         aprint_error("%s: wedge named '%s' already "
288                                             "existed, using '%s'\n", pdk->dk_name,
289                                             orig, ent_guid_str);
290                     }
291                     if (error == EEXIST)
292                               aprint_error("%s: wedge named '%s' already exists, "
293                                   "manual intervention required\n", pdk->dk_name,
294                                   dkw.dkw_wname);
295                     else if (error)
296                               aprint_error("%s: error %d adding entry %u (%s), "
297                                   "type %s\n", pdk->dk_name, error, i, ent_guid_str,
298                                   ptype_guid_str);
299           }
300           error = 0;
301 
302  out:
303           brelse(bp, 0);
304           return (error);
305 }
306 
307 DKWEDGE_DISCOVERY_METHOD_DECL(GPT, 0, dkwedge_discover_gpt);
308