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