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 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #include "lint.h" 28 #include "mtlib.h" 29 #include <string.h> 30 #include <syslog.h> 31 #include <sys/stat.h> 32 #include <fcntl.h> 33 #include <limits.h> 34 #include <unistd.h> 35 #include <stdlib.h> 36 #include <thread.h> 37 #include <synch.h> 38 #include <ctype.h> 39 #include <errno.h> 40 #include "libc.h" 41 #include "nlspath_checks.h" 42 43 extern const char **_environ; 44 45 /* 46 * We want to prevent the use of NLSPATH by setugid applications but 47 * not completely. CDE depends on this very much. 48 * Yes, this is ugly. 49 */ 50 51 struct trusted_systemdirs { 52 const char *dir; 53 size_t dirlen; 54 }; 55 56 #define _USRLIB "/usr/lib/" 57 #define _USRDT "/usr/dt/" 58 #define _USROW "/usr/openwin/" 59 60 static const struct trusted_systemdirs prefix[] = { 61 { _USRLIB, sizeof (_USRLIB) - 1 }, 62 { _USRDT, sizeof (_USRDT) - 1 }, 63 { _USROW, sizeof (_USROW) - 1 }, 64 { NULL, 0 } 65 }; 66 67 static int8_t nlspath_safe; 68 69 /* 70 * Routine to check the safety of a messages file. 71 * When the program specifies a pathname and doesn't 72 * use NLSPATH, it should specify the "safe" flag as 1. 73 * Most checks will be disabled then. 74 * fstat64 is done here and the stat structure is returned 75 * to prevent duplication of system calls. 76 * 77 * The trust return value contains an indication of 78 * trustworthiness (i.e., does check_format need to be called or 79 * not) 80 */ 81 82 int 83 nls_safe_open(const char *path, struct stat64 *statbuf, int *trust, int safe) 84 { 85 int fd; 86 int trust_path; 87 int systemdir = 0; 88 int abs_path = 0; 89 int trust_owner = 0; 90 int trust_group = 0; 91 const struct trusted_systemdirs *p; 92 93 /* 94 * If SAFE_F has been specified or NLSPATH is safe (or not set), 95 * set trust_path and trust the file as an initial value. 96 */ 97 trust_path = *trust = safe || nlspath_safe; 98 99 fd = open(path, O_RDONLY); 100 101 if (fd < 0) 102 return (-1); 103 104 if (fstat64(fd, statbuf) == -1) { 105 (void) close(fd); 106 return (-1); 107 } 108 109 /* 110 * Trust only files owned by root or bin (uid 2), except 111 * when specified as full path or when NLSPATH is known to 112 * be safe. 113 * Don't trust files writable by other or writable 114 * by non-bin, non-root system group. 115 * Don't trust these files even if the path is correct. 116 * Since we don't support changing uids/gids on our files, 117 * we hardcode them here for now. 118 */ 119 120 /* 121 * if the path is absolute and does not contain "/../", 122 * set abs_path. 123 */ 124 if (*path == '/' && strstr(path, "/../") == NULL) { 125 abs_path = 1; 126 /* 127 * if the path belongs to the trusted system directory, 128 * set systemdir. 129 */ 130 for (p = prefix; p->dir; p++) { 131 if (strncmp(p->dir, path, p->dirlen) == 0) { 132 systemdir = 1; 133 break; 134 } 135 } 136 } 137 138 /* 139 * If the owner is root or bin, set trust_owner. 140 */ 141 if (statbuf->st_uid == 0 || statbuf->st_uid == 2) { 142 trust_owner = 1; 143 } 144 /* 145 * If the file is neither other-writable nor group-writable by 146 * non-bin and non-root system group, set trust_group. 147 */ 148 if ((statbuf->st_mode & (S_IWOTH)) == 0 && 149 ((statbuf->st_mode & (S_IWGRP)) == 0 || 150 (statbuf->st_gid < 4 && statbuf->st_gid != 1))) { 151 trust_group = 1; 152 } 153 154 /* 155 * Even if UNSAFE_F has been specified and unsafe-NLSPATH 156 * has been set, trust the file as long as it belongs to 157 * the trusted system directory. 158 */ 159 if (!*trust && systemdir) { 160 *trust = 1; 161 } 162 163 /* 164 * If: 165 * file is not a full pathname, 166 * or 167 * neither trust_owner nor trust_path is set, 168 * or 169 * trust_group is not set, 170 * untrust it. 171 */ 172 if (*trust && 173 (!abs_path || (!trust_owner && !trust_path) || !trust_group)) { 174 *trust = 0; 175 } 176 177 /* 178 * If set[ug]id process, open for the untrusted file should fail. 179 * Otherwise, the message extracted from the untrusted file 180 * will have to be checked by check_format(). 181 */ 182 if (issetugid()) { 183 if (!*trust) { 184 /* 185 * Open should fail 186 */ 187 (void) close(fd); 188 return (-1); 189 } 190 191 /* 192 * if the path does not belong to the trusted system directory 193 * or if the owner is neither root nor bin, untrust it. 194 */ 195 if (!systemdir || !trust_owner) { 196 *trust = 0; 197 } 198 } 199 200 return (fd); 201 } 202 203 /* 204 * Extract a format into a normalized format string. 205 * Returns the number of arguments converted, -1 on error. 206 * The string norm should contain 2N bytes; an upperbound is the 207 * length of the format string. 208 * The canonical format consists of two chars: one is the conversion 209 * character (s, c, d, x, etc), the second one is the option flag. 210 * L, ll, l, w as defined below. 211 * A special conversion character, '*', indicates that the argument 212 * is used as a precision specifier. 213 */ 214 215 #define OPT_L 0x01 216 #define OPT_l 0x02 217 #define OPT_ll 0x04 218 #define OPT_w 0x08 219 #define OPT_h 0x10 220 #define OPT_hh 0x20 221 #define OPT_j 0x40 222 223 /* Number of bytes per canonical format entry */ 224 #define FORMAT_SIZE 2 225 226 /* 227 * Check and store the argument; allow each argument to be used only as 228 * one type even though printf allows multiple uses. The specification only 229 * allows one use, but we don't want to break existing functional code, 230 * even if it's buggy. 231 */ 232 #define STORE(buf, size, arg, val) if (arg * FORMAT_SIZE + 1 >= size ||\ 233 (strict ? \ 234 (buf[arg*FORMAT_SIZE] != '\0' && \ 235 buf[arg*FORMAT_SIZE] != val) \ 236 : \ 237 (buf[arg*FORMAT_SIZE] == 'n'))) \ 238 return (-1); \ 239 else {\ 240 if (arg >= maxarg) \ 241 maxarg = arg + 1; \ 242 narg++; \ 243 buf[arg*FORMAT_SIZE] = val; \ 244 } 245 246 /* 247 * This function extracts sprintf format into a canonical 248 * sprintf form. It's not as easy as just removing everything 249 * that isn't a format specifier, because of "%n$" specifiers. 250 * Ideally, this should be compatible with printf and not 251 * fail on bad formats. 252 * However, that makes writing a proper check_format that 253 * doesn't cause crashes a lot harder. 254 */ 255 256 static int 257 extract_format(const char *fmt, char *norm, size_t sz, int strict) 258 { 259 int narg = 0; 260 int t, arg, argp; 261 int dotseen; 262 char flag; 263 char conv; 264 int lastarg = -1; 265 int prevarg; 266 int maxarg = 0; /* Highest index seen + 1 */ 267 int lflag; 268 269 (void) memset(norm, '\0', sz); 270 271 #ifdef DEBUG 272 printf("Format \"%s\" canonical form: ", fmt); 273 #endif 274 275 for (; *fmt; fmt++) { 276 if (*fmt == '%') { 277 if (*++fmt == '%') 278 continue; 279 280 if (*fmt == '\0') 281 break; 282 283 prevarg = lastarg; 284 arg = ++lastarg; 285 286 t = 0; 287 while (*fmt && isdigit(*fmt)) 288 t = t * 10 + *fmt++ - '0'; 289 290 if (*fmt == '$') { 291 lastarg = arg = t - 1; 292 fmt++; 293 } 294 295 if (*fmt == '\0') 296 goto end; 297 298 dotseen = 0; 299 flag = 0; 300 lflag = 0; 301 again: 302 /* Skip flags */ 303 while (*fmt) { 304 switch (*fmt) { 305 case '\'': 306 case '+': 307 case '-': 308 case ' ': 309 case '#': 310 case '0': 311 fmt++; 312 continue; 313 } 314 break; 315 } 316 317 while (*fmt && isdigit(*fmt)) 318 fmt++; 319 320 if (*fmt == '*') { 321 if (isdigit(fmt[1])) { 322 fmt++; 323 t = 0; 324 while (*fmt && isdigit(*fmt)) 325 t = t * 10 + *fmt++ - '0'; 326 327 if (*fmt == '$') { 328 argp = t - 1; 329 STORE(norm, sz, argp, '*'); 330 } 331 /* 332 * If digits follow a '*', it is 333 * not loaded as an argument, the 334 * digits are used instead. 335 */ 336 } else { 337 /* 338 * Weird as it may seem, if we 339 * use an numbered argument, we 340 * get the next one if we have 341 * an unnumbered '*' 342 */ 343 if (fmt[1] == '$') 344 fmt++; 345 else { 346 argp = arg; 347 prevarg = arg; 348 lastarg = ++arg; 349 STORE(norm, sz, argp, '*'); 350 } 351 } 352 fmt++; 353 } 354 355 /* Fail on two or more dots if we do strict checking */ 356 if (*fmt == '.' || *fmt == '*') { 357 if (dotseen && strict) 358 return (-1); 359 dotseen = 1; 360 fmt++; 361 goto again; 362 } 363 364 if (*fmt == '\0') 365 goto end; 366 367 while (*fmt) { 368 switch (*fmt) { 369 case 'l': 370 if (!(flag & OPT_ll)) { 371 if (lflag) { 372 flag &= ~OPT_l; 373 flag |= OPT_ll; 374 } else { 375 flag |= OPT_l; 376 } 377 } 378 lflag++; 379 break; 380 case 'L': 381 flag |= OPT_L; 382 break; 383 case 'w': 384 flag |= OPT_w; 385 break; 386 case 'h': 387 if (flag & (OPT_h|OPT_hh)) 388 flag |= OPT_hh; 389 else 390 flag |= OPT_h; 391 break; 392 case 'j': 393 flag |= OPT_j; 394 break; 395 case 'z': 396 case 't': 397 if (!(flag & OPT_ll)) { 398 flag |= OPT_l; 399 } 400 break; 401 case '\'': 402 case '+': 403 case '-': 404 case ' ': 405 case '#': 406 case '.': 407 case '*': 408 goto again; 409 default: 410 if (isdigit(*fmt)) 411 goto again; 412 else 413 goto done; 414 } 415 fmt++; 416 } 417 done: 418 if (*fmt == '\0') 419 goto end; 420 421 switch (*fmt) { 422 case 'C': 423 flag |= OPT_l; 424 /* FALLTHROUGH */ 425 case 'd': 426 case 'i': 427 case 'o': 428 case 'u': 429 case 'c': 430 case 'x': 431 case 'X': 432 conv = 'I'; 433 break; 434 case 'e': 435 case 'E': 436 case 'f': 437 case 'F': 438 case 'a': 439 case 'A': 440 case 'g': 441 case 'G': 442 conv = 'D'; 443 break; 444 case 'S': 445 flag |= OPT_l; 446 /* FALLTHROUGH */ 447 case 's': 448 conv = 's'; 449 break; 450 case 'p': 451 case 'n': 452 conv = *fmt; 453 break; 454 default: 455 lastarg = prevarg; 456 continue; 457 } 458 459 STORE(norm, sz, arg, conv); 460 norm[arg*FORMAT_SIZE + 1] = flag; 461 } 462 } 463 #ifdef DEBUG 464 for (t = 0; t < maxarg * FORMAT_SIZE; t += FORMAT_SIZE) { 465 printf("%c(%d)", norm[t], norm[t+1]); 466 } 467 putchar('\n'); 468 #endif 469 end: 470 if (strict) 471 for (arg = 0; arg < maxarg; arg++) 472 if (norm[arg*FORMAT_SIZE] == '\0') 473 return (-1); 474 475 return (maxarg); 476 } 477 478 char * 479 check_format(const char *org, const char *new, int strict) 480 { 481 char *ofmt, *nfmt, *torg; 482 size_t osz, nsz; 483 int olen, nlen; 484 485 if (!org) { 486 /* 487 * Default message is NULL. 488 * dtmail uses NULL for default message. 489 */ 490 torg = "(NULL)"; 491 } else { 492 torg = (char *)org; 493 } 494 495 /* Short cut */ 496 if (org == new || strcmp(torg, new) == 0 || 497 strchr(new, '%') == NULL) 498 return ((char *)new); 499 500 osz = strlen(torg) * FORMAT_SIZE; 501 ofmt = malloc(osz); 502 if (ofmt == NULL) 503 return ((char *)org); 504 505 olen = extract_format(torg, ofmt, osz, 0); 506 507 if (olen == -1) 508 syslog(LOG_AUTH|LOG_INFO, 509 "invalid format in gettext argument: \"%s\"", torg); 510 511 nsz = strlen(new) * FORMAT_SIZE; 512 nfmt = malloc(nsz); 513 if (nfmt == NULL) { 514 free(ofmt); 515 return ((char *)org); 516 } 517 518 nlen = extract_format(new, nfmt, nsz, strict); 519 520 if (nlen == -1) { 521 free(ofmt); 522 free(nfmt); 523 syslog(LOG_AUTH|LOG_NOTICE, 524 "invalid format in message file \"%.100s\" -> \"%s\"", 525 torg, new); 526 errno = EBADMSG; 527 return ((char *)org); 528 } 529 530 if (strict && (olen != nlen || olen == -1)) { 531 free(ofmt); 532 free(nfmt); 533 syslog(LOG_AUTH|LOG_NOTICE, 534 "incompatible format in message file: \"%.100s\" != \"%s\"", 535 torg, new); 536 errno = EBADMSG; 537 return ((char *)org); 538 } 539 540 if (strict && memcmp(ofmt, nfmt, nlen * FORMAT_SIZE) == 0) { 541 free(ofmt); 542 free(nfmt); 543 return ((char *)new); 544 } else { 545 if (!strict) { 546 char *n; 547 548 nlen *= FORMAT_SIZE; 549 550 for (n = nfmt; n = memchr(n, 'n', nfmt + nlen - n); 551 n++) { 552 int off = (n - nfmt); 553 554 if (off >= olen * FORMAT_SIZE || 555 ofmt[off] != 'n' || 556 ofmt[off+1] != nfmt[off+1]) { 557 free(ofmt); 558 free(nfmt); 559 syslog(LOG_AUTH|LOG_NOTICE, 560 "dangerous format in message file: " 561 "\"%.100s\" -> \"%s\"", torg, new); 562 errno = EBADMSG; 563 return ((char *)org); 564 } 565 } 566 free(ofmt); 567 free(nfmt); 568 return ((char *)new); 569 } 570 free(ofmt); 571 free(nfmt); 572 syslog(LOG_AUTH|LOG_NOTICE, 573 "incompatible format in message file \"%.100s\" != \"%s\"", 574 torg, new); 575 errno = EBADMSG; 576 return ((char *)org); 577 } 578 } 579 580 /* 581 * s1 is either name, or name=value 582 * s2 is name=value 583 * if names match, return value of s2, else NULL 584 * used for environment searching: see getenv 585 */ 586 const char * 587 nvmatch(const char *s1, const char *s2) 588 { 589 while (*s1 == *s2++) 590 if (*s1++ == '=') 591 return (s2); 592 if (*s1 == '\0' && *(s2-1) == '=') 593 return (s2); 594 return (NULL); 595 } 596 597 /* 598 * Handle NLSPATH environment variables in the environment. 599 * This routine is hooked into getenv/putenv at first call. 600 * 601 * The intention is to ignore NLSPATH in set-uid applications, 602 * and determine whether the NLSPATH in an application was set 603 * by the applications or derived from the user's environment. 604 */ 605 606 void 607 clean_env(void) 608 { 609 const char **p; 610 611 if (_environ == NULL) { 612 /* can happen when processing a SunOS 4.x AOUT file */ 613 nlspath_safe = 1; 614 return; 615 } 616 617 /* Find the first NLSPATH occurrence */ 618 for (p = _environ; *p; p++) 619 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL) 620 break; 621 622 if (!*p) /* None found, we're safe */ 623 nlspath_safe = 1; 624 else if (issetugid()) { /* Found and set-uid, clean */ 625 int off = 1; 626 627 for (p++; (p[-off] = p[0]) != NULL; p++) 628 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL) 629 off++; 630 631 nlspath_safe = 1; 632 } 633 } 634