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