sfakeroot.c (19801B)
1 /* sfakeroot: manipulate files faking root privileges 2 * 3 * Copyright © 2020 Richard Ipsum 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, version 3 of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <stdbool.h> 22 #include <limits.h> 23 #include <dirent.h> 24 #include <unistd.h> 25 #include <string.h> 26 #include <errno.h> 27 #include <arpa/inet.h> 28 #include <sys/types.h> 29 #include <sys/socket.h> 30 #include <sys/wait.h> 31 #include <sys/un.h> 32 #include <signal.h> 33 34 #include "sfakeroot.h" 35 36 #include <sys/syscall.h> 37 38 #define FALLBACK_SHELL "/bin/sh" 39 #define SONAME "libsfakeroot.so" 40 41 #ifdef __linux__ 42 #ifndef __x86_64__ 43 #error "Architectures other than amd64 are not supported on Linux" 44 #endif 45 /*./x86_64-linux-gnu/asm/unistd_64.h:#define __NR_newfstatat 262 */ 46 #define SYS_fstatat 262 47 #define SESSION_FILE_LINE_FMT "%lu,%u,%u,%u\n" 48 #else 49 #define SESSION_FILE_LINE_FMT "%llu,%u,%u,%u\n" 50 #endif 51 52 static char *argv0; 53 54 struct sfakeroot_list { 55 struct sfakeroot_ent *first; 56 struct sfakeroot_ent *last; 57 }; 58 59 struct sfakeroot_list ents; 60 61 struct cmdline_options { 62 bool require_session_file; 63 char *session_file; 64 }; 65 66 struct cmdline_arguments { 67 struct cmdline_options options; 68 char **args; 69 size_t args_len; 70 }; 71 72 static void xsend(int sockfd, struct sfakeroot_msg *m) 73 { 74 if (sfakeroot_sendmsg(sockfd, m) == -1) { 75 exit(1); 76 } 77 } 78 79 static struct sfakeroot_ent *lookupent(struct sfakeroot_list *list, ino_t st_ino) 80 { 81 for (struct sfakeroot_ent *e = list->first; e != NULL; e = e->next) { 82 debug("checking st_ino %llu\n", e->st.st_ino); 83 if (e->st.st_ino == st_ino) { 84 return e; 85 } 86 } 87 88 return NULL; 89 } 90 91 static struct sfakeroot_ent *addent(struct sfakeroot_list *list, 92 const struct stat *s, bool stale) 93 { 94 struct sfakeroot_ent *ent; 95 96 ent = malloc(sizeof (*ent)); 97 if (ent == NULL) { 98 return NULL; 99 } 100 101 debug("add ent: %llu\n", s->st_ino); 102 103 ent->stale = stale; 104 ent->next = NULL; 105 ent->st = *s; 106 107 if (list->first == NULL) { 108 list->first = ent; 109 goto out; 110 } 111 112 if (list->last != NULL) { 113 list->last->next = ent; 114 } 115 116 out: 117 list->last = ent; 118 return ent; 119 } 120 121 static int real_stat_internal(int fd, const char *path, struct stat *sb, 122 int flag, const char *working_dir, int *errno_out, 123 int sysno) 124 { 125 int cwdfd, ret; 126 DIR *cwd = opendir("."); 127 128 if (cwd == NULL) { 129 *errno_out = errno; 130 return -1; 131 } 132 133 debug("chdir %s\n", working_dir); 134 cwdfd = dirfd(cwd); 135 if (chdir(working_dir) == -1) { 136 *errno_out = errno; 137 closedir(cwd); 138 return -1; 139 } 140 141 debug("do syscall\n"); 142 switch (sysno) { 143 case SYS_fstatat: 144 ret = syscall(sysno, fd, path, sb, flag); 145 break; 146 case SYS_stat: 147 case SYS_lstat: 148 ret = syscall(sysno, path, sb); 149 break; 150 default: 151 ret = -1; 152 errno = EINVAL; 153 break; 154 } 155 156 *errno_out = errno; 157 fchdir(cwdfd); 158 closedir(cwd); 159 return ret; 160 } 161 162 static int real_stat(const char *path, struct stat *sb, bool islstat, 163 char *working_dir, int *errno_out) 164 { 165 int sysno = islstat ? SYS_lstat : SYS_stat; 166 return real_stat_internal(-1, path, sb, 0, working_dir, errno_out, sysno); 167 } 168 169 static int real_fstatat(int fd, const char *path, struct stat *sb, int flag, 170 const char *working_dir, int *errno_out) 171 { 172 return real_stat_internal(fd, path, sb, flag, working_dir, 173 errno_out, SYS_fstatat); 174 } 175 176 static int real_fstat(int fd, struct stat *sb, int *errno_out) 177 { 178 int ret = syscall(SYS_fstat, fd, sb); 179 *errno_out = errno; 180 return ret; 181 } 182 183 static void handle_stat(struct sfakeroot_msg *m) 184 { 185 struct sfakeroot_ent *ent; 186 int errno_sv = 0; 187 188 debug("handle_fstat\n"); 189 190 switch (m->type) { 191 case SFAKEROOT_MSG_FSTAT: 192 m->retcode = real_fstat(m->fd, &m->st, &errno_sv); 193 break; 194 case SFAKEROOT_MSG_STAT: 195 case SFAKEROOT_MSG_LSTAT: 196 m->retcode = real_stat(m->path, &m->st, 197 m->type == SFAKEROOT_MSG_LSTAT, 198 m->working_dir, &errno_sv); 199 break; 200 case SFAKEROOT_MSG_FSTATAT: 201 m->retcode = real_fstatat(m->fd, m->path, &m->st, m->flag, 202 m->working_dir, &errno_sv); 203 break; 204 default: 205 debug("non stat message in handle_stat\n"); 206 return; 207 } 208 209 if (m->retcode != 0) { 210 m->reterrno = errno_sv; 211 return; 212 } 213 214 if ((ent = lookupent(&ents, m->st.st_ino)) != NULL) { 215 if (ent->stale) { 216 /* update the entry with the new stat data */ 217 m->st.st_uid = ent->st.st_uid; 218 m->st.st_gid = ent->st.st_gid; 219 m->st.st_mode = ent->st.st_mode; 220 ent->st = m->st; 221 ent->stale = false; 222 } 223 else { 224 m->st = ent->st; 225 } 226 if ((int) m->st.st_uid == -1) { 227 m->st.st_uid = 0; 228 } 229 if ((int) m->st.st_gid == -1) { 230 m->st.st_gid = 0; 231 } 232 return; 233 } 234 235 m->st.st_uid = 0; 236 m->st.st_gid = 0; 237 } 238 239 static void handle_perms_change(struct sfakeroot_msg *m) 240 { 241 struct stat s; 242 int errno_sv; 243 struct sfakeroot_ent *ent; 244 245 debug("handle_perms_change: m->path: %s, m->working_dir: %s\n", 246 m->path, m->working_dir); 247 248 switch (m->type) { 249 case SFAKEROOT_MSG_LCHOWN: 250 m->retcode = real_stat(m->path, &s, true, m->working_dir, &errno_sv); 251 break; 252 case SFAKEROOT_MSG_CHOWN: 253 case SFAKEROOT_MSG_CHMOD: 254 m->retcode = real_stat(m->path, &s, false, m->working_dir, &errno_sv); 255 break; 256 case SFAKEROOT_MSG_FCHOWNAT: 257 m->retcode = real_fstatat(m->fd, m->path, &s, m->flag, 258 m->working_dir, &errno_sv); 259 break; 260 case SFAKEROOT_MSG_FCHOWN: 261 m->retcode = real_fstat(m->fd, &s, &errno_sv); 262 break; 263 default: 264 debug("non chown/chmod message in handle_perms_change\n"); 265 return; 266 } 267 268 if (m->retcode == -1) { 269 debug("stat returned error\n"); 270 m->reterrno = errno_sv; 271 return; 272 } 273 274 /* path exists */ 275 switch (m->type) { 276 case SFAKEROOT_MSG_LCHOWN: 277 case SFAKEROOT_MSG_CHOWN: 278 case SFAKEROOT_MSG_FCHOWNAT: 279 s.st_uid = m->uid; 280 s.st_gid = m->gid; 281 break; 282 case SFAKEROOT_MSG_CHMOD: 283 s.st_mode = (s.st_mode & S_IFMT) | m->mode; 284 break; 285 default: 286 debug("non chown/chmod message in handle_perms_change\n"); 287 return; 288 } 289 290 if ((ent = lookupent(&ents, s.st_ino)) != NULL) { 291 uid_t u = ent->st.st_uid; 292 gid_t g = ent->st.st_gid; 293 ent->st = s; 294 ent->st.st_uid = ((int) m->uid == -1) ? u : m->uid; 295 ent->st.st_gid = ((int) m->gid == -1) ? g : m->gid; 296 m->retcode = 0; 297 return; 298 } 299 300 s.st_uid = m->uid; 301 s.st_gid = m->gid; 302 303 if (addent(&ents, &s, false) == NULL) { 304 m->reterrno = ENOMEM; 305 m->retcode = -1; 306 return; 307 } 308 309 m->retcode = 0; 310 } 311 312 /* listen for new connections */ 313 static int sfakeroot_create_listener(const char *session_socket_path) 314 { 315 static struct sockaddr_un sa = {.sun_family = AF_UNIX}; 316 socklen_t sock_namelen; 317 int sock; 318 319 sock = socket(AF_UNIX, SOCK_STREAM, 0); 320 if (sock == -1) { 321 fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno)); 322 exit(1); 323 } 324 325 strlcpy(sa.sun_path, session_socket_path, sizeof (sa.sun_path)); 326 if (unlink(session_socket_path) == -1 && errno != ENOENT) { 327 fprintf(stderr, "%s: unlink \"%s\" failed: %s\n", 328 argv0, sa.sun_path, strerror(errno)); 329 exit(1); 330 } 331 332 sock_namelen = strlen(sa.sun_path) + 1 + sizeof (sa.sun_family); 333 if (bind(sock, (struct sockaddr *) &sa, sock_namelen) == -1) { 334 fprintf(stderr, "%s: bind: %s\n", argv0, strerror(errno)); 335 exit(1); 336 } 337 338 if (listen(sock, 21) == -1) { 339 fprintf(stderr, "%s: listen: %s\n", argv0, strerror(errno)); 340 exit(1); 341 } 342 343 return sock; 344 } 345 346 static int write_session_to_file(const char *save_path) 347 { 348 FILE *f = fopen(save_path, "w"); 349 350 debug("write session to `%s'\n", save_path); 351 352 if (f == NULL) { 353 goto error; 354 } 355 356 for (struct sfakeroot_ent *p = ents.first; p != NULL; p = p->next) { 357 struct stat *s = &p->st; 358 if (fprintf(f, SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode) < 0) { 359 goto error; 360 } 361 debug(SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode); 362 } 363 364 if (fclose(f) != 0) { 365 goto error; 366 } 367 368 return 0; 369 370 error: 371 fprintf(stderr, "%s: error saving session to `%s': %s\n", 372 argv0, save_path, strerror(errno)); 373 return -1; 374 } 375 376 static int sfakeroot_finish_session(const struct cmdline_arguments *args) 377 { 378 if (args->options.session_file != NULL) { 379 if (write_session_to_file(args->options.session_file) == -1) { 380 return -1; 381 } 382 } 383 384 return 0; 385 } 386 387 int estrtol(const char *s, long *out) 388 { 389 char *e; 390 391 long n = strtol(s, &e, 10); 392 393 if (*s == '\0' || *e != '\0') { 394 fprintf(stderr, "%s: estrtol: `%s' not a number\n", argv0, s); 395 return -1; 396 } 397 398 if ((errno == ERANGE && (n == LONG_MIN || n == LONG_MAX))) { 399 fprintf(stderr, "%s: estrtol: `%s' out of range\n", argv0, s); 400 return -1; 401 } 402 403 *out = n; 404 return 0; 405 } 406 407 static int sfakeroot_load_session_from_file(const struct cmdline_arguments *args) 408 { 409 char *line = NULL, *session_filepath; 410 size_t n = 0; 411 ssize_t count; 412 FILE *f; 413 414 session_filepath = args->options.session_file; 415 f = fopen(session_filepath, "r"); 416 if (f == NULL) { 417 char *errfmt = "%s: couldn't open session file `%s': %s\n"; 418 debug(errfmt, argv0, session_filepath, strerror(errno)); 419 if (args->options.require_session_file) { 420 fprintf(stderr, errfmt, argv0, session_filepath, strerror(errno)); 421 return -1; 422 } 423 return 0; 424 } 425 426 debug("loading session from `%s'\n", session_filepath); 427 428 while ((count = getline(&line, &n, f)) != -1) { 429 struct stat s = {0}; 430 long values[4]; 431 int i = 0; 432 line[count - 1] = '\0'; /* strip '\n' */ 433 for (char *s = strtok(line, ","); s != NULL && i < 4; s = strtok(NULL, ","), i++) { 434 if (estrtol(s, values + i) == -1) { 435 free(line); 436 return -1; 437 } 438 } 439 s.st_ino = (ino_t) values[0]; 440 s.st_uid = (uid_t) values[1]; 441 s.st_gid = (gid_t) values[2]; 442 s.st_mode = (mode_t) values[3]; 443 addent(&ents, &s, true); 444 } 445 446 if (ferror(f)) { 447 fprintf(stderr, "%s: error reading from `%s': %s\n", 448 argv0, session_filepath, strerror(errno)); 449 free(line); 450 return -1; 451 } 452 453 free(line); 454 return 0; 455 } 456 457 static void sfakeroot_server(int pipewfd, const char *session_socket_path, 458 const struct cmdline_arguments *args) 459 { 460 static struct sockaddr_un sa; 461 int listen_sock, sock; 462 socklen_t namelen; 463 464 if (args->options.session_file != NULL) { 465 if (sfakeroot_load_session_from_file(args) == -1) { 466 exit(1); 467 } 468 } 469 470 listen_sock = sfakeroot_create_listener(session_socket_path); 471 472 /* close write end of pipe to indicate to parent process that we 473 * are now ready to accept incoming connections 474 */ 475 close(pipewfd); 476 477 for (;;) { 478 struct sfakeroot_msg m; 479 480 sock = accept(listen_sock, (struct sockaddr *) &sa, &namelen); 481 if (sock == -1) { 482 fprintf(stderr, "%s: accept: %s\n", argv0, strerror(errno)); 483 exit(1); 484 } 485 if (sfakeroot_recvmsg(sock, &m) == -1) { 486 fprintf(stderr, "eof?\n"); 487 continue; 488 } 489 490 switch (m.type) { 491 case SFAKEROOT_MSG_FSTAT: 492 case SFAKEROOT_MSG_LSTAT: 493 case SFAKEROOT_MSG_STAT: 494 case SFAKEROOT_MSG_FSTATAT: 495 handle_stat(&m); 496 break; 497 case SFAKEROOT_MSG_CHOWN: 498 case SFAKEROOT_MSG_LCHOWN: 499 case SFAKEROOT_MSG_CHMOD: 500 // TODO: fchmod 501 // TODO: fchmodat 502 case SFAKEROOT_MSG_FCHOWN: 503 case SFAKEROOT_MSG_FCHOWNAT: 504 handle_perms_change(&m); 505 break; 506 case SFAKEROOT_MSG_FINISH: 507 if (sfakeroot_finish_session(args) == -1) { 508 exit(1); 509 } 510 exit(0); 511 } 512 xsend(sock, &m); 513 close(sock); 514 } 515 } 516 517 static int sfakeroot_daemon(const char *session_socket_path, 518 const struct cmdline_arguments *args) 519 { 520 int pipefds[2]; 521 char buf[1]; 522 pid_t pid; 523 int status; 524 525 if (pipe(pipefds) == -1) { 526 fprintf(stderr, "%s: pipe: %s\n", argv0, strerror(errno)); 527 return -1; 528 } 529 530 switch ((pid = fork())) { 531 case 0: 532 /* child */ 533 close(pipefds[0]); /* close read end */ 534 if (daemon(1, 1) == -1) { 535 fprintf(stderr, "%s: failed to daemonise: %s\n", 536 argv0, strerror(errno)); 537 exit(1); 538 } 539 sfakeroot_server(pipefds[1], session_socket_path, args); 540 break; 541 case -1: 542 fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno)); 543 return -1; 544 default: 545 /* parent */ 546 close(pipefds[1]); /* close write end */ 547 /* This read will block until the child process signals it 548 * is ready, i.e. it's ready to accept connections from us. 549 */ 550 if (read(pipefds[0], buf, sizeof (buf)) == -1) { 551 fprintf(stderr, "%s: error reading from pipe: %s\n", 552 argv0, strerror(errno)); 553 return -1; 554 } 555 if (waitpid(pid, &status, 0) == -1) { 556 fprintf(stderr, "%s: waitpid: %s\n", argv0, strerror(errno)); 557 return -1; 558 } 559 if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { 560 fprintf(stderr, "%s: sfakeroot daemon unexpected condition\n", 561 argv0); 562 return -1; 563 } 564 } 565 566 return 0; 567 } 568 569 int sfakeroot_session_open(void); 570 571 static int setenvvars(const char *session_socket_path) 572 { 573 char *wd = SFAKEROOT_LIBDIR, *path = NULL; 574 size_t len; 575 576 len = snprintf(NULL, 0, "%s/%s", wd, SONAME); 577 path = malloc(len + 1); 578 if (path == NULL) { 579 goto error; 580 } 581 snprintf(path, len + 1, "%s/%s", wd, SONAME); 582 583 if (setenv("LD_PRELOAD", path, 1) == -1) { 584 goto error; 585 } 586 free(path); 587 588 if (setenv("SFAKEROOT_SOCKET_PATH", session_socket_path, 1) == -1) { 589 goto error; 590 } 591 592 return 0; 593 594 error: 595 fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno)); 596 return -1; 597 } 598 599 static void usage(void) 600 { 601 fprintf(stderr, "usage: %s [-Ff file]\n", argv0); 602 } 603 604 static void parse_options(int argc, char *argv[], struct cmdline_arguments *args) 605 { 606 int ch; 607 extern int optind; 608 extern char *optarg; 609 610 while ((ch = getopt(argc, argv, "F:f:")) != -1) { 611 switch (ch) { 612 case 'F': 613 args->options.require_session_file = true; 614 /* fallthrough */ 615 case 'f': 616 args->options.session_file = optarg; 617 break; 618 default: 619 usage(); 620 exit(1); 621 } 622 } 623 624 args->args = argv + optind; 625 args->args_len = argc - optind; 626 } 627 628 int main(int argc, char *argv[]) 629 { 630 int sockfd, status, exit_status, len; 631 pid_t pid; 632 struct sfakeroot_msg m; 633 char *sargv[] = {FALLBACK_SHELL, NULL}, *shell, **exec_argvp; 634 char tempdir_path[8192] = "/tmp/sfakeroot.XXXXXXXXXX"; 635 char session_socket_path[8192]; 636 static struct cmdline_arguments args; 637 638 argv0 = argv[0]; 639 parse_options(argc, argv, &args); 640 641 if ((shell = getenv("SHELL")) != NULL) { 642 sargv[0] = shell; 643 } 644 645 if (mkdtemp(tempdir_path) == NULL) { 646 fprintf(stderr, "%s: mkdtemp: %s\n", argv0, strerror(errno)); 647 exit_status = 1; 648 goto cleanup; 649 } 650 651 len = snprintf(session_socket_path, sizeof (session_socket_path), "%s/%s", 652 tempdir_path, "uds.sock"); 653 if (len == -1) { 654 fprintf(stderr, "%s: snprintf\n", argv0); 655 exit_status = 1; 656 goto cleanup; 657 } 658 else if ((size_t) len > sizeof (session_socket_path)) { 659 fprintf(stderr, "%s: path too long\n", argv0); 660 exit_status = 1; 661 goto cleanup; 662 } 663 664 if (setenvvars(session_socket_path) == -1) { 665 exit_status = 1; 666 goto cleanup; 667 } 668 669 if (!sfakeroot_daemon_running()) { 670 if (sfakeroot_daemon(session_socket_path, &args) == -1) { 671 exit_status = 1; 672 goto cleanup; 673 } 674 } 675 676 exec_argvp = args.args_len > 0 ? args.args : sargv; 677 switch (pid = fork()) { 678 case 0: 679 execvp(exec_argvp[0], exec_argvp); 680 fprintf(stderr, "%s: exec `%s': %s\n", 681 argv0, exec_argvp[0], strerror(errno)); 682 switch (errno) { 683 case EACCES: 684 exit_status = 126; 685 goto cleanup; 686 case ENOENT: 687 exit_status = 127; 688 goto cleanup; 689 default: 690 exit_status = 1; 691 goto cleanup; 692 } 693 case -1: 694 fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno)); 695 exit_status = 1; 696 goto cleanup; 697 default: 698 waitpid(pid, &status, 0); 699 debug("instructing daemon to finish...\n"); 700 m.type = SFAKEROOT_MSG_FINISH; 701 sockfd = sfakeroot_session_open(); 702 if (sockfd == -1) { 703 fprintf(stderr, "%s: failed to open session\n", argv0); 704 exit_status = 1; 705 goto cleanup; 706 } 707 if (sfakeroot_sendmsg(sockfd, &m) == -1) { 708 exit_status = 1; 709 goto cleanup; 710 } 711 close(sockfd); 712 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { 713 exit_status = WEXITSTATUS(status); 714 goto cleanup; 715 } 716 if (WIFSIGNALED(status)) { 717 exit_status = 127 + WTERMSIG(status); 718 goto cleanup; 719 } 720 else if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { 721 exit_status = 1; 722 goto cleanup; 723 } 724 break; 725 } 726 727 exit_status = 0; 728 729 cleanup: 730 unlink(session_socket_path); 731 rmdir(tempdir_path); 732 return exit_status; 733 }