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