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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 27 #include <sys/stat.h> 28 #include <sys/types.h> 29 30 #include <assert.h> 31 #include <ctype.h> 32 #include <errno.h> 33 #include <fcntl.h> 34 #include <libintl.h> 35 #include <libscf.h> 36 #include <libuutil.h> 37 #include <limits.h> 38 #include <md5.h> 39 #include <pthread.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <strings.h> 44 #include <unistd.h> 45 46 #include <manifest_hash.h> 47 48 /* 49 * Translate a file name to property name. Return an allocated string or NULL 50 * if realpath() fails. If deathrow is true, realpath() is skipped. This 51 * allows to return the property name even if the file doesn't exist. 52 */ 53 char * 54 mhash_filename_to_propname(const char *in, boolean_t deathrow) 55 { 56 char *out, *cp, *base; 57 size_t len, piece_len; 58 59 out = uu_zalloc(PATH_MAX + 1); 60 if (deathrow) { 61 /* used only for service deathrow handling */ 62 if (strlcpy(out, in, PATH_MAX + 1) >= (PATH_MAX + 1)) { 63 uu_free(out); 64 return (NULL); 65 } 66 } else { 67 if (realpath(in, out) == NULL) { 68 uu_free(out); 69 return (NULL); 70 } 71 } 72 73 base = getenv("PKG_INSTALL_ROOT"); 74 75 /* 76 * We copy-shift over the basedir and the leading slash, since it's 77 * not relevant to when we boot with this repository. 78 */ 79 80 cp = out + ((base != NULL)? strlen(base) : 0); 81 if (*cp == '/') 82 cp++; 83 (void) memmove(out, cp, strlen(cp) + 1); 84 85 len = strlen(out); 86 if (len > scf_limit(SCF_LIMIT_MAX_NAME_LENGTH)) { 87 /* Use the first half and the second half. */ 88 piece_len = (scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) - 3) / 2; 89 90 (void) strncpy(out + piece_len, "__", 2); 91 92 (void) memmove(out + piece_len + 2, out + (len - piece_len), 93 piece_len + 1); 94 } 95 96 /* 97 * Translate non-property characters to '_', first making sure that 98 * we don't begin with '_'. 99 */ 100 101 if (!isalpha(*out)) 102 *out = 'A'; 103 104 for (cp = out + 1; *cp != '\0'; ++cp) { 105 if (!(isalnum(*cp) || *cp == '_' || *cp == '-')) 106 *cp = '_'; 107 } 108 109 return (out); 110 } 111 112 int 113 mhash_retrieve_entry(scf_handle_t *hndl, const char *name, uchar_t *hash) 114 { 115 scf_scope_t *scope; 116 scf_service_t *svc; 117 scf_propertygroup_t *pg; 118 scf_property_t *prop; 119 scf_value_t *val; 120 ssize_t szret; 121 int result = 0; 122 123 /* 124 * In this implementation the hash for name is the opaque value of 125 * svc:/MHASH_SVC/:properties/name/MHASH_PROP 126 */ 127 128 if ((scope = scf_scope_create(hndl)) == NULL || 129 (svc = scf_service_create(hndl)) == NULL || 130 (pg = scf_pg_create(hndl)) == NULL || 131 (prop = scf_property_create(hndl)) == NULL || 132 (val = scf_value_create(hndl)) == NULL) { 133 result = -1; 134 goto out; 135 } 136 137 if (scf_handle_get_local_scope(hndl, scope) < 0) { 138 result = -1; 139 goto out; 140 } 141 142 if (scf_scope_get_service(scope, MHASH_SVC, svc) < 0) { 143 result = -1; 144 goto out; 145 } 146 147 if (scf_service_get_pg(svc, name, pg) != SCF_SUCCESS) { 148 result = -1; 149 goto out; 150 } 151 152 if (scf_pg_get_property(pg, MHASH_PROP, prop) != SCF_SUCCESS) { 153 result = -1; 154 goto out; 155 } 156 157 if (scf_property_get_value(prop, val) != SCF_SUCCESS) { 158 result = -1; 159 goto out; 160 } 161 162 szret = scf_value_get_opaque(val, hash, MHASH_SIZE); 163 if (szret < 0) { 164 result = -1; 165 goto out; 166 } 167 168 /* 169 * Make sure that the old hash is returned with 170 * remainder of the bytes zeroed. 171 */ 172 if (szret == MHASH_SIZE_OLD) { 173 (void) memset(hash + MHASH_SIZE_OLD, 0, 174 MHASH_SIZE - MHASH_SIZE_OLD); 175 } else if (szret != MHASH_SIZE) { 176 scf_value_destroy(val); 177 result = -1; 178 goto out; 179 } 180 181 out: 182 (void) scf_value_destroy(val); 183 scf_property_destroy(prop); 184 scf_pg_destroy(pg); 185 scf_service_destroy(svc); 186 scf_scope_destroy(scope); 187 188 return (result); 189 } 190 191 int 192 mhash_store_entry(scf_handle_t *hndl, const char *name, const char *fname, 193 uchar_t *hash, char **errstr) 194 { 195 scf_scope_t *scope = NULL; 196 scf_service_t *svc = NULL; 197 scf_propertygroup_t *pg = NULL; 198 scf_property_t *prop = NULL; 199 scf_value_t *val = NULL; 200 scf_value_t *fval = NULL; 201 scf_transaction_t *tx = NULL; 202 scf_transaction_entry_t *e = NULL; 203 scf_transaction_entry_t *fe = NULL; 204 int ret, result = 0; 205 206 int i; 207 208 if ((scope = scf_scope_create(hndl)) == NULL || 209 (svc = scf_service_create(hndl)) == NULL || 210 (pg = scf_pg_create(hndl)) == NULL || 211 (prop = scf_property_create(hndl)) == NULL) { 212 if (errstr != NULL) 213 *errstr = gettext("Could not create scf objects"); 214 result = -1; 215 goto out; 216 } 217 218 if (scf_handle_get_local_scope(hndl, scope) != SCF_SUCCESS) { 219 if (errstr != NULL) 220 *errstr = gettext("Could not get local scope"); 221 result = -1; 222 goto out; 223 } 224 225 for (i = 0; i < 5; ++i) { 226 scf_error_t err; 227 228 if (scf_scope_get_service(scope, MHASH_SVC, svc) == 229 SCF_SUCCESS) 230 break; 231 232 if (scf_error() != SCF_ERROR_NOT_FOUND) { 233 if (errstr != NULL) 234 *errstr = gettext("Could not get manifest hash " 235 "service"); 236 result = -1; 237 goto out; 238 } 239 240 if (scf_scope_add_service(scope, MHASH_SVC, svc) == 241 SCF_SUCCESS) 242 break; 243 244 err = scf_error(); 245 246 if (err == SCF_ERROR_EXISTS) 247 /* Try again. */ 248 continue; 249 else if (err == SCF_ERROR_PERMISSION_DENIED) { 250 if (errstr != NULL) 251 *errstr = gettext("Could not store file hash: " 252 "permission denied.\n"); 253 result = -1; 254 goto out; 255 } 256 257 if (errstr != NULL) 258 *errstr = gettext("Could not add manifest hash " 259 "service"); 260 result = -1; 261 goto out; 262 } 263 264 if (i == 5) { 265 if (errstr != NULL) 266 *errstr = gettext("Could not store file hash: " 267 "service addition contention.\n"); 268 result = -1; 269 goto out; 270 } 271 272 for (i = 0; i < 5; ++i) { 273 scf_error_t err; 274 275 if (scf_service_get_pg(svc, name, pg) == SCF_SUCCESS) 276 break; 277 278 if (scf_error() != SCF_ERROR_NOT_FOUND) { 279 if (errstr != NULL) 280 *errstr = gettext("Could not get service's " 281 "hash record)"); 282 result = -1; 283 goto out; 284 } 285 286 if (scf_service_add_pg(svc, name, MHASH_PG_TYPE, 287 MHASH_PG_FLAGS, pg) == SCF_SUCCESS) 288 break; 289 290 err = scf_error(); 291 292 if (err == SCF_ERROR_EXISTS) 293 /* Try again. */ 294 continue; 295 else if (err == SCF_ERROR_PERMISSION_DENIED) { 296 if (errstr != NULL) 297 *errstr = gettext("Could not store file hash: " 298 "permission denied.\n"); 299 result = -1; 300 goto out; 301 } 302 303 if (errstr != NULL) 304 *errstr = gettext("Could not store file hash"); 305 result = -1; 306 goto out; 307 } 308 if (i == 5) { 309 if (errstr != NULL) 310 *errstr = gettext("Could not store file hash: " 311 "property group addition contention.\n"); 312 result = -1; 313 goto out; 314 } 315 316 if ((e = scf_entry_create(hndl)) == NULL || 317 (val = scf_value_create(hndl)) == NULL || 318 (fe = scf_entry_create(hndl)) == NULL || 319 (fval = scf_value_create(hndl)) == NULL) { 320 if (errstr != NULL) 321 *errstr = gettext("Could not store file hash: " 322 "permission denied.\n"); 323 result = -1; 324 goto out; 325 } 326 327 ret = scf_value_set_opaque(val, hash, MHASH_SIZE); 328 assert(ret == SCF_SUCCESS); 329 ret = scf_value_set_astring(fval, fname); 330 assert(ret == SCF_SUCCESS); 331 332 tx = scf_transaction_create(hndl); 333 if (tx == NULL) { 334 if (errstr != NULL) 335 *errstr = gettext("Could not create transaction"); 336 result = -1; 337 goto out; 338 } 339 340 do { 341 if (scf_pg_update(pg) == -1) { 342 if (errstr != NULL) 343 *errstr = gettext("Could not update hash " 344 "entry"); 345 result = -1; 346 goto out; 347 } 348 if (scf_transaction_start(tx, pg) != SCF_SUCCESS) { 349 if (scf_error() != SCF_ERROR_PERMISSION_DENIED) { 350 if (errstr != NULL) 351 *errstr = gettext("Could not start " 352 "hash transaction.\n"); 353 result = -1; 354 goto out; 355 } 356 357 if (errstr != NULL) 358 *errstr = gettext("Could not store file hash: " 359 "permission denied.\n"); 360 result = -1; 361 362 scf_transaction_destroy(tx); 363 (void) scf_entry_destroy(e); 364 goto out; 365 } 366 367 if (scf_transaction_property_new(tx, e, MHASH_PROP, 368 SCF_TYPE_OPAQUE) != SCF_SUCCESS && 369 scf_transaction_property_change_type(tx, e, MHASH_PROP, 370 SCF_TYPE_OPAQUE) != SCF_SUCCESS) { 371 if (errstr != NULL) 372 *errstr = gettext("Could not modify hash " 373 "entry"); 374 result = -1; 375 goto out; 376 } 377 378 ret = scf_entry_add_value(e, val); 379 assert(ret == SCF_SUCCESS); 380 381 if (scf_transaction_property_new(tx, fe, MFILE_PROP, 382 SCF_TYPE_ASTRING) != SCF_SUCCESS && 383 scf_transaction_property_change_type(tx, fe, MFILE_PROP, 384 SCF_TYPE_ASTRING) != SCF_SUCCESS) { 385 if (errstr != NULL) 386 *errstr = gettext("Could not modify file " 387 "entry"); 388 result = -1; 389 goto out; 390 } 391 392 ret = scf_entry_add_value(fe, fval); 393 assert(ret == SCF_SUCCESS); 394 395 ret = scf_transaction_commit(tx); 396 397 if (ret == 0) 398 scf_transaction_reset(tx); 399 } while (ret == 0); 400 401 if (ret < 0) { 402 if (scf_error() != SCF_ERROR_PERMISSION_DENIED) { 403 if (errstr != NULL) 404 *errstr = gettext("Could not store file hash: " 405 "permission denied.\n"); 406 result = -1; 407 goto out; 408 } 409 410 if (errstr != NULL) 411 *errstr = gettext("Could not commit transaction"); 412 result = -1; 413 } 414 415 scf_transaction_destroy(tx); 416 (void) scf_entry_destroy(e); 417 (void) scf_entry_destroy(fe); 418 419 out: 420 (void) scf_value_destroy(val); 421 (void) scf_value_destroy(fval); 422 scf_property_destroy(prop); 423 scf_pg_destroy(pg); 424 scf_service_destroy(svc); 425 scf_scope_destroy(scope); 426 427 return (result); 428 } 429 430 /* 431 * Generate the md5 hash of a file; manifest files are smallish 432 * so we can read them in one gulp. 433 */ 434 static int 435 md5_hash_file(const char *file, off64_t sz, uchar_t *hash) 436 { 437 char *buf; 438 int fd; 439 ssize_t res; 440 int ret; 441 442 fd = open(file, O_RDONLY); 443 if (fd < 0) 444 return (-1); 445 446 buf = malloc(sz); 447 if (buf == NULL) { 448 (void) close(fd); 449 return (-1); 450 } 451 452 res = read(fd, buf, (size_t)sz); 453 454 (void) close(fd); 455 456 if (res == sz) { 457 ret = 0; 458 md5_calc(hash, (uchar_t *)buf, (unsigned int) sz); 459 } else { 460 ret = -1; 461 } 462 463 free(buf); 464 return (ret); 465 } 466 467 /* 468 * int mhash_test_file(scf_handle_t *, const char *, uint_t, char **, uchar_t *) 469 * Test the given filename against the hashed metadata in the repository. 470 * The behaviours for import and apply are slightly different. For imports, 471 * if the hash value is absent or different, then the import operation 472 * continues. For profile application, the operation continues only if the 473 * hash value for the file is absent. 474 * 475 * We keep two hashes: one which can be quickly test: the metadata hash, 476 * and one which is more expensive to test: the file contents hash. 477 * 478 * If either hash matches, the file does not need to be re-read. 479 * If only one of the hashes matches, a side effect of this function 480 * is to store the newly computed hash. 481 * If neither hash matches, the hash computed for the new file is returned 482 * and not stored. 483 * 484 * Return values: 485 * MHASH_NEWFILE - the file no longer matches the hash or no hash existed 486 * ONLY in this case we return the new file's hash. 487 * MHASH_FAILURE - an internal error occurred, or the file was not found. 488 * MHASH_RECONCILED- based on the metadata/file hash, the file does 489 * not need to be re-read; if necessary, 490 * the hash was upgraded or reconciled. 491 * 492 * NOTE: no hash is returned UNLESS MHASH_NEWFILE is returned. 493 */ 494 int 495 mhash_test_file(scf_handle_t *hndl, const char *file, uint_t is_profile, 496 char **pnamep, uchar_t *hashbuf) 497 { 498 boolean_t do_hash; 499 struct stat64 st; 500 char *cp; 501 char *data; 502 uchar_t stored_hash[MHASH_SIZE]; 503 uchar_t hash[MHASH_SIZE]; 504 char *pname; 505 int ret; 506 int hashash; 507 int metahashok = 0; 508 509 /* 510 * In the case where we are doing automated imports, we reduce the UID, 511 * the GID, the size, and the mtime into a string (to eliminate 512 * endianness) which we then make opaque as a single MD5 digest. 513 * 514 * The previous hash was composed of the inode number, the UID, the file 515 * size, and the mtime. This formulation was found to be insufficiently 516 * portable for use in highly replicated deployments. The current 517 * algorithm will allow matches of this "v1" hash, but always returns 518 * the effective "v2" hash, such that updates result in the more 519 * portable hash being used. 520 * 521 * An unwanted side effect of a hash based solely on the file 522 * meta data is the fact that we pay no attention to the contents 523 * which may remain the same despite meta data changes. This happens 524 * with (live) upgrades. We extend the V2 hash with an additional 525 * digest of the file contents and the code retrieving the hash 526 * from the repository zero fills the remainder so we can detect 527 * it is missing. 528 * 529 * If the the V2 digest matches, we check for the presence of 530 * the contents digest and compute and store it if missing. 531 * 532 * If the V2 digest doesn't match but we also have a non-zero 533 * file hash, we match the file content digest. If it matches, 534 * we compute and store the new complete hash so that later 535 * checks will find the meta data digest correct. 536 * 537 * If the above matches fail and the V1 hash doesn't match either, 538 * we consider the test to have failed, implying that some aspect 539 * of the manifest has changed. 540 */ 541 542 cp = getenv("SVCCFG_CHECKHASH"); 543 do_hash = (cp != NULL && *cp != '\0'); 544 if (!do_hash) { 545 if (pnamep != NULL) 546 *pnamep = NULL; 547 return (MHASH_NEWFILE); 548 } 549 550 pname = mhash_filename_to_propname(file, B_FALSE); 551 if (pname == NULL) 552 return (MHASH_FAILURE); 553 554 hashash = mhash_retrieve_entry(hndl, pname, stored_hash) == 0; 555 556 /* Never reread a profile. */ 557 if (hashash && is_profile) { 558 uu_free(pname); 559 return (MHASH_RECONCILED); 560 } 561 562 /* 563 * No hash and not interested in one, then don't bother computing it. 564 * We also skip returning the property name in that case. 565 */ 566 if (!hashash && hashbuf == NULL) { 567 uu_free(pname); 568 return (MHASH_NEWFILE); 569 } 570 571 do { 572 ret = stat64(file, &st); 573 } while (ret < 0 && errno == EINTR); 574 if (ret < 0) { 575 uu_free(pname); 576 return (MHASH_FAILURE); 577 } 578 579 data = uu_msprintf(MHASH_FORMAT_V2, st.st_uid, st.st_gid, 580 st.st_size, st.st_mtime); 581 if (data == NULL) { 582 uu_free(pname); 583 return (MHASH_FAILURE); 584 } 585 586 (void) memset(hash, 0, MHASH_SIZE); 587 md5_calc(hash, (uchar_t *)data, strlen(data)); 588 589 uu_free(data); 590 591 /* 592 * Verify the meta data hash. 593 */ 594 if (hashash && memcmp(hash, stored_hash, MD5_DIGEST_LENGTH) == 0) { 595 int i; 596 597 metahashok = 1; 598 /* 599 * The metadata hash matches; now we see if there was a 600 * content hash; if not, we will continue on and compute and 601 * store the updated hash. 602 * If there was no content hash, mhash_retrieve_entry() 603 * will have zero filled it. 604 */ 605 for (i = 0; i < MD5_DIGEST_LENGTH; i++) { 606 if (stored_hash[MD5_DIGEST_LENGTH+i] != 0) { 607 uu_free(pname); 608 return (MHASH_RECONCILED); 609 } 610 } 611 } 612 613 /* 614 * Compute the file hash as we can no longer avoid having to know it. 615 * Note: from this point on "hash" contains the full, current, hash. 616 */ 617 if (md5_hash_file(file, st.st_size, &hash[MHASH_SIZE_OLD]) != 0) { 618 uu_free(pname); 619 return (MHASH_FAILURE); 620 } 621 if (hashash) { 622 uchar_t hash_v1[MHASH_SIZE_OLD]; 623 624 if (metahashok || 625 memcmp(&hash[MHASH_SIZE_OLD], &stored_hash[MHASH_SIZE_OLD], 626 MD5_DIGEST_LENGTH) == 0) { 627 628 /* 629 * Reconcile entry: we get here when either the 630 * meta data hash matches or the content hash matches; 631 * we then update the database with the complete 632 * new hash so we can be a bit quicker next time. 633 */ 634 (void) mhash_store_entry(hndl, pname, file, hash, NULL); 635 uu_free(pname); 636 return (MHASH_RECONCILED); 637 } 638 639 /* 640 * No match on V2 hash or file content; compare V1 hash. 641 */ 642 data = uu_msprintf(MHASH_FORMAT_V1, st.st_ino, st.st_uid, 643 st.st_size, st.st_mtime); 644 if (data == NULL) { 645 uu_free(pname); 646 return (MHASH_FAILURE); 647 } 648 649 md5_calc(hash_v1, (uchar_t *)data, strlen(data)); 650 651 uu_free(data); 652 653 if (memcmp(hash_v1, stored_hash, MD5_DIGEST_LENGTH) == 0) { 654 /* 655 * Update the new entry so we don't have to go through 656 * all this trouble next time. 657 */ 658 (void) mhash_store_entry(hndl, pname, file, hash, NULL); 659 uu_free(pname); 660 return (MHASH_RECONCILED); 661 } 662 } 663 664 if (pnamep != NULL) 665 *pnamep = pname; 666 else 667 uu_free(pname); 668 669 if (hashbuf != NULL) 670 (void) memcpy(hashbuf, hash, MHASH_SIZE); 671 672 return (MHASH_NEWFILE); 673 } 674