1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
23 */
24
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <libintl.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34
35 #include "bblk_einfo.h"
36 #include "boot_utils.h"
37
38 bblk_hash_t bblk_no_hash = {BBLK_NO_HASH, 0, "(no hash)", NULL};
39 bblk_hash_t bblk_md5_hash = {BBLK_HASH_MD5, 0x10, "MD5", md5_calc};
40
41 bblk_hash_t *bblk_hash_list[BBLK_HASH_TOT] = {
42 &bblk_no_hash,
43 &bblk_md5_hash
44 };
45
46 /*
47 * einfo_compare_dotted_version()
48 * Compares two strings with an arbitrary long number of dot-separated numbers.
49 * Returns: 0 - if the version numbers are equal
50 * 1 - if str1 version number is more recent than str2
51 * 2 - if str2 version number is more recent than str1
52 * -1 - if an error occurred
53 *
54 * Comparison is done field by field, by retrieving an unsigned integer value,
55 * (missing fields are assumed as 0, but explict zeroes take precedence) so:
56 * 4.1.2.11 > 4.1.2.2 > 4.1.2.0 > 4.1.2
57 *
58 * where ">" means "more recent than".
59 */
60 static int
einfo_compare_dotted_version(const char * str1,const char * str2)61 einfo_compare_dotted_version(const char *str1, const char *str2)
62 {
63 int retval = 0;
64 char *verstr1, *verstr2, *freeptr1, *freeptr2;
65 char *parsep1, *parsep2;
66 unsigned int val_str1, val_str2;
67
68 freeptr1 = verstr1 = strdup(str1);
69 freeptr2 = verstr2 = strdup(str2);
70 if (verstr1 == NULL || verstr2 == NULL) {
71 retval = -1;
72 goto out;
73 }
74
75 while (verstr1 != NULL && verstr2 != NULL) {
76 parsep1 = strsep(&verstr1, ".");
77 parsep2 = strsep(&verstr2, ".");
78
79 val_str1 = atoi(parsep1);
80 val_str2 = atoi(parsep2);
81
82 if (val_str1 > val_str2) {
83 retval = 1;
84 goto out;
85 }
86
87 if (val_str2 > val_str1) {
88 retval = 2;
89 goto out;
90 }
91 }
92
93 /* Common portion of the version string is equal. */
94 if (verstr1 == NULL && verstr2 != NULL)
95 retval = 2;
96 if (verstr2 == NULL && verstr1 != NULL)
97 retval = 1;
98
99 out:
100 free(freeptr1);
101 free(freeptr2);
102 return (retval);
103 }
104
105 /*
106 * einfo_compare_timestamps()
107 * Currently, timestamp is in %Y%m%dT%H%M%SZ format in UTC, which means that
108 * we can simply do a lexicographic comparison to know which one is the most
109 * recent.
110 *
111 * Returns: 0 - if timestamps coincide
112 * 1 - if the timestamp in str1 is more recent
113 * 2 - if the timestamp in str2 is more recent
114 */
115 static int
einfo_compare_timestamps(const char * str1,const char * str2)116 einfo_compare_timestamps(const char *str1, const char *str2)
117 {
118 int retval;
119
120 retval = strcmp(str1, str2);
121 if (retval > 0)
122 retval = 1;
123 if (retval < 0)
124 retval = 2;
125
126 return (retval);
127 }
128
129 /*
130 * einfo_compare_version()
131 * Given two extended versions, compare the two and returns which one is more
132 * "recent". Comparison is based on dotted version number fields and a
133 * timestamp.
134 *
135 * Returns: -1 - on error
136 * 0 - if the two versions coincide
137 * 1 - if the version in str1 is more recent
138 * 2 - if the version in str2 is more recent
139 */
140 static int
einfo_compare_version(const char * str1,const char * str2)141 einfo_compare_version(const char *str1, const char *str2)
142 {
143 int retval = 0;
144 char *verstr1, *verstr2, *freeptr1, *freeptr2;
145 char *parsep1, *parsep2;
146
147 freeptr1 = verstr1 = strdup(str1);
148 freeptr2 = verstr2 = strdup(str2);
149 if (verstr1 == NULL || verstr2 == NULL) {
150 retval = -1;
151 goto out;
152 }
153
154 parsep1 = verstr1;
155 parsep2 = verstr2;
156
157 while (parsep1 != NULL && parsep2 != NULL) {
158 parsep1 = strsep(&verstr1, ",:-");
159 parsep2 = strsep(&verstr2, ",:-");
160
161 /* verstr1 or verstr2 will be NULL before parsep1 or parsep2. */
162 if (verstr1 == NULL || verstr2 == NULL) {
163 retval = einfo_compare_timestamps(parsep1, parsep2);
164 goto out;
165 }
166
167 retval = einfo_compare_dotted_version(parsep1, parsep2);
168 if (retval == 0)
169 continue;
170 else
171 goto out;
172 }
173 out:
174 free(freeptr1);
175 free(freeptr2);
176 return (retval);
177 }
178
179 /*
180 * print_einfo()
181 *
182 * Print the extended information contained into the pointed structure.
183 * 'bufsize' specifies the real size of the structure, since str_off and
184 * hash_off need to point somewhere past the header.
185 */
186 void
print_einfo(uint8_t flags,bblk_einfo_t * einfo,unsigned long bufsize)187 print_einfo(uint8_t flags, bblk_einfo_t *einfo, unsigned long bufsize)
188 {
189 int i = 0;
190 char *version;
191 boolean_t has_hash = B_FALSE;
192 unsigned char *hash;
193
194 if (einfo->str_off + einfo->str_size > bufsize) {
195 (void) fprintf(stdout, gettext("String offset %d is beyond the "
196 "buffer size\n"), einfo->str_off);
197 return;
198 }
199
200 version = (char *)einfo + einfo->str_off;
201 if (einfo->hash_type != BBLK_NO_HASH &&
202 einfo->hash_type < BBLK_HASH_TOT) {
203 if (einfo->hash_off + einfo->hash_size > bufsize) {
204 (void) fprintf(stdout, gettext("Warning: hashing "
205 "present but hash offset %d is beyond the buffer "
206 "size\n"), einfo->hash_off);
207 has_hash = B_FALSE;
208 } else {
209 hash = (unsigned char *)einfo + einfo->hash_off;
210 has_hash = B_TRUE;
211 }
212 }
213
214 if (flags & EINFO_PRINT_HEADER) {
215 (void) fprintf(stdout, "Boot Block Extended Info Header:\n");
216 (void) fprintf(stdout, "\tmagic: ");
217 for (i = 0; i < EINFO_MAGIC_SIZE; i++)
218 (void) fprintf(stdout, "%c", einfo->magic[i]);
219 (void) fprintf(stdout, "\n");
220 (void) fprintf(stdout, "\tversion: %d\n", einfo->version);
221 (void) fprintf(stdout, "\tflags: %x\n", einfo->flags);
222 (void) fprintf(stdout, "\textended version string offset: %d\n",
223 einfo->str_off);
224 (void) fprintf(stdout, "\textended version string size: %d\n",
225 einfo->str_size);
226 (void) fprintf(stdout, "\thashing type: %d (%s)\n",
227 einfo->hash_type, has_hash ?
228 bblk_hash_list[einfo->hash_type]->name : "nil");
229 (void) fprintf(stdout, "\thash offset: %d\n", einfo->hash_off);
230 (void) fprintf(stdout, "\thash size: %d\n", einfo->hash_size);
231 }
232
233 if (flags & EINFO_EASY_PARSE) {
234 (void) fprintf(stdout, "%s\n", version);
235 } else {
236 (void) fprintf(stdout, "Extended version string: %s\n",
237 version);
238 if (has_hash) {
239 (void) fprintf(stdout, "%s hash: ",
240 bblk_hash_list[einfo->hash_type]->name);
241 } else {
242 (void) fprintf(stdout, "No hashing available\n");
243 }
244 }
245
246 if (has_hash) {
247 for (i = 0; i < einfo->hash_size; i++) {
248 (void) fprintf(stdout, "%02x", hash[i]);
249 }
250 (void) fprintf(stdout, "\n");
251 }
252 }
253
254 static int
compute_hash(bblk_hs_t * hs,unsigned char * dest,bblk_hash_t * hash)255 compute_hash(bblk_hs_t *hs, unsigned char *dest, bblk_hash_t *hash)
256 {
257 if (hs == NULL || dest == NULL || hash == NULL)
258 return (-1);
259
260 hash->compute_hash(dest, hs->src_buf, hs->src_size);
261 return (0);
262 }
263
264 int
prepare_and_write_einfo(unsigned char * dest,char * infostr,bblk_hs_t * hs,uint32_t maxsize,uint32_t * used_space)265 prepare_and_write_einfo(unsigned char *dest, char *infostr, bblk_hs_t *hs,
266 uint32_t maxsize, uint32_t *used_space)
267 {
268 uint16_t hash_size;
269 uint32_t hash_off;
270 unsigned char *data;
271 bblk_einfo_t *einfo = (bblk_einfo_t *)dest;
272 bblk_hash_t *hashinfo = bblk_hash_list[BBLK_DEFAULT_HASH];
273
274 /*
275 * 'dest' might be both containing the buffer we want to hash and
276 * containing our einfo structure: delay any update of it after the
277 * hashing has been calculated.
278 */
279 hash_size = hashinfo->size;
280 hash_off = sizeof (bblk_einfo_t);
281
282 if (hash_off + hash_size > maxsize) {
283 (void) fprintf(stderr, gettext("Unable to add extended info, "
284 "not enough space\n"));
285 return (-1);
286 }
287
288 data = dest + hash_off;
289 if (compute_hash(hs, data, hashinfo) < 0) {
290 (void) fprintf(stderr, gettext("%s hash operation failed\n"),
291 hashinfo->name);
292 einfo->hash_type = bblk_no_hash.type;
293 einfo->hash_size = bblk_no_hash.size;
294 } else {
295 einfo->hash_type = hashinfo->type;
296 einfo->hash_size = hashinfo->size;
297 }
298
299 (void) memcpy(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE);
300 einfo->version = BBLK_EINFO_VERSION;
301 einfo->flags = 0;
302 einfo->hash_off = hash_off;
303 einfo->hash_size = hash_size;
304 einfo->str_off = einfo->hash_off + einfo->hash_size + 1;
305
306 if (infostr == NULL) {
307 (void) fprintf(stderr, gettext("Unable to add extended info, "
308 "string is empty\n"));
309 return (-1);
310 }
311 einfo->str_size = strlen(infostr);
312
313 if (einfo->str_off + einfo->str_size > maxsize) {
314 (void) fprintf(stderr, gettext("Unable to add extended info, "
315 "not enough space\n"));
316 return (-1);
317 }
318
319 data = dest + einfo->str_off;
320 (void) memcpy(data, infostr, einfo->str_size);
321 *used_space = einfo->str_off + einfo->str_size;
322
323 return (0);
324 }
325
326 /*
327 * einfo_should_update()
328 * Given information on the boot block currently on disk (disk_einfo) and
329 * information on the supplied boot block (hs for hashing, verstr as the
330 * associated version string) decide if an update of the on-disk boot block
331 * is necessary or not.
332 */
333 boolean_t
einfo_should_update(bblk_einfo_t * disk_einfo,bblk_hs_t * hs,char * verstr)334 einfo_should_update(bblk_einfo_t *disk_einfo, bblk_hs_t *hs, char *verstr)
335 {
336 bblk_hash_t *hashing;
337 unsigned char *disk_hash;
338 unsigned char *local_hash;
339 char *disk_version;
340 int retval;
341
342 if (disk_einfo == NULL)
343 return (B_TRUE);
344
345 if (memcmp(disk_einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE) != 0)
346 return (B_TRUE);
347
348 if (disk_einfo->version < BBLK_EINFO_VERSION)
349 return (B_TRUE);
350
351 disk_version = einfo_get_string(disk_einfo);
352 retval = einfo_compare_version(verstr, disk_version);
353 /*
354 * If something goes wrong or if the on-disk version is more recent
355 * do not update the bootblock.
356 */
357 if (retval == -1 || retval == 2)
358 return (B_FALSE);
359
360 /*
361 * If we got here it means that the two version strings are either
362 * equal or the new bootblk binary is more recent. In order to save
363 * some needless writes let's use the hash to determine if an update
364 * is really necessary.
365 */
366 if (disk_einfo->hash_type == bblk_no_hash.type)
367 return (B_TRUE);
368
369 if (disk_einfo->hash_type >= BBLK_HASH_TOT)
370 return (B_TRUE);
371
372 hashing = bblk_hash_list[disk_einfo->hash_type];
373
374 local_hash = malloc(hashing->size);
375 if (local_hash == NULL)
376 return (B_TRUE);
377
378 /*
379 * Failure in computing the hash may mean something wrong
380 * with the boot block file. Better be conservative here.
381 */
382 if (compute_hash(hs, local_hash, hashing) < 0) {
383 free(local_hash);
384 return (B_FALSE);
385 }
386
387 disk_hash = (unsigned char *)einfo_get_hash(disk_einfo);
388
389 if (memcmp(local_hash, disk_hash, disk_einfo->hash_size) == 0) {
390 free(local_hash);
391 return (B_FALSE);
392 }
393
394 free(local_hash);
395 return (B_TRUE);
396 }
397
398 char *
einfo_get_string(bblk_einfo_t * einfo)399 einfo_get_string(bblk_einfo_t *einfo)
400 {
401 if (einfo == NULL)
402 return (NULL);
403
404 return ((char *)einfo + einfo->str_off);
405 }
406
407 char *
einfo_get_hash(bblk_einfo_t * einfo)408 einfo_get_hash(bblk_einfo_t *einfo)
409 {
410 if (einfo == NULL)
411 return (NULL);
412
413 return ((char *)einfo + einfo->hash_off);
414 }
415