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 #pragma ident "%Z%%M% %I% %E% SMI"
28
29 #include "lint.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
nls_safe_open(const char * path,struct stat64 * statbuf,int * trust,int safe)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
extract_format(const char * fmt,char * norm,size_t sz,int strict)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 *
check_format(const char * org,const char * new,int strict)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 *
nvmatch(const char * s1,const char * s2)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
clean_env(void)609 clean_env(void)
610 {
611 const char **p;
612
613 if (_environ == NULL) {
614 /* can happen when processing a SunOS 4.x AOUT file */
615 nlspath_safe = 1;
616 return;
617 }
618
619 /* Find the first NLSPATH occurrence */
620 for (p = _environ; *p; p++)
621 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
622 break;
623
624 if (!*p) /* None found, we're safe */
625 nlspath_safe = 1;
626 else if (issetugid()) { /* Found and set-uid, clean */
627 int off = 1;
628
629 for (p++; (p[-off] = p[0]) != '\0'; p++)
630 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
631 off++;
632
633 nlspath_safe = 1;
634 }
635 }
636