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