Eric Paris | e134013 | 2011-08-15 20:10:14 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Authors: Dan Walsh <dwalsh@redhat.com> |
| 3 | * Authors: Thomas Liu <tliu@fedoraproject.org> |
| 4 | */ |
| 5 | |
Eric Paris | 39066bd | 2011-08-02 13:58:07 -0400 | [diff] [blame] | 6 | #define _GNU_SOURCE |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 7 | #include <signal.h> |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 8 | #include <sys/fsuid.h> |
| 9 | #include <sys/stat.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 10 | #include <sys/types.h> |
| 11 | #include <sys/wait.h> |
| 12 | #include <syslog.h> |
| 13 | #include <sys/mount.h> |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 14 | #include <glob.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 15 | #include <pwd.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 16 | #include <sched.h> |
| 17 | #include <string.h> |
| 18 | #include <stdio.h> |
Eric Paris | 4347a5c | 2011-08-03 15:09:22 -0400 | [diff] [blame] | 19 | #include <regex.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 20 | #include <unistd.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <cap-ng.h> |
| 23 | #include <getopt.h> /* for getopt_long() form of getopt() */ |
| 24 | #include <limits.h> |
| 25 | #include <stdlib.h> |
| 26 | #include <errno.h> |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 27 | #include <fcntl.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 28 | |
| 29 | #include <selinux/selinux.h> |
| 30 | #include <selinux/context.h> /* for context-mangling functions */ |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 31 | #include <dirent.h> |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 32 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 33 | #ifdef USE_NLS |
| 34 | #include <locale.h> /* for setlocale() */ |
| 35 | #include <libintl.h> /* for gettext() */ |
| 36 | #define _(msgid) gettext (msgid) |
| 37 | #else |
| 38 | #define _(msgid) (msgid) |
| 39 | #endif |
| 40 | |
Steve Lawrence | 582fd00 | 2010-06-10 16:37:59 -0400 | [diff] [blame] | 41 | #ifndef MS_REC |
| 42 | #define MS_REC 1<<14 |
| 43 | #endif |
| 44 | |
Dan Walsh | 70c582f | 2012-01-03 13:45:08 -0500 | [diff] [blame] | 45 | #ifndef MS_SLAVE |
| 46 | #define MS_SLAVE 1<<19 |
Steve Lawrence | 582fd00 | 2010-06-10 16:37:59 -0400 | [diff] [blame] | 47 | #endif |
| 48 | |
Eric Paris | e134013 | 2011-08-15 20:10:14 -0400 | [diff] [blame] | 49 | #ifndef PACKAGE |
| 50 | #define PACKAGE "policycoreutils" /* the name of this package lang translation */ |
| 51 | #endif |
| 52 | |
Eric Paris | 4347a5c | 2011-08-03 15:09:22 -0400 | [diff] [blame] | 53 | #define BUF_SIZE 1024 |
Eric Paris | 406ae12 | 2011-08-03 16:23:12 -0400 | [diff] [blame] | 54 | #define DEFAULT_PATH "/usr/bin:/bin" |
Dan Walsh | de0795a | 2014-05-12 13:19:20 -0400 | [diff] [blame] | 55 | #define USAGE_STRING _("USAGE: seunshare [ -v ] [ -C ] [ -k ] [ -t tmpdir ] [ -h homedir ] [ -Z CONTEXT ] -- executable [args] ") |
Eric Paris | 406ae12 | 2011-08-03 16:23:12 -0400 | [diff] [blame] | 56 | |
| 57 | static int verbose = 0; |
Dan Walsh | 216f456 | 2011-07-06 20:52:05 -0400 | [diff] [blame] | 58 | static int child = 0; |
Eric Paris | 406ae12 | 2011-08-03 16:23:12 -0400 | [diff] [blame] | 59 | |
Dan Walsh | 1f0b5bd | 2012-02-22 15:55:39 -0500 | [diff] [blame] | 60 | static capng_select_t cap_set = CAPNG_SELECT_CAPS; |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 61 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 62 | /** |
Eric Paris | d6c0960 | 2011-08-05 13:33:35 -0400 | [diff] [blame] | 63 | * This function will drop all capabilities. |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 64 | */ |
Nicolas Iooss | c4a4a1a | 2014-09-14 23:41:49 +0200 | [diff] [blame] | 65 | static int drop_caps(void) |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 66 | { |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 67 | if (capng_have_capabilities(cap_set) == CAPNG_NONE) |
Eric Paris | d6c0960 | 2011-08-05 13:33:35 -0400 | [diff] [blame] | 68 | return 0; |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 69 | capng_clear(cap_set); |
| 70 | if (capng_lock() == -1 || capng_apply(cap_set) == -1) { |
Eric Paris | d6c0960 | 2011-08-05 13:33:35 -0400 | [diff] [blame] | 71 | fprintf(stderr, _("Failed to drop all capabilities\n")); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 72 | return -1; |
| 73 | } |
Eric Paris | d6c0960 | 2011-08-05 13:33:35 -0400 | [diff] [blame] | 74 | return 0; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * This function will drop all privileges. |
| 79 | */ |
| 80 | static int drop_privs(uid_t uid) |
| 81 | { |
| 82 | if (drop_caps() == -1 || setresuid(uid, uid, uid) == -1) { |
| 83 | fprintf(stderr, _("Failed to drop privileges\n")); |
| 84 | return -1; |
| 85 | } |
| 86 | return 0; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 87 | } |
| 88 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 89 | /** |
Dan Walsh | 216f456 | 2011-07-06 20:52:05 -0400 | [diff] [blame] | 90 | * If the user sends a siginto to seunshare, kill the child's session |
| 91 | */ |
| 92 | void handler(int sig) { |
| 93 | if (child > 0) kill(-child,sig); |
| 94 | } |
| 95 | |
| 96 | /** |
Eric Paris | 89e3dd6 | 2011-08-03 14:27:32 -0400 | [diff] [blame] | 97 | * Take care of any signal setup. |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 98 | */ |
| 99 | static int set_signal_handles(void) |
| 100 | { |
| 101 | sigset_t empty; |
| 102 | |
| 103 | /* Empty the signal mask in case someone is blocking a signal */ |
| 104 | if (sigemptyset(&empty)) { |
| 105 | fprintf(stderr, "Unable to obtain empty signal set\n"); |
| 106 | return -1; |
| 107 | } |
| 108 | |
| 109 | (void)sigprocmask(SIG_SETMASK, &empty, NULL); |
| 110 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 111 | /* Terminate on SIGHUP */ |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 112 | if (signal(SIGHUP, SIG_DFL) == SIG_ERR) { |
| 113 | perror("Unable to set SIGHUP handler"); |
| 114 | return -1; |
| 115 | } |
| 116 | |
Dan Walsh | 216f456 | 2011-07-06 20:52:05 -0400 | [diff] [blame] | 117 | if (signal(SIGINT, handler) == SIG_ERR) { |
| 118 | perror("Unable to set SIGINT handler"); |
| 119 | return -1; |
| 120 | } |
| 121 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 122 | return 0; |
| 123 | } |
| 124 | |
Eric Paris | f6558d9 | 2011-08-05 14:06:34 -0400 | [diff] [blame] | 125 | #define status_to_retval(status,retval) do { \ |
| 126 | if ((status) == -1) \ |
| 127 | retval = -1; \ |
| 128 | else if (WIFEXITED((status))) \ |
| 129 | retval = WEXITSTATUS((status)); \ |
| 130 | else if (WIFSIGNALED((status))) \ |
| 131 | retval = 128 + WTERMSIG((status)); \ |
| 132 | else \ |
| 133 | retval = -1; \ |
| 134 | } while(0) |
| 135 | |
| 136 | /** |
| 137 | * Spawn external command using system() with dropped privileges. |
| 138 | * TODO: avoid system() and use exec*() instead |
| 139 | */ |
| 140 | static int spawn_command(const char *cmd, uid_t uid){ |
Nicolas Iooss | f978b1b | 2014-09-14 23:41:35 +0200 | [diff] [blame] | 141 | int childpid; |
Eric Paris | f6558d9 | 2011-08-05 14:06:34 -0400 | [diff] [blame] | 142 | int status = -1; |
| 143 | |
| 144 | if (verbose > 1) |
| 145 | printf("spawn_command: %s\n", cmd); |
| 146 | |
Nicolas Iooss | f978b1b | 2014-09-14 23:41:35 +0200 | [diff] [blame] | 147 | childpid = fork(); |
| 148 | if (childpid == -1) { |
Eric Paris | f6558d9 | 2011-08-05 14:06:34 -0400 | [diff] [blame] | 149 | perror(_("Unable to fork")); |
| 150 | return status; |
| 151 | } |
| 152 | |
Nicolas Iooss | f978b1b | 2014-09-14 23:41:35 +0200 | [diff] [blame] | 153 | if (childpid == 0) { |
Eric Paris | f6558d9 | 2011-08-05 14:06:34 -0400 | [diff] [blame] | 154 | if (drop_privs(uid) != 0) exit(-1); |
| 155 | |
| 156 | status = system(cmd); |
| 157 | status_to_retval(status, status); |
| 158 | exit(status); |
| 159 | } |
| 160 | |
Nicolas Iooss | f978b1b | 2014-09-14 23:41:35 +0200 | [diff] [blame] | 161 | waitpid(childpid, &status, 0); |
Eric Paris | f6558d9 | 2011-08-05 14:06:34 -0400 | [diff] [blame] | 162 | status_to_retval(status, status); |
| 163 | return status; |
| 164 | } |
| 165 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 166 | /** |
Eric Paris | bf22cff | 2011-08-05 14:36:29 -0400 | [diff] [blame] | 167 | * Check file/directory ownership, struct stat * must be passed to the |
| 168 | * functions. |
| 169 | */ |
| 170 | static int check_owner_uid(uid_t uid, const char *file, struct stat *st) { |
| 171 | if (S_ISLNK(st->st_mode)) { |
| 172 | fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file); |
| 173 | return -1; |
| 174 | } |
| 175 | if (st->st_uid != uid) { |
| 176 | fprintf(stderr, _("Error: %s not owned by UID %d\n"), file, uid); |
| 177 | return -1; |
| 178 | } |
| 179 | return 0; |
| 180 | } |
| 181 | |
| 182 | static int check_owner_gid(gid_t gid, const char *file, struct stat *st) { |
| 183 | if (S_ISLNK(st->st_mode)) { |
| 184 | fprintf(stderr, _("Error: %s must not be a symbolic link\n"), file); |
| 185 | return -1; |
| 186 | } |
| 187 | if (st->st_gid != gid) { |
| 188 | fprintf(stderr, _("Error: %s not owned by GID %d\n"), file, gid); |
| 189 | return -1; |
| 190 | } |
| 191 | return 0; |
| 192 | } |
| 193 | |
| 194 | #define equal_stats(one,two) \ |
| 195 | ((one)->st_dev == (two)->st_dev && (one)->st_ino == (two)->st_ino && \ |
| 196 | (one)->st_uid == (two)->st_uid && (one)->st_gid == (two)->st_gid && \ |
| 197 | (one)->st_mode == (two)->st_mode) |
| 198 | |
| 199 | /** |
| 200 | * Sanity check specified directory. Store stat info for future comparison, or |
| 201 | * compare with previously saved info to detect replaced directories. |
| 202 | * Note: This function does not perform owner checks. |
| 203 | */ |
| 204 | static int verify_directory(const char *dir, struct stat *st_in, struct stat *st_out) { |
| 205 | struct stat sb; |
| 206 | |
| 207 | if (st_out == NULL) st_out = &sb; |
| 208 | |
| 209 | if (lstat(dir, st_out) == -1) { |
| 210 | fprintf(stderr, _("Failed to stat %s: %s\n"), dir, strerror(errno)); |
| 211 | return -1; |
| 212 | } |
| 213 | if (! S_ISDIR(st_out->st_mode)) { |
| 214 | fprintf(stderr, _("Error: %s is not a directory: %s\n"), dir, strerror(errno)); |
| 215 | return -1; |
| 216 | } |
| 217 | if (st_in && !equal_stats(st_in, st_out)) { |
| 218 | fprintf(stderr, _("Error: %s was replaced by a different directory\n"), dir); |
| 219 | return -1; |
| 220 | } |
| 221 | |
| 222 | return 0; |
| 223 | } |
| 224 | |
| 225 | /** |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 226 | * This function checks to see if the shell is known in /etc/shells. |
| 227 | * If so, it returns 0. On error or illegal shell, it returns -1. |
| 228 | */ |
| 229 | static int verify_shell(const char *shell_name) |
| 230 | { |
| 231 | int rc = -1; |
| 232 | const char *buf; |
| 233 | |
| 234 | if (!(shell_name && shell_name[0])) |
| 235 | return rc; |
| 236 | |
| 237 | while ((buf = getusershell()) != NULL) { |
| 238 | /* ignore comments */ |
| 239 | if (*buf == '#') |
| 240 | continue; |
| 241 | |
| 242 | /* check the shell skipping newline char */ |
| 243 | if (!strcmp(shell_name, buf)) { |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 244 | rc = 0; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 245 | break; |
| 246 | } |
| 247 | } |
| 248 | endusershell(); |
| 249 | return rc; |
| 250 | } |
| 251 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 252 | /** |
| 253 | * Mount directory and check that we mounted the right directory. |
| 254 | */ |
| 255 | static int seunshare_mount(const char *src, const char *dst, struct stat *src_st) |
| 256 | { |
Dan Walsh | 70c582f | 2012-01-03 13:45:08 -0500 | [diff] [blame] | 257 | int flags = 0; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 258 | int is_tmp = 0; |
| 259 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 260 | if (verbose) |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 261 | printf(_("Mounting %s on %s\n"), src, dst); |
| 262 | |
| 263 | if (strcmp("/tmp", dst) == 0) { |
| 264 | flags = flags | MS_NODEV | MS_NOSUID | MS_NOEXEC; |
| 265 | is_tmp = 1; |
| 266 | } |
| 267 | |
| 268 | /* mount directory */ |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 269 | if (mount(src, dst, NULL, MS_BIND | flags, NULL) < 0) { |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 270 | fprintf(stderr, _("Failed to mount %s on %s: %s\n"), src, dst, strerror(errno)); |
| 271 | return -1; |
| 272 | } |
| 273 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 274 | /* verify whether we mounted what we expected to mount */ |
| 275 | if (verify_directory(dst, src_st, NULL) < 0) return -1; |
| 276 | |
| 277 | /* bind mount /tmp on /var/tmp too */ |
| 278 | if (is_tmp) { |
| 279 | if (verbose) |
| 280 | printf(_("Mounting /tmp on /var/tmp\n")); |
| 281 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 282 | if (mount("/tmp", "/var/tmp", NULL, MS_BIND | flags, NULL) < 0) { |
| 283 | fprintf(stderr, _("Failed to mount /tmp on /var/tmp: %s\n"), strerror(errno)); |
| 284 | return -1; |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | return 0; |
| 289 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 290 | } |
| 291 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 292 | /* |
Nicolas Iooss | b550c0e | 2019-08-05 22:11:20 +0200 | [diff] [blame] | 293 | If path is empty or ends with "/." or "/.. return -1 else return 0; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 294 | */ |
| 295 | static int bad_path(const char *path) { |
| 296 | const char *ptr; |
| 297 | ptr = path; |
| 298 | while (*ptr) ptr++; |
| 299 | if (ptr == path) return -1; // ptr null |
| 300 | ptr--; |
| 301 | if (ptr != path && *ptr == '.') { |
| 302 | ptr--; |
| 303 | if (*ptr == '/') return -1; // path ends in /. |
| 304 | if (*ptr == '.') { |
| 305 | if (ptr != path) { |
| 306 | ptr--; |
| 307 | if (*ptr == '/') return -1; // path ends in /.. |
| 308 | } |
| 309 | } |
| 310 | } |
| 311 | return 0; |
| 312 | } |
| 313 | |
| 314 | static int rsynccmd(const char * src, const char *dst, char **cmdbuf) |
| 315 | { |
| 316 | char *buf = NULL; |
| 317 | char *newbuf = NULL; |
| 318 | glob_t fglob; |
| 319 | fglob.gl_offs = 0; |
| 320 | int flags = GLOB_PERIOD; |
| 321 | unsigned int i = 0; |
| 322 | int rc = -1; |
| 323 | |
| 324 | /* match glob for all files in src dir */ |
| 325 | if (asprintf(&buf, "%s/*", src) == -1) { |
| 326 | fprintf(stderr, "Out of memory\n"); |
| 327 | return -1; |
| 328 | } |
| 329 | |
| 330 | if (glob(buf, flags, NULL, &fglob) != 0) { |
| 331 | free(buf); buf = NULL; |
| 332 | return -1; |
| 333 | } |
| 334 | |
| 335 | free(buf); buf = NULL; |
| 336 | |
| 337 | for ( i=0; i < fglob.gl_pathc; i++) { |
| 338 | const char *path = fglob.gl_pathv[i]; |
| 339 | |
| 340 | if (bad_path(path)) continue; |
| 341 | |
| 342 | if (!buf) { |
| 343 | if (asprintf(&newbuf, "\'%s\'", path) == -1) { |
| 344 | fprintf(stderr, "Out of memory\n"); |
| 345 | goto err; |
| 346 | } |
| 347 | } else { |
| 348 | if (asprintf(&newbuf, "%s \'%s\'", buf, path) == -1) { |
| 349 | fprintf(stderr, "Out of memory\n"); |
| 350 | goto err; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | free(buf); buf = newbuf; |
| 355 | newbuf = NULL; |
| 356 | } |
| 357 | |
| 358 | if (buf) { |
| 359 | if (asprintf(&newbuf, "/usr/bin/rsync -trlHDq %s '%s'", buf, dst) == -1) { |
| 360 | fprintf(stderr, "Out of memory\n"); |
| 361 | goto err; |
| 362 | } |
| 363 | *cmdbuf=newbuf; |
| 364 | } |
| 365 | else { |
| 366 | *cmdbuf=NULL; |
| 367 | } |
| 368 | rc = 0; |
| 369 | |
| 370 | err: |
| 371 | free(buf); buf = NULL; |
| 372 | globfree(&fglob); |
| 373 | return rc; |
| 374 | } |
| 375 | |
| 376 | /** |
| 377 | * Clean up runtime temporary directory. Returns 0 if no problem was detected, |
| 378 | * >0 if some error was detected, but errors here are treated as non-fatal and |
| 379 | * left to tmpwatch to finish incomplete cleanup. |
| 380 | */ |
| 381 | static int cleanup_tmpdir(const char *tmpdir, const char *src, |
| 382 | struct passwd *pwd, int copy_content) |
| 383 | { |
| 384 | char *cmdbuf = NULL; |
| 385 | int rc = 0; |
| 386 | |
| 387 | /* rsync files back */ |
| 388 | if (copy_content) { |
| 389 | if (asprintf(&cmdbuf, "/usr/bin/rsync --exclude=.X11-unix -utrlHDq --delete '%s/' '%s/'", tmpdir, src) == -1) { |
| 390 | fprintf(stderr, _("Out of memory\n")); |
| 391 | cmdbuf = NULL; |
| 392 | rc++; |
| 393 | } |
| 394 | if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) { |
| 395 | fprintf(stderr, _("Failed to copy files from the runtime temporary directory\n")); |
| 396 | rc++; |
| 397 | } |
| 398 | free(cmdbuf); cmdbuf = NULL; |
| 399 | } |
| 400 | |
| 401 | /* remove files from the runtime temporary directory */ |
| 402 | if (asprintf(&cmdbuf, "/bin/rm -r '%s/' 2>/dev/null", tmpdir) == -1) { |
| 403 | fprintf(stderr, _("Out of memory\n")); |
| 404 | cmdbuf = NULL; |
| 405 | rc++; |
| 406 | } |
| 407 | /* this may fail if there's root-owned file left in the runtime tmpdir */ |
| 408 | if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) rc++; |
| 409 | free(cmdbuf); cmdbuf = NULL; |
| 410 | |
| 411 | /* remove runtime temporary directory */ |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 412 | if ((uid_t)setfsuid(0) != 0) { |
Nicolas Iooss | b550c0e | 2019-08-05 22:11:20 +0200 | [diff] [blame] | 413 | /* setfsuid does not return error, but this check makes code checkers happy */ |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 414 | rc++; |
| 415 | } |
| 416 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 417 | if (rmdir(tmpdir) == -1) |
| 418 | fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno)); |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 419 | if ((uid_t)setfsuid(pwd->pw_uid) != 0) { |
| 420 | fprintf(stderr, _("unable to switch back to user after clearing tmp dir\n")); |
| 421 | rc++; |
| 422 | } |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 423 | |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 424 | return rc; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 425 | } |
| 426 | |
| 427 | /** |
| 428 | * seunshare will create a tmpdir in /tmp, with root ownership. The parent |
| 429 | * process waits for it child to exit to attempt to remove the directory. If |
| 430 | * it fails to remove the directory, we will need to rely on tmpreaper/tmpwatch |
| 431 | * to clean it up. |
| 432 | */ |
| 433 | static char *create_tmpdir(const char *src, struct stat *src_st, |
| 434 | struct stat *out_st, struct passwd *pwd, security_context_t execcon) |
| 435 | { |
| 436 | char *tmpdir = NULL; |
| 437 | char *cmdbuf = NULL; |
| 438 | int fd_t = -1, fd_s = -1; |
| 439 | struct stat tmp_st; |
| 440 | security_context_t con = NULL; |
| 441 | |
| 442 | /* get selinux context */ |
| 443 | if (execcon) { |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 444 | if ((uid_t)setfsuid(pwd->pw_uid) != 0) |
| 445 | goto err; |
| 446 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 447 | if ((fd_s = open(src, O_RDONLY)) < 0) { |
| 448 | fprintf(stderr, _("Failed to open directory %s: %s\n"), src, strerror(errno)); |
| 449 | goto err; |
| 450 | } |
| 451 | if (fstat(fd_s, &tmp_st) == -1) { |
| 452 | fprintf(stderr, _("Failed to stat directory %s: %s\n"), src, strerror(errno)); |
| 453 | goto err; |
| 454 | } |
| 455 | if (!equal_stats(src_st, &tmp_st)) { |
| 456 | fprintf(stderr, _("Error: %s was replaced by a different directory\n"), src); |
| 457 | goto err; |
| 458 | } |
| 459 | if (fgetfilecon(fd_s, &con) == -1) { |
| 460 | fprintf(stderr, _("Failed to get context of the directory %s: %s\n"), src, strerror(errno)); |
| 461 | goto err; |
| 462 | } |
| 463 | |
| 464 | /* ok to not reach this if there is an error */ |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 465 | if ((uid_t)setfsuid(0) != pwd->pw_uid) |
| 466 | goto err; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 467 | } |
| 468 | |
| 469 | if (asprintf(&tmpdir, "/tmp/.sandbox-%s-XXXXXX", pwd->pw_name) == -1) { |
| 470 | fprintf(stderr, _("Out of memory\n")); |
| 471 | tmpdir = NULL; |
| 472 | goto err; |
| 473 | } |
| 474 | if (mkdtemp(tmpdir) == NULL) { |
| 475 | fprintf(stderr, _("Failed to create temporary directory: %s\n"), strerror(errno)); |
| 476 | goto err; |
| 477 | } |
| 478 | |
| 479 | /* temporary directory must be owned by root:user */ |
| 480 | if (verify_directory(tmpdir, NULL, out_st) < 0) { |
| 481 | goto err; |
| 482 | } |
| 483 | |
| 484 | if (check_owner_uid(0, tmpdir, out_st) < 0) |
| 485 | goto err; |
| 486 | |
| 487 | if (check_owner_gid(getgid(), tmpdir, out_st) < 0) |
| 488 | goto err; |
| 489 | |
| 490 | /* change permissions of the temporary directory */ |
| 491 | if ((fd_t = open(tmpdir, O_RDONLY)) < 0) { |
| 492 | fprintf(stderr, _("Failed to open directory %s: %s\n"), tmpdir, strerror(errno)); |
| 493 | goto err; |
| 494 | } |
| 495 | if (fstat(fd_t, &tmp_st) == -1) { |
| 496 | fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno)); |
| 497 | goto err; |
| 498 | } |
| 499 | if (!equal_stats(out_st, &tmp_st)) { |
| 500 | fprintf(stderr, _("Error: %s was replaced by a different directory\n"), tmpdir); |
| 501 | goto err; |
| 502 | } |
| 503 | if (fchmod(fd_t, 01770) == -1) { |
| 504 | fprintf(stderr, _("Unable to change mode on %s: %s\n"), tmpdir, strerror(errno)); |
| 505 | goto err; |
| 506 | } |
| 507 | /* re-stat again to pick change mode */ |
| 508 | if (fstat(fd_t, out_st) == -1) { |
| 509 | fprintf(stderr, _("Failed to stat directory %s: %s\n"), tmpdir, strerror(errno)); |
| 510 | goto err; |
| 511 | } |
| 512 | |
| 513 | /* copy selinux context */ |
| 514 | if (execcon) { |
| 515 | if (fsetfilecon(fd_t, con) == -1) { |
| 516 | fprintf(stderr, _("Failed to set context of the directory %s: %s\n"), tmpdir, strerror(errno)); |
| 517 | goto err; |
| 518 | } |
| 519 | } |
| 520 | |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 521 | if ((uid_t)setfsuid(pwd->pw_uid) != 0) |
| 522 | goto err; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 523 | |
| 524 | if (rsynccmd(src, tmpdir, &cmdbuf) < 0) { |
| 525 | goto err; |
| 526 | } |
| 527 | |
| 528 | /* ok to not reach this if there is an error */ |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 529 | if ((uid_t)setfsuid(0) != pwd->pw_uid) |
| 530 | goto err; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 531 | |
| 532 | if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) { |
| 533 | fprintf(stderr, _("Failed to populate runtime temporary directory\n")); |
| 534 | cleanup_tmpdir(tmpdir, src, pwd, 0); |
| 535 | goto err; |
| 536 | } |
| 537 | |
| 538 | goto good; |
| 539 | err: |
| 540 | free(tmpdir); tmpdir = NULL; |
| 541 | good: |
| 542 | free(cmdbuf); cmdbuf = NULL; |
| 543 | freecon(con); con = NULL; |
| 544 | if (fd_t >= 0) close(fd_t); |
| 545 | if (fd_s >= 0) close(fd_s); |
| 546 | return tmpdir; |
| 547 | } |
| 548 | |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 549 | #define PROC_BASE "/proc" |
| 550 | |
| 551 | static int |
| 552 | killall (security_context_t execcon) |
| 553 | { |
| 554 | DIR *dir; |
| 555 | security_context_t scon; |
| 556 | struct dirent *de; |
| 557 | pid_t *pid_table, pid, self; |
| 558 | int i; |
| 559 | int pids, max_pids; |
| 560 | int running = 0; |
| 561 | self = getpid(); |
| 562 | if (!(dir = opendir(PROC_BASE))) { |
| 563 | return -1; |
| 564 | } |
| 565 | max_pids = 256; |
| 566 | pid_table = malloc(max_pids * sizeof (pid_t)); |
| 567 | if (!pid_table) { |
| 568 | (void)closedir(dir); |
| 569 | return -1; |
| 570 | } |
| 571 | pids = 0; |
| 572 | context_t con; |
| 573 | con = context_new(execcon); |
| 574 | const char *mcs = context_range_get(con); |
| 575 | printf("mcs=%s\n", mcs); |
| 576 | while ((de = readdir (dir)) != NULL) { |
| 577 | if (!(pid = (pid_t)atoi(de->d_name)) || pid == self) |
| 578 | continue; |
| 579 | |
| 580 | if (pids == max_pids) { |
Eric Paris | 0a5dc30 | 2013-02-01 15:23:12 -0500 | [diff] [blame] | 581 | pid_t *new_pid_table = realloc(pid_table, 2*pids*sizeof(pid_t)); |
| 582 | if (!new_pid_table) { |
| 583 | free(pid_table); |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 584 | (void)closedir(dir); |
| 585 | return -1; |
| 586 | } |
Eric Paris | 0a5dc30 | 2013-02-01 15:23:12 -0500 | [diff] [blame] | 587 | pid_table = new_pid_table; |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 588 | max_pids *= 2; |
| 589 | } |
| 590 | pid_table[pids++] = pid; |
| 591 | } |
| 592 | |
| 593 | (void)closedir(dir); |
| 594 | |
| 595 | for (i = 0; i < pids; i++) { |
| 596 | pid_t id = pid_table[i]; |
| 597 | |
| 598 | if (getpidcon(id, &scon) == 0) { |
| 599 | |
| 600 | context_t pidcon = context_new(scon); |
| 601 | /* Attempt to kill remaining processes */ |
| 602 | if (strcmp(context_range_get(pidcon), mcs) == 0) |
| 603 | kill(id, SIGKILL); |
| 604 | |
| 605 | context_free(pidcon); |
| 606 | freecon(scon); |
| 607 | } |
| 608 | running++; |
| 609 | } |
| 610 | |
| 611 | context_free(con); |
| 612 | free(pid_table); |
| 613 | return running; |
| 614 | } |
| 615 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 616 | int main(int argc, char **argv) { |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 617 | int status = -1; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 618 | security_context_t execcon = NULL; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 619 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 620 | int clflag; /* holds codes for command line flags */ |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 621 | int kill_all = 0; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 622 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 623 | char *homedir_s = NULL; /* homedir spec'd by user in argv[] */ |
| 624 | char *tmpdir_s = NULL; /* tmpdir spec'd by user in argv[] */ |
| 625 | char *tmpdir_r = NULL; /* tmpdir created by seunshare */ |
| 626 | |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 627 | struct stat st_curhomedir; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 628 | struct stat st_homedir; |
| 629 | struct stat st_tmpdir_s; |
| 630 | struct stat st_tmpdir_r; |
| 631 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 632 | const struct option long_options[] = { |
| 633 | {"homedir", 1, 0, 'h'}, |
| 634 | {"tmpdir", 1, 0, 't'}, |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 635 | {"kill", 1, 0, 'k'}, |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 636 | {"verbose", 1, 0, 'v'}, |
Eric Paris | da7ae79 | 2011-08-15 16:00:04 -0400 | [diff] [blame] | 637 | {"context", 1, 0, 'Z'}, |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 638 | {"capabilities", 1, 0, 'C'}, |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 639 | {NULL, 0, 0, 0} |
| 640 | }; |
| 641 | |
| 642 | uid_t uid = getuid(); |
Dan Walsh | a0e2e16 | 2011-07-26 10:42:26 -0400 | [diff] [blame] | 643 | /* |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 644 | if (!uid) { |
| 645 | fprintf(stderr, _("Must not be root")); |
| 646 | return -1; |
| 647 | } |
Dan Walsh | a0e2e16 | 2011-07-26 10:42:26 -0400 | [diff] [blame] | 648 | */ |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 649 | |
Eric Paris | e134013 | 2011-08-15 20:10:14 -0400 | [diff] [blame] | 650 | #ifdef USE_NLS |
| 651 | setlocale(LC_ALL, ""); |
| 652 | bindtextdomain(PACKAGE, LOCALEDIR); |
| 653 | textdomain(PACKAGE); |
| 654 | #endif |
| 655 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 656 | struct passwd *pwd=getpwuid(uid); |
| 657 | if (!pwd) { |
| 658 | perror(_("getpwduid failed")); |
| 659 | return -1; |
| 660 | } |
| 661 | |
| 662 | if (verify_shell(pwd->pw_shell) < 0) { |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 663 | fprintf(stderr, _("Error: User shell is not valid\n")); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 664 | return -1; |
| 665 | } |
| 666 | |
| 667 | while (1) { |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 668 | clflag = getopt_long(argc, argv, "Ccvh:t:Z:", long_options, NULL); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 669 | if (clflag == -1) |
| 670 | break; |
| 671 | |
| 672 | switch (clflag) { |
| 673 | case 't': |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 674 | tmpdir_s = optarg; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 675 | break; |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 676 | case 'k': |
| 677 | kill_all = 1; |
| 678 | break; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 679 | case 'h': |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 680 | homedir_s = optarg; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 681 | break; |
| 682 | case 'v': |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 683 | verbose++; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 684 | break; |
Dan Walsh | 149afc6 | 2011-06-13 13:24:38 -0400 | [diff] [blame] | 685 | case 'C': |
| 686 | cap_set = CAPNG_SELECT_CAPS; |
| 687 | break; |
Eric Paris | da7ae79 | 2011-08-15 16:00:04 -0400 | [diff] [blame] | 688 | case 'Z': |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 689 | execcon = optarg; |
Eric Paris | da7ae79 | 2011-08-15 16:00:04 -0400 | [diff] [blame] | 690 | break; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 691 | default: |
| 692 | fprintf(stderr, "%s\n", USAGE_STRING); |
| 693 | return -1; |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | if (! homedir_s && ! tmpdir_s) { |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 698 | fprintf(stderr, _("Error: tmpdir and/or homedir required\n %s\n"), USAGE_STRING); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 699 | return -1; |
| 700 | } |
| 701 | |
Eric Paris | da7ae79 | 2011-08-15 16:00:04 -0400 | [diff] [blame] | 702 | if (argc - optind < 1) { |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 703 | fprintf(stderr, _("Error: executable required\n %s\n"), USAGE_STRING); |
| 704 | return -1; |
| 705 | } |
| 706 | |
| 707 | if (execcon && is_selinux_enabled() != 1) { |
| 708 | fprintf(stderr, _("Error: execution context specified, but SELinux is not enabled\n")); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 709 | return -1; |
| 710 | } |
| 711 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 712 | if (set_signal_handles()) |
| 713 | return -1; |
| 714 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 715 | /* set fsuid to ruid */ |
| 716 | /* Changing fsuid is usually required when user-specified directory is |
| 717 | * on an NFS mount. It's also desired to avoid leaking info about |
| 718 | * existence of the files not accessible to the user. */ |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 719 | if (((uid_t)setfsuid(uid) != 0) && (errno != 0)) { |
| 720 | fprintf(stderr, _("Error: unable to setfsuid %m\n")); |
| 721 | |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 722 | return -1; |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 723 | } |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 724 | |
| 725 | /* verify homedir and tmpdir */ |
| 726 | if (homedir_s && ( |
| 727 | verify_directory(homedir_s, NULL, &st_homedir) < 0 || |
| 728 | check_owner_uid(uid, homedir_s, &st_homedir))) return -1; |
| 729 | if (tmpdir_s && ( |
| 730 | verify_directory(tmpdir_s, NULL, &st_tmpdir_s) < 0 || |
| 731 | check_owner_uid(uid, tmpdir_s, &st_tmpdir_s))) return -1; |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 732 | if ((uid_t)setfsuid(0) != uid) return -1; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 733 | |
| 734 | /* create runtime tmpdir */ |
| 735 | if (tmpdir_s && (tmpdir_r = create_tmpdir(tmpdir_s, &st_tmpdir_s, |
| 736 | &st_tmpdir_r, pwd, execcon)) == NULL) { |
| 737 | fprintf(stderr, _("Failed to create runtime temporary directory\n")); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 738 | return -1; |
| 739 | } |
| 740 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 741 | /* spawn child process */ |
Dan Walsh | 216f456 | 2011-07-06 20:52:05 -0400 | [diff] [blame] | 742 | child = fork(); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 743 | if (child == -1) { |
| 744 | perror(_("Unable to fork")); |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 745 | goto err; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 746 | } |
| 747 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 748 | if (child == 0) { |
| 749 | char *display = NULL; |
Dan Walsh | 5c2a0d1 | 2011-09-07 14:20:30 -0400 | [diff] [blame] | 750 | char *LANG = NULL; |
Dan Walsh | 6ee0299 | 2014-05-12 13:19:19 -0400 | [diff] [blame] | 751 | char *RUNTIME_DIR = NULL; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 752 | int rc = -1; |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 753 | char *resolved_path = NULL; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 754 | |
| 755 | if (unshare(CLONE_NEWNS) < 0) { |
| 756 | perror(_("Failed to unshare")); |
| 757 | goto childerr; |
| 758 | } |
| 759 | |
Dan Walsh | 70c582f | 2012-01-03 13:45:08 -0500 | [diff] [blame] | 760 | /* Remount / as SLAVE so that nothing mounted in the namespace |
| 761 | shows up in the parent */ |
| 762 | if (mount("none", "/", NULL, MS_SLAVE | MS_REC , NULL) < 0) { |
| 763 | perror(_("Failed to make / a SLAVE mountpoint\n")); |
| 764 | goto childerr; |
| 765 | } |
| 766 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 767 | /* assume fsuid==ruid after this point */ |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 768 | if ((uid_t)setfsuid(uid) != 0) goto childerr; |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 769 | |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 770 | resolved_path = realpath(pwd->pw_dir,NULL); |
| 771 | if (! resolved_path) goto childerr; |
| 772 | |
| 773 | if (verify_directory(resolved_path, NULL, &st_curhomedir) < 0) |
| 774 | goto childerr; |
| 775 | if (check_owner_uid(uid, resolved_path, &st_curhomedir) < 0) |
| 776 | goto childerr; |
| 777 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 778 | /* mount homedir and tmpdir, in this order */ |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 779 | if (homedir_s && seunshare_mount(homedir_s, resolved_path, |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 780 | &st_homedir) != 0) goto childerr; |
| 781 | if (tmpdir_s && seunshare_mount(tmpdir_r, "/tmp", |
| 782 | &st_tmpdir_r) != 0) goto childerr; |
| 783 | |
| 784 | if (drop_privs(uid) != 0) goto childerr; |
| 785 | |
| 786 | /* construct a new environment */ |
| 787 | if ((display = getenv("DISPLAY")) != NULL) { |
| 788 | if ((display = strdup(display)) == NULL) { |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 789 | perror(_("Out of memory")); |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 790 | goto childerr; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 791 | } |
| 792 | } |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 793 | |
Dan Walsh | 5c2a0d1 | 2011-09-07 14:20:30 -0400 | [diff] [blame] | 794 | /* construct a new environment */ |
| 795 | if ((LANG = getenv("LANG")) != NULL) { |
| 796 | if ((LANG = strdup(LANG)) == NULL) { |
| 797 | perror(_("Out of memory")); |
| 798 | goto childerr; |
| 799 | } |
| 800 | } |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 801 | |
Dan Walsh | 6ee0299 | 2014-05-12 13:19:19 -0400 | [diff] [blame] | 802 | if ((RUNTIME_DIR = getenv("XDG_RUNTIME_DIR")) != NULL) { |
| 803 | if ((RUNTIME_DIR = strdup(RUNTIME_DIR)) == NULL) { |
| 804 | perror(_("Out of memory")); |
| 805 | goto childerr; |
| 806 | } |
| 807 | } |
| 808 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 809 | if ((rc = clearenv()) != 0) { |
| 810 | perror(_("Failed to clear environment")); |
| 811 | goto childerr; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 812 | } |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 813 | if (display) |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 814 | rc |= setenv("DISPLAY", display, 1); |
Eric Paris | 221e6d4 | 2012-09-26 11:00:56 -0400 | [diff] [blame] | 815 | if (LANG) |
Dan Walsh | 5c2a0d1 | 2011-09-07 14:20:30 -0400 | [diff] [blame] | 816 | rc |= setenv("LANG", LANG, 1); |
Dan Walsh | 6ee0299 | 2014-05-12 13:19:19 -0400 | [diff] [blame] | 817 | if (RUNTIME_DIR) |
| 818 | rc |= setenv("XDG_RUNTIME_DIR", RUNTIME_DIR, 1); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 819 | rc |= setenv("HOME", pwd->pw_dir, 1); |
| 820 | rc |= setenv("SHELL", pwd->pw_shell, 1); |
| 821 | rc |= setenv("USER", pwd->pw_name, 1); |
| 822 | rc |= setenv("LOGNAME", pwd->pw_name, 1); |
| 823 | rc |= setenv("PATH", DEFAULT_PATH, 1); |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 824 | if (rc != 0) { |
| 825 | fprintf(stderr, _("Failed to construct environment\n")); |
| 826 | goto childerr; |
| 827 | } |
| 828 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 829 | if (chdir(pwd->pw_dir)) { |
| 830 | perror(_("Failed to change dir to homedir")); |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 831 | goto childerr; |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 832 | } |
| 833 | setsid(); |
Andy Lutomirski | 74d27a9 | 2014-05-12 13:19:21 -0400 | [diff] [blame] | 834 | |
| 835 | /* selinux context */ |
| 836 | if (execcon) { |
| 837 | /* try dyntransition, since no_new_privs can interfere |
| 838 | * with setexeccon */ |
| 839 | if (setcon(execcon) != 0) { |
| 840 | /* failed; fall back to setexeccon */ |
| 841 | if (setexeccon(execcon) != 0) { |
| 842 | fprintf(stderr, _("Could not set exec context to %s. %s\n"), execcon, strerror(errno)); |
| 843 | goto childerr; |
| 844 | } |
| 845 | } |
| 846 | } |
| 847 | |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 848 | execv(argv[optind], argv + optind); |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 849 | fprintf(stderr, _("Failed to execute command %s: %s\n"), argv[optind], strerror(errno)); |
| 850 | childerr: |
Dan Walsh | e4488ec | 2013-10-09 17:28:37 -0400 | [diff] [blame] | 851 | free(resolved_path); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 852 | free(display); |
Dan Walsh | 5c2a0d1 | 2011-09-07 14:20:30 -0400 | [diff] [blame] | 853 | free(LANG); |
Dan Walsh | 6ee0299 | 2014-05-12 13:19:19 -0400 | [diff] [blame] | 854 | free(RUNTIME_DIR); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 855 | exit(-1); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 856 | } |
| 857 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 858 | drop_caps(); |
Steve Lawrence | 582fd00 | 2010-06-10 16:37:59 -0400 | [diff] [blame] | 859 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 860 | /* parent waits for child exit to do the cleanup */ |
| 861 | waitpid(child, &status, 0); |
| 862 | status_to_retval(status, status); |
| 863 | |
Dan Walsh | 216f456 | 2011-07-06 20:52:05 -0400 | [diff] [blame] | 864 | /* Make sure all child processes exit */ |
| 865 | kill(-child,SIGTERM); |
| 866 | |
Dan Walsh | e8575bf | 2011-07-06 20:22:26 -0400 | [diff] [blame] | 867 | if (execcon && kill_all) |
| 868 | killall(execcon); |
| 869 | |
Eric Paris | 31edb31 | 2011-08-15 19:58:08 -0400 | [diff] [blame] | 870 | if (tmpdir_r) cleanup_tmpdir(tmpdir_r, tmpdir_s, pwd, 1); |
| 871 | |
| 872 | err: |
| 873 | free(tmpdir_r); |
Daniel J Walsh | d6848ea | 2010-06-10 16:35:55 -0400 | [diff] [blame] | 874 | return status; |
| 875 | } |