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  * The version string generally uses following form:
141  *     self_release,build_release:timestamp
142  * The release numbers are compared as dotted versions.
143  *
144  * While comparing, if the self releases are identical but the build
145  * release is missing, this version string is considered older.
146  *
147  * If the release strings are identical, and one of the timestamps is missing,
148  * we return an error. Otherwise, return the result from comparing the
149  * timestamps.
150  */
151 static int
einfo_compare_version(const char * str1,const char * str2)152 einfo_compare_version(const char *str1, const char *str2)
153 {
154 	int	retval = 0;
155 	char	*verstr1, *verstr2, *freeptr1, *freeptr2;
156 	char	*parsep1, *parsep2;
157 	char	*timep1, *timep2;
158 
159 	freeptr1 = verstr1 = strdup(str1);
160 	freeptr2 = verstr2 = strdup(str2);
161 	if (verstr1 == NULL || verstr2 == NULL) {
162 		retval = -1;
163 		goto out;
164 	}
165 
166 	/* Extract the time part from the version string. */
167 	timep1 = verstr1;
168 	timep2 = verstr2;
169 	parsep1 = strsep(&timep1, ":");
170 	parsep2 = strsep(&timep2, ":");
171 
172 	while (parsep1 != NULL && parsep2 != NULL) {
173 		parsep1 = strsep(&verstr1, ",-");
174 		parsep2 = strsep(&verstr2, ",-");
175 
176 		/* If both are NULL, compare timestamps */
177 		if (parsep1 == NULL && parsep2 == NULL)
178 			break;
179 
180 		if (parsep1 == NULL) {
181 			retval = 2;
182 			goto out;
183 		}
184 		if (parsep2 == NULL) {
185 			retval = 1;
186 			goto out;
187 		}
188 
189 		retval = einfo_compare_dotted_version(parsep1, parsep2);
190 		if (retval == 0)
191 			continue;
192 		else
193 			goto out;
194 	}
195 
196 	/* The dotted versions are identical, check timestamps. */
197 	if (timep1 == NULL || timep2 == NULL) {
198 		retval = -1;
199 		goto out;
200 	}
201 	retval = einfo_compare_timestamps(timep1, timep2);
202 out:
203 	free(freeptr1);
204 	free(freeptr2);
205 	return (retval);
206 }
207 
208 /*
209  * print_einfo()
210  *
211  * Print the extended information contained into the pointed structure.
212  * 'bufsize' specifies the real size of the structure, since str_off and
213  * hash_off need to point somewhere past the header.
214  */
215 void
print_einfo(uint8_t flags,bblk_einfo_t * einfo,unsigned long bufsize)216 print_einfo(uint8_t flags, bblk_einfo_t *einfo, unsigned long bufsize)
217 {
218 	int		i = 0;
219 	char		*version;
220 	boolean_t	has_hash = B_FALSE;
221 	unsigned char	*hash = NULL;
222 
223 	if (einfo->str_off + einfo->str_size > bufsize) {
224 		(void) fprintf(stdout, gettext("String offset %d is beyond the "
225 		    "buffer size\n"), einfo->str_off);
226 		return;
227 	}
228 
229 	version = (char *)einfo + einfo->str_off;
230 	if (einfo->hash_type != BBLK_NO_HASH &&
231 	    einfo->hash_type < BBLK_HASH_TOT) {
232 		if (einfo->hash_off + einfo->hash_size > bufsize) {
233 			(void) fprintf(stdout, gettext("Warning: hashing "
234 			    "present but hash offset %d is beyond the buffer "
235 			    "size\n"), einfo->hash_off);
236 			has_hash = B_FALSE;
237 		} else {
238 			hash = (unsigned char *)einfo + einfo->hash_off;
239 			has_hash = B_TRUE;
240 		}
241 	}
242 
243 	if (flags & EINFO_PRINT_HEADER) {
244 		(void) fprintf(stdout, "Boot Block Extended Info Header:\n");
245 		(void) fprintf(stdout, "\tmagic: ");
246 		for (i = 0; i < EINFO_MAGIC_SIZE; i++)
247 			(void) fprintf(stdout, "%c", einfo->magic[i]);
248 		(void) fprintf(stdout, "\n");
249 		(void) fprintf(stdout, "\tversion: %d\n", einfo->version);
250 		(void) fprintf(stdout, "\tflags: %x\n", einfo->flags);
251 		(void) fprintf(stdout, "\textended version string offset: %d\n",
252 		    einfo->str_off);
253 		(void) fprintf(stdout, "\textended version string size: %d\n",
254 		    einfo->str_size);
255 		(void) fprintf(stdout, "\thashing type: %d (%s)\n",
256 		    einfo->hash_type, has_hash ?
257 		    bblk_hash_list[einfo->hash_type]->name : "nil");
258 		(void) fprintf(stdout, "\thash offset: %d\n", einfo->hash_off);
259 		(void) fprintf(stdout, "\thash size: %d\n", einfo->hash_size);
260 	}
261 
262 	if (flags & EINFO_EASY_PARSE) {
263 		(void) fprintf(stdout, "%s\n", version);
264 	} else {
265 		(void) fprintf(stdout, "Extended version string: %s\n",
266 		    version);
267 		if (has_hash) {
268 			(void) fprintf(stdout, "%s hash: ",
269 			    bblk_hash_list[einfo->hash_type]->name);
270 		} else {
271 			(void) fprintf(stdout, "No hashing available\n");
272 		}
273 	}
274 
275 	if (has_hash) {
276 		for (i = 0; i < einfo->hash_size; i++) {
277 			(void) fprintf(stdout, "%02x", hash[i]);
278 		}
279 		(void) fprintf(stdout, "\n");
280 	}
281 }
282 
283 static int
compute_hash(bblk_hs_t * hs,unsigned char * dest,bblk_hash_t * hash)284 compute_hash(bblk_hs_t *hs, unsigned char *dest, bblk_hash_t *hash)
285 {
286 	if (hs == NULL || dest == NULL || hash == NULL)
287 		return (-1);
288 
289 	hash->compute_hash(dest, hs->src_buf, hs->src_size);
290 	return (0);
291 }
292 
293 int
prepare_and_write_einfo(unsigned char * dest,char * infostr,bblk_hs_t * hs,uint32_t maxsize,uint32_t * used_space)294 prepare_and_write_einfo(unsigned char *dest, char *infostr, bblk_hs_t *hs,
295     uint32_t maxsize, uint32_t *used_space)
296 {
297 	uint16_t	hash_size;
298 	uint32_t	hash_off;
299 	unsigned char	*data;
300 	bblk_einfo_t	*einfo = (bblk_einfo_t *)dest;
301 	bblk_hash_t	*hashinfo = bblk_hash_list[BBLK_DEFAULT_HASH];
302 
303 	/*
304 	 * 'dest' might be both containing the buffer we want to hash and
305 	 * containing our einfo structure: delay any update of it after the
306 	 * hashing has been calculated.
307 	 */
308 	hash_size = hashinfo->size;
309 	hash_off = sizeof (bblk_einfo_t);
310 
311 	if (hash_off + hash_size > maxsize) {
312 		(void) fprintf(stderr, gettext("Unable to add extended info, "
313 		    "not enough space\n"));
314 		return (-1);
315 	}
316 
317 	data = dest + hash_off;
318 	if (compute_hash(hs, data, hashinfo) < 0) {
319 		(void) fprintf(stderr, gettext("%s hash operation failed\n"),
320 		    hashinfo->name);
321 		einfo->hash_type = bblk_no_hash.type;
322 		einfo->hash_size = bblk_no_hash.size;
323 	} else {
324 		einfo->hash_type = hashinfo->type;
325 		einfo->hash_size = hashinfo->size;
326 	}
327 
328 	(void) memcpy(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE);
329 	einfo->version = BBLK_EINFO_VERSION;
330 	einfo->flags = 0;
331 	einfo->hash_off = hash_off;
332 	einfo->hash_size = hash_size;
333 	einfo->str_off = einfo->hash_off + einfo->hash_size + 1;
334 
335 	if (infostr == NULL) {
336 		(void) fprintf(stderr, gettext("Unable to add extended info, "
337 		    "string is empty\n"));
338 		return (-1);
339 	}
340 	einfo->str_size = strlen(infostr);
341 
342 	if (einfo->str_off + einfo->str_size > maxsize) {
343 		(void) fprintf(stderr, gettext("Unable to add extended info, "
344 		    "not enough space\n"));
345 		return (-1);
346 	}
347 
348 	data = dest + einfo->str_off;
349 	(void) memcpy(data, infostr, einfo->str_size);
350 	*used_space = einfo->str_off + einfo->str_size;
351 
352 	return (0);
353 }
354 
355 /*
356  * einfo_should_update()
357  * Given information on the boot block currently on disk (disk_einfo) and
358  * information on the supplied boot block (hs for hashing, verstr as the
359  * associated version string) decide if an update of the on-disk boot block
360  * is necessary or not.
361  */
362 boolean_t
einfo_should_update(bblk_einfo_t * disk_einfo,bblk_hs_t * hs,char * verstr)363 einfo_should_update(bblk_einfo_t *disk_einfo, bblk_hs_t *hs, char *verstr)
364 {
365 	bblk_hash_t	*hashing;
366 	unsigned char	*disk_hash;
367 	unsigned char	*local_hash;
368 	char		*disk_version;
369 	int		retval;
370 
371 	if (disk_einfo == NULL)
372 		return (B_TRUE);
373 
374 	if (memcmp(disk_einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE) != 0)
375 		return (B_TRUE);
376 
377 	if (disk_einfo->version < BBLK_EINFO_VERSION)
378 		return (B_TRUE);
379 
380 	disk_version = einfo_get_string(disk_einfo);
381 	retval = einfo_compare_version(verstr, disk_version);
382 	/*
383 	 * If something goes wrong or if the on-disk version is more recent
384 	 * do not update the bootblock.
385 	 */
386 	if (retval == -1 || retval == 2)
387 		return (B_FALSE);
388 
389 	/*
390 	 * If we got here it means that the two version strings are either
391 	 * equal or the new bootblk binary is more recent. In order to save
392 	 * some needless writes let's use the hash to determine if an update
393 	 * is really necessary.
394 	 */
395 	if (disk_einfo->hash_type == bblk_no_hash.type)
396 		return (B_TRUE);
397 
398 	if (disk_einfo->hash_type >= BBLK_HASH_TOT)
399 		return (B_TRUE);
400 
401 	hashing = bblk_hash_list[disk_einfo->hash_type];
402 
403 	local_hash = malloc(hashing->size);
404 	if (local_hash == NULL)
405 		return (B_TRUE);
406 
407 	/*
408 	 * Failure in computing the hash may mean something wrong
409 	 * with the boot block file. Better be conservative here.
410 	 */
411 	if (compute_hash(hs, local_hash, hashing) < 0) {
412 		free(local_hash);
413 		return (B_FALSE);
414 	}
415 
416 	disk_hash = (unsigned char *)einfo_get_hash(disk_einfo);
417 
418 	if (memcmp(local_hash, disk_hash, disk_einfo->hash_size) == 0) {
419 		free(local_hash);
420 		return (B_FALSE);
421 	}
422 
423 	free(local_hash);
424 	return (B_TRUE);
425 }
426 
427 char *
einfo_get_string(bblk_einfo_t * einfo)428 einfo_get_string(bblk_einfo_t *einfo)
429 {
430 	if (einfo == NULL)
431 		return (NULL);
432 
433 	return ((char *)einfo + einfo->str_off);
434 }
435 
436 char *
einfo_get_hash(bblk_einfo_t * einfo)437 einfo_get_hash(bblk_einfo_t *einfo)
438 {
439 	if (einfo == NULL)
440 		return (NULL);
441 
442 	return ((char *)einfo + einfo->hash_off);
443 }
444