xref: /openssh-portable/regress/check-perm.c (revision 1acc058d)
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 
22 static void
fatal(const char * fmt,...)23 fatal(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 */
34 static void
safely_chroot(const char * path,uid_t uid)35 safely_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 */
80 int
platform_sys_dir_uid(uid_t uid)81 platform_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 */
93 int
auth_secure_path(const char * name,struct stat * stp,const char * pw_dir,uid_t uid,char * err,size_t errlen)94 auth_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 
151 static void
usage(void)152 usage(void)
153 {
154 	fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n");
155 	exit(1);
156 }
157 
158 int
main(int argc,char ** argv)159 main(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