diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index eb172b319..4ef8e73c2 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -17,6 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -293,8 +294,10 @@ add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) xfree (hdr); else { + /* FIXME: We don't have the extended info yet available so we + * can't print them. */ if (opt.verbose) - gpgtar_print_header (hdr, log_get_stream ()); + gpgtar_print_header (hdr, NULL, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; } @@ -771,7 +774,7 @@ build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) hdr->name, gpg_strerror (err)); return err; } - sl->flags = 2; /* Mark as linkname */ + sl->flags = 2; /* Mark as linkpath */ } } #endif /*!HAVE_W32_SYSTEM*/ diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index 3da100c07..e9baada4e 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -1,4 +1,6 @@ /* gpgtar-extract.c - Extract from a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. @@ -15,6 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -33,26 +36,66 @@ #include "../common/ccparray.h" #include "gpgtar.h" +static gpg_error_t +check_suspicious_name (const char *name) +{ + size_t n; + + n = strlen (name); +#ifdef HAVE_DOSISH_SYSTEM + if (strchr (name, '\\')) + { + log_error ("filename '%s' contains a backslash - " + "can't extract on this system\n", name); + return gpg_error (GPG_ERR_INV_NAME); + } +#endif /*HAVE_DOSISH_SYSTEM*/ + + if (!n + || strstr (name, "//") + || strstr (name, "/../") + || !strncmp (name, "../", 3) + || (n >= 3 && !strcmp (name+n-3, "/.." ))) + { + log_error ("filename '%s' has suspicious parts - not extracting\n", + name); + return gpg_error (GPG_ERR_INV_NAME); + } + + return 0; +} + static gpg_error_t extract_regular (estream_t stream, const char *dirname, - tarinfo_t info, tar_header_t hdr) + tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; char record[RECORDSIZE]; size_t n, nbytes, nwritten; - char *fname; + char *fname_buffer = NULL; + const char *fname; estream_t outfp = NULL; + strlist_t sl; - fname = strconcat (dirname, "/", hdr->name, NULL); - if (!fname) + fname = hdr->name; + for (sl = exthdr; sl; sl = sl->next) + if (sl->flags == 1) + fname = sl->d; + + err = check_suspicious_name (fname); + if (err) + goto leave; + + fname_buffer = strconcat (dirname, "/", fname, NULL); + if (!fname_buffer) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } - else - err = 0; + fname = fname_buffer; + if (opt.dry_run) outfp = es_fopenmem (0, "wb"); @@ -97,29 +140,36 @@ extract_regular (estream_t stream, const char *dirname, log_error ("error removing incomplete file '%s': %s\n", fname, gpg_strerror (gpg_error_from_syserror ())); } - xfree (fname); + xfree (fname_buffer); return err; } static gpg_error_t -extract_directory (const char *dirname, tar_header_t hdr) +extract_directory (const char *dirname, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; - char *fname; - size_t prefixlen; + const char *name; + char *fname = NULL; + strlist_t sl; - prefixlen = strlen (dirname) + 1; - fname = strconcat (dirname, "/", hdr->name, NULL); + name = hdr->name; + for (sl = exthdr; sl; sl = sl->next) + if (sl->flags == 1) + name = sl->d; + + err = check_suspicious_name (name); + if (err) + goto leave; + + fname = strconcat (dirname, "/", name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } - else - err = 0; - + /* Remove a possible trailing slash. */ if (fname[strlen (fname)-1] == '/') fname[strlen (fname)-1] = 0; @@ -136,8 +186,13 @@ extract_directory (const char *dirname, tar_header_t hdr) { /* Try to create the directory with parents but keep the original error code in case of a failure. */ - char *p; int rc = 0; + char *p; + size_t prefixlen; + + /* (PREFIXLEN is the length of the new directory we use to + * extract the tarball.) */ + prefixlen = strlen (dirname) + 1; for (p = fname+prefixlen; (p = strchr (p, '/')); p++) { @@ -165,36 +220,15 @@ extract_directory (const char *dirname, tar_header_t hdr) static gpg_error_t extract (estream_t stream, const char *dirname, tarinfo_t info, - tar_header_t hdr) + tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; size_t n; - n = strlen (hdr->name); -#ifdef HAVE_DOSISH_SYSTEM - if (strchr (hdr->name, '\\')) - { - log_error ("filename '%s' contains a backslash - " - "can't extract on this system\n", hdr->name); - return gpg_error (GPG_ERR_INV_NAME); - } -#endif /*HAVE_DOSISH_SYSTEM*/ - - if (!n - || strstr (hdr->name, "//") - || strstr (hdr->name, "/../") - || !strncmp (hdr->name, "../", 3) - || (n >= 3 && !strcmp (hdr->name+n-3, "/.." ))) - { - log_error ("filename '%s' as suspicious parts - not extracting\n", - hdr->name); - return gpg_error (GPG_ERR_INV_NAME); - } - if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) - err = extract_regular (stream, dirname, info, hdr); + err = extract_regular (stream, dirname, info, hdr, exthdr); else if (hdr->typeflag == TF_DIRECTORY) - err = extract_directory (dirname, hdr); + err = extract_directory (dirname, hdr, exthdr); else { char record[RECORDSIZE]; @@ -286,6 +320,7 @@ gpgtar_extract (const char *filename, int decrypt) estream_t stream; estream_t cipher_stream = NULL; tar_header_t header = NULL; + strlist_t extheader = NULL; const char *dirprefix = NULL; char *dirname = NULL; struct tarinfo_s tarinfo_buffer; @@ -388,19 +423,22 @@ gpgtar_extract (const char *filename, int decrypt) for (;;) { - err = gpgtar_read_header (stream, tarinfo, &header); + err = gpgtar_read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; - err = extract (stream, dirname, tarinfo, header); + err = extract (stream, dirname, tarinfo, header, extheader); if (err) goto leave; + free_strlist (extheader); + extheader = NULL; xfree (header); header = NULL; } leave: + free_strlist (extheader); xfree (header); xfree (dirname); if (stream != es_stdin) diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 396e837f4..413abeed3 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -1,4 +1,6 @@ /* gpgtar-list.c - List a TAR archive + * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH + * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. @@ -15,6 +17,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -22,7 +25,6 @@ #include #include #include -#include #include "../common/i18n.h" #include "gpgtar.h" @@ -160,11 +162,15 @@ parse_header (const void *record, const char *filename, tarinfo_t info) case '5': header->typeflag = TF_DIRECTORY; break; case '6': header->typeflag = TF_FIFO; break; case '7': header->typeflag = TF_RESERVED; break; + case 'g': header->typeflag = TF_GEXTHDR; break; + case 'x': header->typeflag = TF_EXTHDR; break; default: header->typeflag = TF_UNKNOWN; break; } /* Compute the number of data records following this header. */ - if (header->typeflag == TF_REGULAR || header->typeflag == TF_UNKNOWN) + if (header->typeflag == TF_REGULAR + || header->typeflag == TF_EXTHDR + || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; @@ -181,18 +187,93 @@ parse_header (const void *record, const char *filename, tarinfo_t info) return header; } +/* Parse the extended header. This funcion may modify BUFFER. */ +static gpg_error_t +parse_extended_header (const char *fname, + char *buffer, size_t buflen, strlist_t *r_exthdr) +{ + unsigned int reclen; + unsigned char *p, *record; + strlist_t sl; + + while (buflen) + { + record = buffer; /* Remember begin of record. */ + reclen = 0; + for (p = buffer; buflen && digitp (p); buflen--, p++) + { + reclen *= 10; + reclen += (*p - '0'); + } + if (!buflen || *p != ' ') + { + log_error ("%s: malformed record length in extended header\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + p++; /* Skip space. */ + buflen--; + if (buflen + (p-record) < reclen) + { + log_error ("%s: extended header record larger" + " than total extended header data\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + if (reclen < (p-record)+2 || record[reclen-1] != '\n') + { + log_error ("%s: malformed extended header record\n", fname); + return gpg_error (GPG_ERR_INV_RECORD); + } + record[reclen-1] = 0; /* For convenience change LF to a Nul. */ + reclen -= (p-record); + /* P points to the begin of the keyword and RECLEN is the + * remaining length of the record excluding the LF. */ + if (memchr (p, 0, reclen-1) + && (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9))) + { + log_error ("%s: extended header record has an embedded nul" + " - ignoring\n", fname); + } + else if (!strncmp (p, "path=", 5)) + { + sl = add_to_strlist_try (r_exthdr, p+5); + if (!sl) + return gpg_error_from_syserror (); + sl->flags = 1; /* Mark as path */ + } + else if (!strncmp (p, "linkpath=", 9)) + { + sl = add_to_strlist_try (r_exthdr, p+9); + if (!sl) + return gpg_error_from_syserror (); + sl->flags = 2; /* Mark as linkpath */ + } + + buffer = p + reclen; + buflen -= reclen; + } + + return 0; +} /* Read the next block, assuming it is a tar header. Returns a header - object on success in R_HEADER, or an error. If the stream is - consumed, R_HEADER is set to NULL. In case of an error an error - message has been printed. */ + * object on success in R_HEADER, or an error. If the stream is + * consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case + * of an error an error message is printed. If the header is an + * extended header, a string list is allocated and stored at + * R_EXTHEADER; the caller should provide a pointer to NULL. Such an + * extended header is fully processed here and the returned R_HEADER + * has then the next regular header. */ static gpg_error_t -read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) +read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header, strlist_t *r_extheader) { gpg_error_t err; char record[RECORDSIZE]; int i; + tar_header_t hdr; + char *buffer; + size_t buflen, nrec; err = read_record (stream, record); if (err) @@ -224,7 +305,67 @@ read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) } *r_header = parse_header (record, es_fname_get (stream), info); - return *r_header ? 0 : gpg_error_from_syserror (); + if (!*r_header) + return gpg_error_from_syserror (); + hdr = *r_header; + + if (hdr->typeflag != TF_EXTHDR || !r_extheader) + return 0; + + /* Read the extended header. */ + if (!hdr->nrecords) + { + /* More than 64k for an extedned header is surely too large. */ + log_info ("%s: warning: empty extended header\n", + es_fname_get (stream)); + return 0; + } + if (hdr->nrecords > 65536 / RECORDSIZE) + { + /* More than 64k for an extedned header is surely too large. */ + log_error ("%s: extended header too large - skipping\n", + es_fname_get (stream)); + return 0; + } + + buffer = xtrymalloc (hdr->nrecords * RECORDSIZE); + if (!buffer) + { + err = gpg_error_from_syserror (); + log_error ("%s: error allocating space for extended header: %s\n", + es_fname_get (stream), gpg_strerror (err)); + return err; + } + buflen = 0; + + for (nrec=0; nrec < hdr->nrecords;) + { + err = read_record (stream, buffer + buflen); + if (err) + { + xfree (buffer); + return err; + } + info->nblocks++; + nrec++; + if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) + buflen += RECORDSIZE; + else + buflen += (hdr->size % RECORDSIZE); + } + + err = parse_extended_header (es_fname_get (stream), + buffer, buflen, r_extheader); + if (err) + { + free_strlist (*r_extheader); + *r_extheader = NULL; + } + + xfree (buffer); + /* Now tha the extedned header has been read, we read the next + * header without allowing an extended header. */ + return read_header (stream, info, r_header, NULL); } @@ -249,11 +390,13 @@ skip_data (estream_t stream, tarinfo_t info, tar_header_t header) static void -print_header (tar_header_t header, estream_t out) +print_header (tar_header_t header, strlist_t extheader, estream_t out) { unsigned long mask; char modestr[10+1]; int i; + strlist_t sl; + const char *name, *linkname; *modestr = '?'; switch (header->typeflag) @@ -266,6 +409,8 @@ print_header (tar_header_t header, estream_t out) case TF_DIRECTORY:*modestr = 'd'; break; case TF_FIFO: *modestr = 'f'; break; case TF_RESERVED: *modestr = '='; break; + case TF_EXTHDR: break; + case TF_GEXTHDR: break; case TF_UNKNOWN: break; case TF_NOTSUP: break; } @@ -279,9 +424,25 @@ print_header (tar_header_t header, estream_t out) modestr[9] = modestr[9] == 'x'? 't':'T'; modestr[10] = 0; - es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s\n", + /* FIXME: We do not parse the linkname unless its part of an + * extended header. */ + name = header->name; + linkname = header->typeflag == TF_SYMLINK? "?" : NULL; + + for (sl = extheader; sl; sl = sl->next) + { + if (sl->flags == 1) + name = sl->d; + else if (sl->flags == 2) + linkname = sl->d; + } + + es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n", modestr, header->nlink, header->uid, header->gid, header->size, - isotimestamp (header->mtime), header->name); + isotimestamp (header->mtime), + name, + linkname? " -> " : "", + linkname? linkname : ""); } @@ -295,6 +456,7 @@ gpgtar_list (const char *filename, int decrypt) estream_t stream; estream_t cipher_stream = NULL; tar_header_t header = NULL; + strlist_t extheader = NULL; struct tarinfo_s tarinfo_buffer; tarinfo_t tarinfo = &tarinfo_buffer; @@ -360,20 +522,23 @@ gpgtar_list (const char *filename, int decrypt) for (;;) { - err = read_header (stream, tarinfo, &header); + err = read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; - print_header (header, es_stdout); + print_header (header, extheader, es_stdout); if (skip_data (stream, tarinfo, header)) goto leave; + free_strlist (extheader); + extheader = NULL; xfree (header); header = NULL; } leave: + free_strlist (extheader); xfree (header); if (stream != es_stdin) es_fclose (stream); @@ -383,14 +548,15 @@ gpgtar_list (const char *filename, int decrypt) } gpg_error_t -gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header) +gpgtar_read_header (estream_t stream, tarinfo_t info, + tar_header_t *r_header, strlist_t *r_extheader) { - return read_header (stream, info, r_header); + return read_header (stream, info, r_header, r_extheader); } void -gpgtar_print_header (tar_header_t header, estream_t out) +gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out) { if (header && out) - print_header (header, out); + print_header (header, extheader, out); } diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 599f17faa..5e44112ca 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -95,6 +95,8 @@ typedef enum TF_DIRECTORY, TF_FIFO, TF_RESERVED, + TF_GEXTHDR, /* Global extended header. */ + TF_EXTHDR, /* Extended header. */ TF_UNKNOWN, /* Needs to be treated as regular file. */ TF_NOTSUP /* Not supported (used with --create). */ } typeflag_t; @@ -140,8 +142,9 @@ gpg_error_t gpgtar_extract (const char *filename, int decrypt); /*-- gpgtar-list.c --*/ gpg_error_t gpgtar_list (const char *filename, int decrypt); gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, - tar_header_t *r_header); -void gpgtar_print_header (tar_header_t header, estream_t out); + tar_header_t *r_header, strlist_t *r_extheader); +void gpgtar_print_header (tar_header_t header, strlist_t extheader, + estream_t out); #endif /*GPGTAR_H*/