gpg: Detect already compressed data also when using a pipe.

* common/iobuf.c (file_filter_ctx_t): Add fields for the peek feature.
(file_filter): Implement peeking.
(iobuf_ioctl): Add new IOBUF_IOCTL_PEEK.
* common/iobuf.h (IOBUF_IOCTL_PEEK, IOBUFCTRL_PEEK): New.
* common/miscellaneous.c (is_file_compressed): Rewrite.  Detect PDF.
* g10/encrypt.c (encrypt_simple): Peek before detecting compression.
(encrypt_crypt): Ditto.
* g10/sign.c (sign_file): Also detect already compressed data.

* g10/options.h (opt): Add explicit_compress_option.
* g10/gpg.c (main): Set opt.explicit_compress_option for -z.

--

Note that this patch also introduces a compression check for signing
which was never done in the past.

GnuPG-bug-id: 6332
This commit is contained in:
Werner Koch 2023-01-18 18:04:50 +01:00
parent 94ae43be36
commit 60963d98cf
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
9 changed files with 201 additions and 64 deletions

View File

@ -1,7 +1,7 @@
/* iobuf.c - File Handling for OpenPGP.
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2006, 2007, 2008,
* 2009, 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
* Copyright (C) 2015, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
@ -95,6 +95,9 @@ typedef struct
int eof_seen;
int delayed_rc;
int print_only_name; /* Flags indicating that fname is not a real file. */
char peeked[32]; /* Read ahead buffer. */
byte npeeked; /* Number of bytes valid in peeked. */
byte upeeked; /* Number of bytes used from peeked. */
char fname[1]; /* Name of the file. */
} file_filter_ctx_t;
@ -458,7 +461,16 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf,
if (control == IOBUFCTRL_UNDERFLOW)
{
log_assert (size); /* We need a buffer. */
if (a->eof_seen)
if (a->npeeked > a->upeeked)
{
nbytes = a->npeeked - a->upeeked;
if (nbytes > size)
nbytes = size;
memcpy (buf, a->peeked + a->upeeked, nbytes);
a->upeeked += nbytes;
*ret_len = nbytes;
}
else if (a->eof_seen)
{
rc = -1;
*ret_len = 0;
@ -596,6 +608,73 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf,
a->delayed_rc = 0;
a->keep_open = 0;
a->no_cache = 0;
a->npeeked = 0;
a->upeeked = 0;
}
else if (control == IOBUFCTRL_PEEK)
{
/* Peek on the input. */
#ifdef HAVE_W32_SYSTEM
unsigned long nread;
nbytes = 0;
if (!ReadFile (f, a->peeked, sizeof a->peeked, &nread, NULL))
{
int ec = (int) GetLastError ();
if (ec != ERROR_BROKEN_PIPE)
{
rc = gpg_error_from_errno (ec);
log_error ("%s: read error: ec=%d\n", a->fname, ec);
}
a->npeeked = 0;
}
else if (!nread)
{
a->eof_seen = 1;
a->npeeked = 0;
}
else
{
a->npeeked = nread;
}
#else /* Unix */
int n;
peek_more:
do
{
n = read (f, a->peeked + a->npeeked, sizeof a->peeked - a->npeeked);
}
while (n == -1 && errno == EINTR);
if (n > 0)
{
a->npeeked += n;
if (a->npeeked < sizeof a->peeked)
goto peek_more;
}
else if (!n) /* eof */
{
if (a->npeeked)
a->delayed_rc = -1;
else
a->eof_seen = 1;
}
else /* error */
{
rc = gpg_error_from_syserror ();
if (gpg_err_code (rc) != GPG_ERR_EPIPE)
log_error ("%s: read error: %s\n", a->fname, gpg_strerror (rc));
if (a->npeeked)
a->delayed_rc = rc;
}
#endif /* Unix */
size = a->npeeked < size? a->npeeked : size;
memcpy (buf, a->peeked, size);
*ret_len = size;
rc = 0; /* Return success - the user needs to check ret_len. */
}
else if (control == IOBUFCTRL_DESC)
{
@ -1576,6 +1655,25 @@ iobuf_ioctl (iobuf_t a, iobuf_ioctl_t cmd, int intval, void *ptrval)
return fd_cache_synchronize (ptrval);
}
}
else if (cmd == IOBUF_IOCTL_PEEK)
{
/* Peek at a justed opened file. Use this only directly after a
* file has been opened for reading. Don't use it after you did
* a seek. This works only if just file filter has been
* pushed. Expects a buffer wit size INTVAL at PTRVAL and returns
* the number of bytes put into the buffer. */
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: ioctl '%s' peek\n",
a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc));
if (a->filter == file_filter && ptrval && intval)
{
file_filter_ctx_t *fcx = a->filter_ov;
size_t len = intval;
if (!file_filter (fcx, IOBUFCTRL_PEEK, NULL, ptrval, &len))
return (int)len;
}
}
return -1;

View File

