xref: /illumos-gate/usr/src/lib/libc/port/gen/nlspath_checks.c (revision 503609a9497e27f206d815a06ce90a747d2ce573)
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
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 	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