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