Index: usr.bin/uudecode/uudecode.c =================================================================== --- usr.bin/uudecode/uudecode.c +++ usr.bin/uudecode/uudecode.c @@ -47,6 +47,7 @@ * create the specified file, decoding as you go. * used with uuencode. */ +#include #include #include #include @@ -65,21 +66,71 @@ #include #include -static const char *infile, *outfile; -static FILE *infp, *outfp; -static int base64, cflag, iflag, oflag, pflag, rflag, sflag; +static const char *outfile; +static FILE *outfp; +static int base64, cflag, iflag, oflag, pflag, rflag, sflag, dirfd; static void usage(void); -static int decode(void); -static int decode2(void); -static int uu_decode(void); -static int base64_decode(void); +static int decode(FILE *infp, char *infile); +static int decode2(FILE *infp, char *infile); +static int uu_decode(FILE *infp, char *infile); +static int base64_decode(FILE *infp, char *infile); + +static int +safeoutfp(void) +{ + int flags, fd; + struct stat st; + + flags = O_WRONLY | O_CREAT | O_EXCL; + if (lstat(outfile, &st) == 0) { + if (iflag) { + warnc(EEXIST, "%s", outfile); + return (0); + } + switch (st.st_mode & S_IFMT) { + case S_IFREG: + case S_IFLNK: + /* avoid symlink attacks */ + if (unlink(outfile) == 0 || errno == ENOENT) + break; + warn("unlink %s", outfile); + return (1); + case S_IFDIR: + warnc(EISDIR, "%s", outfile); + return (1); + default: + if (oflag) { + /* trust command-line names */ + flags &= ~O_EXCL; + break; + } + warnc(EEXIST, "%s", outfile); + return (1); + } + } else if (errno != ENOENT) { + warn("stat %s", outfile); + return (1); + } + if ((fd = open(outfile, flags, 0664)) < 0 || + (outfp = fdopen(fd, "w")) == NULL) { + warn("open %s", outfile); + return (1); + } + + return (0); +} int main(int argc, char *argv[]) { - int rval, ch; - + int rval, ch, fnum, i; + FILE **infps; + char **infiles; + + fnum = 0; + rval = 0; + outfp = NULL; if (strcmp(basename(argv[0]), "b64decode") == 0) base64 = 1; @@ -102,6 +153,10 @@ oflag = 1; /* output to the specified file */ sflag = 1; /* do not strip pathnames for output */ outfile = optarg; /* set the output filename */ + /* Precreate the output fd before sandboxing */ + rval = safeoutfp(); + if (rval != 0) + exit(rval); break; case 'p': if (oflag) @@ -126,27 +181,52 @@ argv += optind; if (*argv != NULL) { - rval = 0; + infps = malloc(sizeof(FILE*) * argc); + infiles = malloc(sizeof(char*) * argc); + if (infps == NULL || infiles == NULL) + errc(1, ENOMEM, "malloc() fds"); do { - infp = fopen(infile = *argv, "r"); - if (infp == NULL) { + infps[fnum] = fopen(*argv, "r"); + if (infps[fnum] == NULL) { warn("%s", *argv); rval = 1; continue; } - rval |= decode(); - fclose(infp); + infiles[fnum] = *argv; + fnum++; } while (*++argv); } else { - infile = "stdin"; - infp = stdin; - rval = decode(); + infps = malloc(sizeof(FILE*)); + infiles = malloc(sizeof(char*)); + if (infps == NULL || infiles == NULL) + errc(1, ENOMEM, "malloc() fd"); + infps[fnum] = stdin; + infiles[fnum] = strdup("stdin"); + if (infiles[fnum] == NULL) + errc(1, ENOMEM, "malloc()"); + } + + if ((dirfd = open(".", O_DIRECTORY)) < 0) { + warn("open(%s)", "."); + exit(1); + } + + if (cap_enter() < 0 && errno != ENOSYS) + err(1, "failed to enter security sandbox"); + + for (i = 0; i < fnum; i++) { + rval |= decode(infps[i], infiles[i]); + fclose(infps[i]); } + + free(infps); + free(infiles); + exit(rval); } static int -decode(void) +decode(FILE *infp, char *infile) { int r, v; @@ -155,17 +235,17 @@ outfile = "/dev/stdout"; outfp = stdout; if (base64) - return (base64_decode()); + return (base64_decode(infp, infile)); else - return (uu_decode()); + return (uu_decode(infp, infile)); } - v = decode2(); + v = decode2(infp, infile); if (v == EOF) { warnx("%s: missing or bad \"begin\" line", infile); return (1); } for (r = v; cflag; r |= v) { - v = decode2(); + v = decode2(infp, infile); if (v == EOF) break; } @@ -173,7 +253,7 @@ } static int -decode2(void) +decode2(FILE *infp, char *infile) { int flags, fd, mode; size_t n, m; @@ -255,9 +335,13 @@ /* POSIX says "/dev/stdout" is a 'magic cookie' not a special file. */ if (pflag || strcmp(outfile, "/dev/stdout") == 0) outfp = stdout; - else { + else if (oflag && outfp != NULL) { + /* set mode of file created earlier */ + if (fchmod(fileno(outfp), mode) != 0) + warn("%s: chmod %s", infile, outfile); + } else { flags = O_WRONLY | O_CREAT | O_EXCL; - if (lstat(outfile, &st) == 0) { + if (fstatat(dirfd, outfile, &st, AT_SYMLINK_NOFOLLOW) == 0) { if (iflag) { warnc(EEXIST, "%s: %s", infile, outfile); return (0); @@ -266,7 +350,8 @@ case S_IFREG: case S_IFLNK: /* avoid symlink attacks */ - if (unlink(outfile) == 0 || errno == ENOENT) + if (unlinkat(dirfd, outfile, 0) == 0 || + errno == ENOENT) break; warn("%s: unlink %s", infile, outfile); return (1); @@ -283,24 +368,24 @@ return (1); } } else if (errno != ENOENT) { - warn("%s: %s", infile, outfile); + warn("%s: stat %s", infile, outfile); return (1); } - if ((fd = open(outfile, flags, mode)) < 0 || + if ((fd = openat(dirfd, outfile, flags, mode)) < 0 || (outfp = fdopen(fd, "w")) == NULL) { - warn("%s: %s", infile, outfile); + warn("%s: open %s", infile, outfile); return (1); } } if (base64) - return (base64_decode()); + return (base64_decode(infp, infile)); else - return (uu_decode()); + return (uu_decode(infp, infile)); } static int -get_line(char *buf, size_t size) +get_line(FILE *infp, char *infile, char *buf, size_t size) { if (fgets(buf, size, infp) != NULL) @@ -312,7 +397,7 @@ } static int -checkend(const char *ptr, const char *end, const char *msg) +checkend(char *infile, const char *ptr, const char *end, const char *msg) { size_t n; @@ -330,7 +415,7 @@ } static int -uu_decode(void) +uu_decode(FILE *infp, char *infile) { int i, ch; char *p; @@ -338,7 +423,7 @@ /* for each input line */ for (;;) { - switch (get_line(buf, sizeof(buf))) { + switch (get_line(infp, infile, buf, sizeof(buf))) { case 0: return (0); case 1: @@ -397,18 +482,18 @@ } } } - switch (get_line(buf, sizeof(buf))) { + switch (get_line(infp, infile, buf, sizeof(buf))) { case 0: return (0); case 1: return (1); default: - return (checkend(buf, "end", "no \"end\" line")); + return (checkend(infile, buf, "end", "no \"end\" line")); } } static int -base64_decode(void) +base64_decode(FILE *infp, char *infile) { int n, count, count4; char inbuf[MAXPATHLEN + 1], *p; @@ -418,7 +503,7 @@ leftover[0] = '\0'; for (;;) { strcpy(inbuf, leftover); - switch (get_line(inbuf + strlen(inbuf), + switch (get_line(infp, infile, inbuf + strlen(inbuf), sizeof(inbuf) - strlen(inbuf))) { case 0: return (0); @@ -450,7 +535,7 @@ break; fwrite(outbuf, 1, n, outfp); } - return (checkend(inbuf, "====", "error decoding base64 input stream")); + return (checkend(infile, inbuf, "====", "error decoding base64 input stream")); } static void