Index: usr.bin/brandelf/brandelf.c =================================================================== --- usr.bin/brandelf/brandelf.c +++ usr.bin/brandelf/brandelf.c @@ -30,9 +30,15 @@ #include __FBSDID("$FreeBSD$"); +#include #include +#include #include #include +#include +#include +#include +#include #include #include #include @@ -40,10 +46,14 @@ #include #include +static int retval = 0, change = 0, force = 0, workers = 0; + +static int brand_fd(int, const char *, int, cap_rights_t *); static int elftype(const char *); static const char *iselftype(int); static void printelftypes(void); static void usage(void); +static void wait_for_forks(int, int); struct ELFtypes { const char *str; @@ -63,8 +73,9 @@ const char *strtype = "FreeBSD"; int type = ELFOSABI_FREEBSD; - int retval = 0; - int ch, change = 0, force = 0, listed = 0; + int ch, listed = 0, kq, ev; + struct kevent cl; + cap_rights_t rights_ro, rights_rw; while ((ch = getopt(argc, argv, "f:lt:v")) != -1) switch (ch) { @@ -112,44 +123,55 @@ usage(); } + if ((kq = kqueue()) == -1) + err(1, "failed to setup kqeueu()"); + + cap_rights_init(&rights_rw, CAP_READ, CAP_WRITE, CAP_SEEK); + cap_rights_init(&rights_ro, CAP_READ, CAP_SEEK); + while (argc) { - int fd; - char buffer[EI_NIDENT]; + int fd, pd; + pid_t pid; + + if (argc > 1) { + /* Only run 3 children concurrently */ + wait_for_forks(kq, 3); + } if ((fd = open(argv[0], change || force ? O_RDWR : O_RDONLY, 0)) < 0) { warn("error opening file %s", argv[0]); retval = 1; + close(fd); goto fail; } - if (read(fd, buffer, EI_NIDENT) < EI_NIDENT) { - warnx("file '%s' too short", argv[0]); - retval = 1; - goto fail; - } - if (buffer[0] != ELFMAG0 || buffer[1] != ELFMAG1 || - buffer[2] != ELFMAG2 || buffer[3] != ELFMAG3) { - warnx("file '%s' is not ELF format", argv[0]); - retval = 1; - goto fail; - } - if (!change && !force) { - fprintf(stdout, - "File '%s' is of brand '%s' (%u).\n", - argv[0], iselftype(buffer[EI_OSABI]), - buffer[EI_OSABI]); - if (!iselftype(type)) { - warnx("ELF ABI Brand '%u' is unknown", - type); - printelftypes(); - } - } - else { - buffer[EI_OSABI] = type; - lseek(fd, 0, SEEK_SET); - if (write(fd, buffer, EI_NIDENT) != EI_NIDENT) { - warn("error writing %s %d", argv[0], fd); - retval = 1; - goto fail; + + if (argc == 1) { + /* + * If there is only one file (left) to do, we can + * sandbox the parent process and do it directly. + */ + retval |= brand_fd(fd, argv[0], type, change || force ? + &rights_rw : &rights_ro); + } else { + /* + * There are many files to brand. Fork a process + * for each FD we have opened, and queue a kevent + * to notify us when it is finished. + */ + pid = pdfork(&pd, 0); + if (pid == 0) { + return (brand_fd(fd, argv[0], type, change || + force ? &rights_rw : &rights_ro)); + } else if (pid > 0) { + /* initalise kevent structure */ + EV_SET(&cl, pd, EVFILT_PROCDESC, EV_ADD | + EV_ENABLE, NOTE_EXIT, 0, 0); + if ((ev = kevent(kq, &cl, 1, NULL, 0, NULL)) + == -1) + err(1, "failed to queue kevent()"); + workers++; + } else { + err(1, "failed to fork: %s", argv[0]); } } fail: @@ -157,8 +179,13 @@ argc--; argv++; } + + /* Wait for all forked processes to finish */ + wait_for_forks(kq, 0); - return retval; + close(kq); + + return (retval); } static void @@ -174,9 +201,7 @@ { size_t elfwalk; - for (elfwalk = 0; - elfwalk < sizeof(elftypes)/sizeof(elftypes[0]); - elfwalk++) + for (elfwalk = 0; elfwalk < nitems(elftypes); elfwalk++) if (etype == elftypes[elfwalk].value) return elftypes[elfwalk].str; return 0; @@ -187,9 +212,7 @@ { size_t elfwalk; - for (elfwalk = 0; - elfwalk < sizeof(elftypes)/sizeof(elftypes[0]); - elfwalk++) + for (elfwalk = 0; elfwalk < nitems(elftypes); elfwalk++) if (strcasecmp(elfstrtype, elftypes[elfwalk].str) == 0) return elftypes[elfwalk].value; return -1; @@ -201,10 +224,99 @@ size_t elfwalk; fprintf(stderr, "known ELF types are: "); - for (elfwalk = 0; - elfwalk < sizeof(elftypes)/sizeof(elftypes[0]); - elfwalk++) + for (elfwalk = 0; elfwalk < nitems(elftypes); elfwalk++) fprintf(stderr, "%s(%u) ", elftypes[elfwalk].str, elftypes[elfwalk].value); fprintf(stderr, "\n"); } + +static int +brand_fd(int fd, const char *name, int type, cap_rights_t *fd_rights) +{ + int rv; + char buffer[EI_NIDENT]; + + rv = 0; + if (cap_enter() < 0 && errno != ENOSYS) { + fprintf(stderr, "%s: failed to enter sandbox: %s: %s\n", + getprogname(), name, strerror(errno)); + rv = 1; + goto end; + } + if (cap_rights_limit(fd, fd_rights) < 0 && errno != ENOSYS) { + fprintf(stderr, "%s: could not restrict capabilities: %s: %s\n", + getprogname(), name, strerror(errno)); + rv = 1; + goto end; + } + + if (read(fd, buffer, EI_NIDENT) < EI_NIDENT) { + fprintf(stderr, "%s: file '%s' too short\n", getprogname(), + name); + rv = 1; + goto end; + } + if (buffer[0] != ELFMAG0 || buffer[1] != ELFMAG1 || + buffer[2] != ELFMAG2 || buffer[3] != ELFMAG3) { + fprintf(stderr, "%s: file '%s' is not ELF format\n", + getprogname(), name); + rv = 1; + goto end; + } + if (!change && !force) { + fprintf(stdout, "File '%s' is of brand '%s' (%u).\n", + name, iselftype(buffer[EI_OSABI]), + buffer[EI_OSABI]); + if (!iselftype(type)) { + fprintf(stderr, "%s: ELF ABI Brand '%u' is unknown\n", + getprogname(), type); + printelftypes(); + } + } + else { + buffer[EI_OSABI] = type; + lseek(fd, 0, SEEK_SET); + if (write(fd, buffer, EI_NIDENT) != EI_NIDENT) { + fprintf(stderr, "%s: error writing: %s %d: %s\n", + getprogname(), name, fd, strerror(errno)); + rv = 1; + goto end; + } + } +end: + close(fd); + fflush(stderr); + return (rv); +} + +static void +wait_for_forks(int kq, int max_workers) +{ + int ev; + struct kevent event; + + while (workers > MAX(max_workers - 1, 0)) { + ev = kevent(kq, NULL, 0, &event, 1, NULL); + if (ev < 0) { + err(1, "failed kevent()"); + } else if (ev > 0) { + if (event.flags & EV_ERROR) { + err(EXIT_FAILURE, "EV_ERROR: %s\n", + strerror(event.data)); + } + if (event.filter == EVFILT_PROCDESC && + event.fflags == NOTE_EXIT) { + if (WTERMSIG(event.data) == 0) { + /* Capture exit code of child */ + retval |= WEXITSTATUS(event.data); + } else { + /* something went wrong */ + retval |= 1; + } + workers--; + /* close the process descriptor */ + close(event.ident); + } + } + } +} \ No newline at end of file