@ -106,6 +106,7 @@ enum
IOBUFCTRL_FLUSH = 4,
IOBUFCTRL_DESC = 5,
IOBUFCTRL_CANCEL = 6,
IOBUFCTRL_PEEK = 7,
IOBUFCTRL_USER = 16
};
@ -116,7 +117,8 @@ typedef enum
IOBUF_IOCTL_KEEP_OPEN = 1, /* Uses intval. */
IOBUF_IOCTL_INVALIDATE_CACHE = 2, /* Uses ptrval. */
IOBUF_IOCTL_NO_CACHE = 3, /* Uses intval. */
IOBUF_IOCTL_FSYNC = 4 /* Uses ptrval. */
IOBUF_IOCTL_FSYNC = 4, /* Uses ptrval. */
IOBUF_IOCTL_PEEK = 5 /* Uses intval and ptrval. */
} iobuf_ioctl_t;
enum iobuf_use

View File

@ -418,7 +418,7 @@ decode_c_string (const char *src)
/* Check whether (BUF,LEN) is valid header for an OpenPGP compressed
* packet. LEN should be at least 6. */
static int
is_openpgp_compressed_packet (unsigned char *buf, size_t len)
is_openpgp_compressed_packet (const unsigned char *buf, size_t len)
{
int c, ctb, pkttype;
int lenbytes;
@ -460,63 +460,46 @@ is_openpgp_compressed_packet (unsigned char *buf, size_t len)
/*
* Check if the file is compressed.
* Check if the file is compressed. You need to pass the first bytes
* of the file as (BUF,BUFLEN). Returns true if the buffer seems to
* be compressed.
*/
int
is_file_compressed (const char *s, int *ret_rc)
is_file_compressed (const byte *buf, unsigned int buflen)
{
iobuf_t a;
byte buf[6];
int i;
int rc = 0;
int overflow;
int i;
struct magic_compress_s {
size_t len;
byte magic[4];
} magic[] = {
{ 3, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */
{ 3, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */
{ 4, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */
};
struct magic_compress_s
{
byte len;
byte magic[5];
} magic[] =
{
{ 3, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */
{ 3, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */
{ 4, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */
{ 5, { '%', 'P', 'D', 'F', '-'} } /* PDF */
};
if ( iobuf_is_pipe_filename (s) || !ret_rc )
return 0; /* We can't check stdin or no file was given */
a = iobuf_open( s );
if ( a == NULL ) {
*ret_rc = gpg_error_from_syserror ();
return 0;
}
iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if ( iobuf_get_filelength( a, &overflow ) < 6 && !overflow) {
*ret_rc = 0;
goto leave;
if ( buflen < 6 )
{
return 0; /* Too short to check - assume uncompressed. */
}
if ( iobuf_read( a, buf, 6 ) == -1 ) {
*ret_rc = a->error;
goto leave;
}
for ( i = 0; i < DIM( magic ); i++ ) {
if ( !memcmp( buf, magic[i].magic, magic[i].len ) ) {
*ret_rc = 0;
rc = 1;
goto leave;
for ( i = 0; i < DIM (magic); i++ )
{
if ( !memcmp( buf, magic[i].magic, magic[i].len ))
{
return 1; /* Is compressed. */
}
}
if (is_openpgp_compressed_packet (buf, 6))
{
*ret_rc = 0;
rc = 1;
}
if (buflen >= 6 && is_openpgp_compressed_packet (buf, buflen))
{
return 1; /* Already compressed. */
}
leave:
iobuf_close( a );
return rc;
return 0; /* Not detected as compressed. */
}

View File

@ -359,7 +359,7 @@ char *try_make_printable_string (const void *p, size_t n, int delim);
char *make_printable_string (const void *p, size_t n, int delim);
char *decode_c_string (const char *src);
int is_file_compressed (const char *s, int *ret_rc);
int is_file_compressed (const byte *buf, unsigned int buflen);
int match_multistr (const char *multistr,const char *match);

View File

@ -635,12 +635,13 @@ The @option{--dearmor} command can also be used to dearmor PEM armors.
@item --unwrap
@opindex unwrap
This command is similar to @option{--decrypt} with the change that the
This command is similar to @option{--decrypt} with the difference that the
output is not the usual plaintext but the original message with the
decryption layer removed. Thus the output will be an OpenPGP data
encryption layer removed. Thus the output will be an OpenPGP data
structure which often means a signed OpenPGP message. Note that this
command may or may not remove a compression layer which is often found
beneath the encryption layer.
beneath the encryption layer. Without the option @option{--output}
the result is written to a file with the suffix stripped.
@item --tofu-policy @{auto|good|unknown|bad|ask@} @var{keys}
@opindex tofu-policy
@ -860,9 +861,10 @@ line.
@opindex keyedit:tsign
Make a trust signature. This is a signature that combines the notions
of certification (like a regular signature), and trust (like the
"trust" command). It is generally only useful in distinct communities
or groups. For more information please read the sections
``Trust Signature'' and ``Regular Expression'' in RFC-4880.
"trust" command). It is generally useful in distinct communities
or groups to implement the concept of a Trusted Introducer. For
more information please read the sections ``Trust Signature'' and
``Regular Expression'' in RFC-4880.
@end table
@c man:.RS
@ -1658,6 +1660,16 @@ for the BZIP2 compression algorithm (defaulting to 6 as well). This is a
different option from @option{--compress-level} since BZIP2 uses a
significant amount of memory for each additional compression level.
@option{-z} sets both. A value of 0 for @var{n} disables compression.
A value of -1 forces compression using the default level.
Except for the @option{--store} command compression is always used
unless @command{gpg} detects that the input is already compressed. To
inhibit the use of compression use @option{-z0}; to force compression
use @option{-z-1} or option @option{z} with another compression level
than the default as indicated by -1. Note that this overriding of the
default deection works only with @option{z} and not with the long
variant of this option.
@item --bzip2-decompress-lowmem
@opindex bzip2-decompress-lowmem

View File

@ -1,7 +1,7 @@
/* encrypt.c - Main encryption driver
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2009 Free Software Foundation, Inc.
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
@ -17,6 +17,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
@ -409,6 +410,8 @@ encrypt_simple (const char *filename, int mode, int use_seskey)
text_filter_context_t tfx;
progress_filter_context_t *pfx;
int do_compress = !!default_compress_algo();
char peekbuf[32];
int peekbuflen;
if (!gnupg_rng_is_compliant (opt.compliance))
{
@ -445,6 +448,14 @@ encrypt_simple (const char *filename, int mode, int use_seskey)
return rc;
}
peekbuflen = iobuf_ioctl (inp, IOBUF_IOCTL_PEEK, sizeof peekbuf, peekbuf);
if (peekbuflen < 0)
{
peekbuflen = 0;
if (DBG_FILTER)
log_debug ("peeking at input failed\n");
}
handle_progress (pfx, inp, filename);
if (opt.textmode)
@ -509,10 +520,11 @@ encrypt_simple (const char *filename, int mode, int use_seskey)
if (do_compress
&& cfx.dek
&& (cfx.dek->use_mdc || cfx.dek->use_aead)
&& is_file_compressed(filename, &rc))
&& !opt.explicit_compress_option
&& is_file_compressed (peekbuf, peekbuflen))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename);
log_info(_("'%s' already compressed\n"), filename? filename: "[stdin]");
do_compress = 0;
}
@ -780,6 +792,8 @@ encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
progress_filter_context_t *pfx;
PK_LIST pk_list;
int do_compress;
char peekbuf[32];
int peekbuflen;
if (filefd != -1 && filename)
return gpg_error (GPG_ERR_INV_ARG); /* Both given. */
@ -852,6 +866,14 @@ encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
if (opt.verbose)
log_info (_("reading from '%s'\n"), iobuf_get_fname_nonnull (inp));
peekbuflen = iobuf_ioctl (inp, IOBUF_IOCTL_PEEK, sizeof peekbuf, peekbuf);
if (peekbuflen < 0)
{
peekbuflen = 0;
if (DBG_FILTER)
log_debug ("peeking at input failed\n");
}
handle_progress (pfx, inp, filename);
if (opt.textmode)
@ -884,10 +906,11 @@ encrypt_crypt (ctrl_t ctrl, int filefd, const char *filename,
* ciphertext attacks. */
if (do_compress
&& (cfx.dek->use_mdc || cfx.dek->use_aead)
&& is_file_compressed (filename, &rc2))
&& !opt.explicit_compress_option
&& is_file_compressed (peekbuf, peekbuflen))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename);
log_info(_("'%s' already compressed\n"), filename? filename: "[stdin]");
do_compress = 0;
}
if (rc2)

View File

@ -3203,6 +3203,7 @@ main (int argc, char **argv)
case oCompress:
/* this is the -z command line option */
opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int;
opt.explicit_compress_option = 1;
break;
case oCompressLevel: opt.compress_level = pargs.r.ret_int; break;
case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break;

View File

@ -98,6 +98,7 @@ struct
int def_digest_algo;
int cert_digest_algo;
int compress_algo;
int explicit_compress_option; /* A compress option was explicitly given. */
int compress_level;
int bz2_compress_level;
int bz2_decompress_lowmem;

View File

@ -1037,6 +1037,9 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int multifile = 0;
u32 duration=0;
pt_extra_hash_data_t extrahash = NULL;
char peekbuf[32];
int peekbuflen = 0;
pfx = new_progress_context ();
afx = new_armor_context ();
@ -1095,6 +1098,14 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
goto leave;
}
peekbuflen = iobuf_ioctl (inp, IOBUF_IOCTL_PEEK, sizeof peekbuf, peekbuf);
if (peekbuflen < 0)
{
peekbuflen = 0;
if (DBG_FILTER)
log_debug ("peeking at input failed\n");
}
handle_progress (pfx, inp, fname);
}
@ -1251,8 +1262,14 @@ sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
{
int compr_algo = opt.compress_algo;
/* If not forced by user */
if (compr_algo==-1)
if (!opt.explicit_compress_option
&& is_file_compressed (peekbuf, peekbuflen))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), fname? fname: "[stdin]");
compr_algo = 0;
}
else if (compr_algo==-1)
{
/* If we're not encrypting, then select_algo_from_prefs
* will fail and we'll end up with the default. If we are