Damien Miller | 1acc058 | 2016-02-23 16:12:13 +1100 | [diff] [blame] | 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 152 | usage(void) |
| 153 | { |
| 154 | fprintf(stderr, "check-perm -m [chroot | keys-command] [path]\n"); |
| 155 | exit(1); |
| 156 | } |
| 157 | |
| 158 | int |
| 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 | } |