check-perm.c revision 323134
1/*
2 * Placed in the public domain
3 */
4
5/* $OpenBSD: modpipe.c,v 1.6 2013/11/21 03:16:47 djm Exp $ */
6
7#include "includes.h"
8
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <unistd.h>
12#include <stdio.h>
13#include <string.h>
14#include <stdarg.h>
15#include <stdlib.h>
16#include <errno.h>
17#include <pwd.h>
18#ifdef HAVE_LIBGEN_H
19#include <libgen.h>
20#endif
21
22static void
23fatal(const char *fmt, ...)
24{
25	va_list args;
26
27	va_start(args, fmt);
28	vfprintf(stderr, fmt, args);
29	fputc('\n', stderr);
30	va_end(args);
31	exit(1);
32}
33/* Based on session.c. NB. keep tests in sync */
34static void
35safely_chroot(const char *path, uid_t uid)
36{
37	const char *cp;
38	char component[PATH_MAX];
39	struct stat st;
40
41	if (*path != '/')
42		fatal("chroot path does not begin at root");
43	if (strlen(path) >= sizeof(component))
44		fatal("chroot path too long");
45
46	/*
47	 * Descend the path, checking that each component is a
48	 * root-owned directory with strict permissions.
49	 */
50	for (cp = path; cp != NULL;) {
51		if ((cp = strchr(cp, '/')) == NULL)
52			strlcpy(component, path, sizeof(component));
53		else {
54			cp++;
55			memcpy(component, path, cp - path);
56			component[cp - path] = '\0';
57		}
58
59		/* debug3("%s: checking '%s'", __func__, component); */
60
61		if (stat(component, &st) != 0)
62			fatal("%s: stat(\"%s\"): %s", __func__,
63			    component, strerror(errno));
64		if (st.st_uid != 0 || (st.st_mode & 022) != 0)
65			fatal("bad ownership or modes for chroot "
66			    "directory %s\"%s\"",
67			    cp == NULL ? "" : "component ", component);
68		if (!S_ISDIR(st.st_mode))
69			fatal("chroot path %s\"%s\" is not a directory",
70			    cp == NULL ? "" : "component ", component);
71
72	}
73
74	if (chdir(path) == -1)
75		fatal("Unable to chdir to chroot path \"%s\": "
76		    "%s", path, strerror(errno));
77}
78
79/* from platform.c */
80int
81platform_sys_dir_uid(uid_t uid)
82{
83	if (uid == 0)
84		return 1;
85#ifdef PLATFORM_SYS_DIR_UID
86	if (uid == PLATFORM_SYS_DIR_UID)
87		return 1;
88#endif
89	return 0;
90}
91
92/* from auth.c */
93int
94auth_secure_path(const char *name, struct stat *stp, const char *pw_dir,
95    uid_t uid, char *err, size_t errlen)
96{
97	char buf[PATH_MAX], homedir[PATH_MAX];
98	char *cp;
99	int comparehome = 0;
100	struct stat st;
101
102	if (realpath(name, buf) == NULL) {
103		snprintf(err, errlen, "realpath %s failed: %s", name,
104		    strerror(errno));
105		return -1;
106	}
107	if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL)
108		comparehome = 1;
109
110	if (!S_ISREG(stp->st_mode)) {
111		snprintf(err, errlen, "%s is not a regular file", buf);
112		return -1;
113	}
114	if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
115	    (stp->st_mode & 022) != 0) {
116		snprintf(err, errlen, "bad ownership or modes for file %s",
117		    buf);
118		return -1;
119	}
120
121	/* for each component of the canonical path, walking upwards */
122	for (;;) {
123		if ((cp = dirname(buf)) == NULL) {
124			snprintf(err, errlen, "dirname() failed");
125			return -1;
126		}
127		strlcpy(buf, cp, sizeof(buf));
128
129		if (stat(buf, &st) < 0 ||
130		    (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
131		    (st.st_mode & 022) != 0) {
132			snprintf(err, errlen,
133			    "bad ownership or modes for directory %s", buf);
134			return -1;
135		}
136
137		/* If are past the homedir then we can stop */
138		if (comparehome && strcmp(homedir, buf) == 0)
139			break;
140
141		/*
142		 * dirname should always complete with a "/" path,
143		 * but we can be paranoid and check for "." too
144		 */
145		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
146			break;
147	}
148	return 0;
149}
150
151static void
152usage(void)
153{
154	fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n");
155	exit(1);
156}
157
158int
159main(int argc, char **argv)
160{
161	const char *path = ".";
162	char errmsg[256];
163	int ch, mode = -1;
164	extern char *optarg;
165	extern int optind;
166	struct stat st;
167
168	while ((ch = getopt(argc, argv, "hm:")) != -1) {
169		switch (ch) {
170		case 'm':
171			if (strcasecmp(optarg, "chroot") == 0)
172				mode = 1;
173			else if (strcasecmp(optarg, "keys-command") == 0)
174				mode = 2;
175			else {
176				fprintf(stderr, "Invalid -m option\n"),
177				usage();
178			}
179			break;
180		default:
181			usage();
182		}
183	}
184	argc -= optind;
185	argv += optind;
186
187	if (argc > 1)
188		usage();
189	else if (argc == 1)
190		path = argv[0];
191
192	if (mode == 1)
193		safely_chroot(path, getuid());
194	else if (mode == 2) {
195		if (stat(path, &st) < 0)
196			fatal("Could not stat %s: %s", path, strerror(errno));
197		if (auth_secure_path(path, &st, NULL, 0,
198		    errmsg, sizeof(errmsg)) != 0)
199			fatal("Unsafe %s: %s", path, errmsg);
200	} else {
201		fprintf(stderr, "Invalid mode\n");
202		usage();
203	}
204	return 0;
205}
206