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
nls_safe_open(const char * path,struct stat64 * statbuf,int * trust,int safe)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
extract_format(const char * fmt,char * norm,size_t sz,int strict)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 NLS_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 NLS_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 *
check_format(const char * org,const char * new,int strict)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;
551 (n = memchr(n, 'n', nfmt + nlen - n)) != NULL;
552 n++) {
553 int off = (n - nfmt);
554
555 if (off >= olen * FORMAT_SIZE ||
556 ofmt[off] != 'n' ||
557 ofmt[off+1] != nfmt[off+1]) {
558 free(ofmt);
559 free(nfmt);
560 syslog(LOG_AUTH|LOG_NOTICE,
561 "dangerous format in message file: "
562 "\"%.100s\" -> \"%s\"", torg, new);
563 errno = EBADMSG;
564 return ((char *)org);
565 }
566 }
567 free(ofmt);
568 free(nfmt);
569 return ((char *)new);
570 }
571 free(ofmt);
572 free(nfmt);
573 syslog(LOG_AUTH|LOG_NOTICE,
574 "incompatible format in message file \"%.100s\" != \"%s\"",
575 torg, new);
576 errno = EBADMSG;
577 return ((char *)org);
578 }
579 }
580
581 /*
582 * s1 is either name, or name=value
583 * s2 is name=value
584 * if names match, return value of s2, else NULL
585 * used for environment searching: see getenv
586 */
587 const char *
nvmatch(const char * s1,const char * s2)588 nvmatch(const char *s1, const char *s2)
589 {
590 while (*s1 == *s2++)
591 if (*s1++ == '=')
592 return (s2);
593 if (*s1 == '\0' && *(s2-1) == '=')
594 return (s2);
595 return (NULL);
596 }
597
598 /*
599 * Handle NLSPATH environment variables in the environment.
600 * This routine is hooked into getenv/putenv at first call.
601 *
602 * The intention is to ignore NLSPATH in set-uid applications,
603 * and determine whether the NLSPATH in an application was set
604 * by the applications or derived from the user's environment.
605 */
606
607 void
clean_env(void)608 clean_env(void)
609 {
610 const char **p;
611
612 if (_environ == NULL) {
613 /* can happen when processing a SunOS 4.x AOUT file */
614 nlspath_safe = 1;
615 return;
616 }
617
618 /* Find the first NLSPATH occurrence */
619 for (p = _environ; *p; p++)
620 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
621 break;
622
623 if (!*p) /* None found, we're safe */
624 nlspath_safe = 1;
625 else if (issetugid()) { /* Found and set-uid, clean */
626 int off = 1;
627
628 for (p++; (p[-off] = p[0]) != NULL; p++)
629 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
630 off++;
631
632 nlspath_safe = 1;
633 }
634 }
635