import less(1)
[unleashed/tickless.git] / usr / src / lib / libc / port / gen / nlspath_checks.c
blobfb63ec8e6ab1eb274b2566b2c4bb0e87bdf38a0a
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #pragma ident "%Z%%M% %I% %E% SMI"
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"
45 extern const char **_environ;
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.
53 struct trusted_systemdirs {
54 const char *dir;
55 size_t dirlen;
58 #define _USRLIB "/usr/lib/"
59 #define _USRDT "/usr/dt/"
60 #define _USROW "/usr/openwin/"
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 }
69 static int8_t nlspath_safe;
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.
79 * The trust return value contains an indication of
80 * trustworthiness (i.e., does check_format need to be called or
81 * not)
84 int
85 nls_safe_open(const char *path, struct stat64 *statbuf, int *trust, int safe)
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;
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.
99 trust_path = *trust = safe || nlspath_safe;
101 fd = open(path, O_RDONLY);
103 if (fd < 0)
104 return (-1);
106 if (fstat64(fd, statbuf) == -1) {
107 (void) close(fd);
108 return (-1);
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.
123 * if the path is absolute and does not contain "/../",
124 * set abs_path.
126 if (*path == '/' && strstr(path, "/../") == NULL) {
127 abs_path = 1;
129 * if the path belongs to the trusted system directory,
130 * set systemdir.
132 for (p = prefix; p->dir; p++) {
133 if (strncmp(p->dir, path, p->dirlen) == 0) {
134 systemdir = 1;
135 break;
141 * If the owner is root or bin, set trust_owner.
143 if (statbuf->st_uid == 0 || statbuf->st_uid == 2) {
144 trust_owner = 1;
147 * If the file is neither other-writable nor group-writable by
148 * non-bin and non-root system group, set trust_group.
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;
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.
161 if (!*trust && systemdir) {
162 *trust = 1;
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.
174 if (*trust &&
175 (!abs_path || (!trust_owner && !trust_path) || !trust_group)) {
176 *trust = 0;
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().
184 if (issetugid()) {
185 if (!*trust) {
187 * Open should fail
189 (void) close(fd);
190 return (-1);
194 * if the path does not belong to the trusted system directory
195 * or if the owner is neither root nor bin, untrust it.
197 if (!systemdir || !trust_owner) {
198 *trust = 0;
202 return (fd);
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.
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
225 /* Number of bytes per canonical format entry */
226 #define FORMAT_SIZE 2
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.
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) \
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; \
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.
258 static int
259 extract_format(const char *fmt, char *norm, size_t sz, int strict)
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;
271 (void) memset(norm, '\0', sz);
273 #ifdef DEBUG
274 printf("Format \"%s\" canonical form: ", fmt);
275 #endif
277 for (; *fmt; fmt++) {
278 if (*fmt == '%') {
279 if (*++fmt == '%')
280 continue;
282 if (*fmt == '\0')
283 break;
285 prevarg = lastarg;
286 arg = ++lastarg;
288 t = 0;
289 while (*fmt && isdigit(*fmt))
290 t = t * 10 + *fmt++ - '0';
292 if (*fmt == '$') {
293 lastarg = arg = t - 1;
294 fmt++;
297 if (*fmt == '\0')
298 goto end;
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;
316 break;
319 while (*fmt && isdigit(*fmt))
320 fmt++;
322 if (*fmt == '*') {
323 if (isdigit(fmt[1])) {
324 fmt++;
325 t = 0;
326 while (*fmt && isdigit(*fmt))
327 t = t * 10 + *fmt++ - '0';
329 if (*fmt == '$') {
330 argp = t - 1;
331 STORE(norm, sz, argp, '*');
334 * If digits follow a '*', it is
335 * not loaded as an argument, the
336 * digits are used instead.
338 } else {
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 '*'
345 if (fmt[1] == '$')
346 fmt++;
347 else {
348 argp = arg;
349 prevarg = arg;
350 lastarg = ++arg;
351 STORE(norm, sz, argp, '*');
354 fmt++;
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;
366 if (*fmt == '\0')
367 goto end;
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;
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;
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;
417 fmt++;
419 done:
420 if (*fmt == '\0')
421 goto end;
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;
461 STORE(norm, sz, arg, conv);
462 norm[arg*FORMAT_SIZE + 1] = flag;
465 #ifdef DEBUG
466 for (t = 0; t < maxarg * FORMAT_SIZE; t += FORMAT_SIZE) {
467 printf("%c(%d)", norm[t], norm[t+1]);
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);
477 return (maxarg);
480 char *
481 check_format(const char *org, const char *new, int strict)
483 char *ofmt, *nfmt, *torg;
484 size_t osz, nsz;
485 int olen, nlen;
487 if (!org) {
489 * Default message is NULL.
490 * dtmail uses NULL for default message.
492 torg = "(NULL)";
493 } else {
494 torg = (char *)org;
497 /* Short cut */
498 if (org == new || strcmp(torg, new) == 0 ||
499 strchr(new, '%') == NULL)
500 return ((char *)new);
502 osz = strlen(torg) * FORMAT_SIZE;
503 ofmt = malloc(osz);
504 if (ofmt == NULL)
505 return ((char *)org);
507 olen = extract_format(torg, ofmt, osz, 0);
509 if (olen == -1)
510 syslog(LOG_AUTH|LOG_INFO,
511 "invalid format in gettext argument: \"%s\"", torg);
513 nsz = strlen(new) * FORMAT_SIZE;
514 nfmt = malloc(nsz);
515 if (nfmt == NULL) {
516 free(ofmt);
517 return ((char *)org);
520 nlen = extract_format(new, nfmt, nsz, strict);
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);
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);
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;
550 nlen *= FORMAT_SIZE;
552 for (n = nfmt; n = memchr(n, 'n', nfmt + nlen - n);
553 n++) {
554 int off = (n - nfmt);
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);
568 free(ofmt);
569 free(nfmt);
570 return ((char *)new);
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);
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
588 const char *
589 nvmatch(const char *s1, const char *s2)
591 while (*s1 == *s2++)
592 if (*s1++ == '=')
593 return (s2);
594 if (*s1 == '\0' && *(s2-1) == '=')
595 return (s2);
596 return (NULL);
600 * Handle NLSPATH environment variables in the environment.
601 * This routine is hooked into getenv/putenv at first call.
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.
608 void
609 clean_env(void)
611 const char **p;
613 if (_environ == NULL) {
614 /* can happen when processing a SunOS 4.x AOUT file */
615 nlspath_safe = 1;
616 return;
619 /* Find the first NLSPATH occurrence */
620 for (p = _environ; *p; p++)
621 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
622 break;
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;
629 for (p++; (p[-off] = p[0]) != '\0'; p++)
630 if (**p == 'N' && nvmatch("NLSPATH", *p) != NULL)
631 off++;
633 nlspath_safe = 1;