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 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 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 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 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 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 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 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 * 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 * 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