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 2005 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 * Return non-zero if we should skip the file because it is unchanged or 446 * nonexistent. 447 */ 448 int 449 mhash_test_file(scf_handle_t *hndl, const char *file, uint_t is_profile, 450 char **pnamep, uchar_t *hash) 451 { 452 boolean_t do_hash; 453 struct stat64 st; 454 char *cp; 455 char *data; 456 uchar_t stored_hash[MHASH_SIZE]; 457 char *pname; 458 int ret; 459 460 /* 461 * In the case where we are doing automated imports, we reduce the UID, 462 * the GID, the size, and the mtime into a string (to eliminate 463 * endianness) which we then make opaque as a single MD5 digest. 464 * 465 * The previous hash was composed of the inode number, the UID, the file 466 * size, and the mtime. This formulation was found to be insufficiently 467 * portable for use in highly replicated deployments. The current 468 * algorithm will allow matches of this "v1" hash, but always returns 469 * the effective "v2" hash, such that updates result in the more 470 * portable hash being used. 471 * 472 * An unwanted side effect of a hash based solely on the file 473 * meta data is the fact that we pay no attention to the contents 474 * which may remain the same despite meta data changes. This happens 475 * with (live) upgrades. We extend the V2 hash with an additional 476 * digest of the file contents and the code retrieving the hash 477 * from the repository zero fills the remainder so we can detect 478 * it is missing. 479 * 480 * If the the V2 digest matches, we check for the presence of 481 * the contents digest and compute and store it if missing. 482 * 483 * If the V2 digest doesn't match but we also have a non-zero 484 * file hash, we match the file content digest. If it matches, 485 * we compute and store the new complete hash so that later 486 * checks will find the meta data digest correct. 487 * 488 * If the above matches fail and the V1 hash doesn't match either, 489 * we consider the test to have failed, implying that some aspect 490 * of the manifest has changed. 491 */ 492 493 cp = getenv("SVCCFG_CHECKHASH"); 494 do_hash = (cp != NULL && *cp != '\0'); 495 if (!do_hash) { 496 *pnamep = NULL; 497 return (0); 498 } 499 500 do 501 ret = stat64(file, &st); 502 while (ret < 0 && errno == EINTR); 503 if (ret < 0) { 504 return (-1); 505 } 506 507 data = uu_msprintf(MHASH_FORMAT_V2, st.st_uid, st.st_gid, 508 st.st_size, st.st_mtime); 509 if (data == NULL) { 510 return (-1); 511 } 512 513 (void) memset(hash, 0, MHASH_SIZE); 514 md5_calc(hash, (uchar_t *)data, strlen(data)); 515 516 uu_free(data); 517 518 pname = mhash_filename_to_propname(file); 519 if (pname == NULL) 520 return (-1); 521 522 if (mhash_retrieve_entry(hndl, pname, stored_hash) == 0) { 523 uchar_t hash_v1[MHASH_SIZE]; 524 525 if (is_profile) { 526 uu_free(pname); 527 return (1); 528 } 529 530 /* 531 * Manifest import. 532 */ 533 if (memcmp(hash, stored_hash, MD5_DIGEST_LENGTH) == 0) { 534 int i; 535 536 /* 537 * If there's no recorded file hash, record it. 538 */ 539 for (i = 0; i < MD5_DIGEST_LENGTH; i++) { 540 if (stored_hash[MD5_DIGEST_LENGTH+i] != 0) 541 break; 542 } 543 if (i == MD5_DIGEST_LENGTH) { 544 if (md5_hash_file(file, st.st_size, 545 &hash[MHASH_SIZE_OLD]) == 0) { 546 (void) mhash_store_entry(hndl, pname, hash, 547 NULL); 548 } 549 } else { 550 /* Always return hash with MD5 content hash */ 551 (void) memcpy(hash, stored_hash, MHASH_SIZE); 552 } 553 uu_free(pname); 554 return (1); 555 } 556 557 /* 558 * The remainder of our hash is all 0; if the returned hash 559 * is not, it's a V2 hash + file checksum; so we're going 560 * to hash the file and then compare the checksum again. 561 */ 562 if (memcmp(&hash[MHASH_SIZE_OLD], &stored_hash[MHASH_SIZE_OLD], 563 MD5_DIGEST_LENGTH) != 0 && 564 md5_hash_file(file, st.st_size, 565 &hash[MHASH_SIZE_OLD]) == 0 && 566 memcmp(&hash[MHASH_SIZE_OLD], &stored_hash[MHASH_SIZE_OLD], 567 MD5_DIGEST_LENGTH) == 0) { 568 569 /* Be kind and update the entry */ 570 (void) mhash_store_entry(hndl, pname, hash, NULL); 571 uu_free(pname); 572 return (1); 573 } 574 575 /* 576 * No match on V2 hash; compare V1 hash. 577 */ 578 data = uu_msprintf(MHASH_FORMAT_V1, st.st_ino, st.st_uid, 579 st.st_size, st.st_mtime); 580 if (data == NULL) { 581 uu_free(pname); 582 return (-1); 583 } 584 585 md5_calc(hash_v1, (uchar_t *)data, strlen(data)); 586 587 uu_free(data); 588 589 if (memcmp(hash_v1, stored_hash, MD5_DIGEST_LENGTH) == 0) { 590 uu_free(pname); 591 return (1); 592 } 593 } 594 595 *pnamep = pname; 596 597 return (0); 598 } 599