diff --git a/doc/gpgme.texi b/doc/gpgme.texi index ea6693ef..b9908170 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -2253,6 +2253,22 @@ the data. If this is set the OpenPGP engine may use this to decide on buffer allocation strategies and to provide a total value for its progress information. +@item io-buffer-size +The value is a decimal number with the length of internal buffers to +used for internal I/O operations. The value is capped at 1048576 (1 +MiB). In certain environments large buffers can yield a performance +boost for callback bases data object, but the details depend a lot on +the circumstances and the operating system. This flag may only be set +once and must be set before any actual I/O happens ion the data +objects. + +@item sensitive +If the numeric value is not 0 the data object is considered to contain +sensitive information like passwords or key material. If this is set +the internal buffers are securely overwritten with zeroes by +gpgme_data_release. + + @end table This function returns @code{0} on success. diff --git a/src/cJSON.c b/src/cJSON.c index 7769b0eb..1925a04e 100644 --- a/src/cJSON.c +++ b/src/cJSON.c @@ -50,21 +50,13 @@ #include "cJSON.h" + /* Only use calloc. */ #define CALLOC_ONLY 1 /* Maximum recursion depth */ #define MAX_DEPTH 512 -/* To avoid that a compiler optimizes certain memset calls away, these - macros may be used instead. */ -#define wipememory2(_ptr,_set,_len) do { \ - volatile char *_vptr=(volatile char *)(_ptr); \ - size_t _vlen=(_len); \ - while(_vlen) { *_vptr=(_set); _vptr++; _vlen--; } \ - } while(0) -#define wipememory(_ptr,_len) wipememory2(_ptr,0,_len) - /* We use malloc function wrappers from gpgrt (aka libgpg-error). */ #include #define xtrycalloc(a,b) gpgrt_calloc ((a), (b)) @@ -77,6 +69,16 @@ #endif +static void +wipememory (void *ptr, size_t len) +{ + /* Prevent compiler from optimizing away the call to memset by accessing + * memset through volatile pointer. */ + static void *(*volatile memset_ptr)(void *, int, size_t) = (void *)memset; + memset_ptr (ptr, 0, len); +} + + static int cJSON_strcasecmp (const char *s1, const char *s2) { diff --git a/src/conversion.c b/src/conversion.c index 1d28096d..17dce7f3 100644 --- a/src/conversion.c +++ b/src/conversion.c @@ -43,6 +43,17 @@ +void +_gpgme_wipememory (void *ptr, size_t len) +{ + /* Prevent compiler from optimizing away the call to memset by accessing + * memset through volatile pointer. */ + static void *(*volatile memset_ptr)(void *, int, size_t) = (void *)memset; + memset_ptr (ptr, 0, len); +} + + + static char * do_strconcat (const char *s1, va_list arg_ptr) { diff --git a/src/data.c b/src/data.c index 14652938..62b72312 100644 --- a/src/data.c +++ b/src/data.c @@ -339,6 +339,21 @@ _gpgme_data_release (gpgme_data_t dh) remove_from_property_table (dh, dh->propidx); if (dh->file_name) free (dh->file_name); + if (dh->inbound_buffer) + { + if (dh->sensitive) + _gpgme_wipememory (dh->inbound_buffer, dh->io_buffer_size); + free (dh->inbound_buffer); + } + if (dh->outbound_buffer) + { + if (dh->sensitive) + _gpgme_wipememory (dh->outbound_buffer, dh->io_buffer_size); + free (dh->outbound_buffer); + } + if (dh->sensitive) + _gpgme_wipememory (dh->outboundspace, BUFFER_SIZE); + free (dh); } @@ -431,11 +446,11 @@ gpgme_data_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) /* For relative movement, we must take into account the actual position of the read counter. */ if (whence == SEEK_CUR) - offset -= dh->pending_len; + offset -= dh->outbound_pending; offset = (*dh->cbs->seek) (dh, offset, whence); if (offset >= 0) - dh->pending_len = 0; + dh->outbound_pending = 0; return TRACE_SYSRES ((int)offset); } @@ -555,6 +570,28 @@ gpgme_data_set_flag (gpgme_data_t dh, const char *name, const char *value) { dh->size_hint= value? _gpgme_string_to_off (value) : 0; } + else if (!strcmp (name, "io-buffer-size")) + { + gpgme_off_t val; + + /* We may set this only once. */ + if (dh->io_buffer_size) + return gpg_error (GPG_ERR_CONFLICT); + + val = value? _gpgme_string_to_off (value) : 0; + if (val > 1024*1024) + val = 1024*1024; /* Cap at 1MiB */ + else if (val < BUFFER_SIZE) + val = 0; /* We can use the default buffer. */ + + /* Actual allocation happens as needed but we round it to a + * multiple of 1k. */ + dh->io_buffer_size = ((val + 1023)/1024)*1024; + } + else if (!strcmp (name, "sensitive")) + { + dh->sensitive = (value && *value)? !!atoi (value) : 0; + } else return gpg_error (GPG_ERR_UNKNOWN_NAME); @@ -569,14 +606,35 @@ gpgme_error_t _gpgme_data_inbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; + gpg_error_t err; gpgme_data_t dh = (gpgme_data_t) data->handler_value; - char buffer[BUFFER_SIZE]; - char *bufp = buffer; + char bufferspace[BUFFER_SIZE]; + char *buffer; + size_t buffer_size; + char *bufp; gpgme_ssize_t buflen; TRACE_BEG (DEBUG_CTX, "_gpgme_data_inbound_handler", dh, "fd=%d", fd); - buflen = _gpgme_io_read (fd, buffer, BUFFER_SIZE); + if (dh->io_buffer_size) + { + if (!dh->inbound_buffer) + { + dh->inbound_buffer = malloc (dh->io_buffer_size); + if (!dh->inbound_buffer) + return TRACE_ERR (gpg_error_from_syserror ()); + } + buffer_size = dh->io_buffer_size; + buffer = dh->inbound_buffer; + } + else + { + buffer_size = BUFFER_SIZE; + buffer = bufferspace; + } + bufp = buffer; + + buflen = _gpgme_io_read (fd, buffer, buffer_size); if (buflen < 0) return gpg_error_from_syserror (); if (buflen == 0) @@ -589,12 +647,21 @@ _gpgme_data_inbound_handler (void *opaque, int fd) { gpgme_ssize_t amt = gpgme_data_write (dh, bufp, buflen); if (amt == 0 || (amt < 0 && errno != EINTR)) - return TRACE_ERR (gpg_error_from_syserror ()); + { + err = gpg_error_from_syserror (); + goto leave; + } bufp += amt; buflen -= amt; } while (buflen > 0); - return TRACE_ERR (0); + err = 0; + + leave: + if (dh->sensitive && buffer == bufferspace) + _gpgme_wipememory (bufferspace, BUFFER_SIZE); + + return TRACE_ERR (err); } @@ -603,13 +670,34 @@ _gpgme_data_outbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; gpgme_data_t dh = (gpgme_data_t) data->handler_value; + char *buffer; + size_t buffer_size; gpgme_ssize_t nwritten; TRACE_BEG (DEBUG_CTX, "_gpgme_data_outbound_handler", dh, "fd=%d", fd); - if (!dh->pending_len) + if (dh->io_buffer_size) { - gpgme_ssize_t amt = gpgme_data_read (dh, dh->pending, BUFFER_SIZE); + if (!dh->outbound_buffer) + { + dh->outbound_buffer = malloc (dh->io_buffer_size); + if (!dh->outbound_buffer) + return TRACE_ERR (gpg_error_from_syserror ()); + dh->outbound_pending = 0; + } + buffer_size = dh->io_buffer_size; + buffer = dh->outbound_buffer; + } + else + { + buffer_size = BUFFER_SIZE; + buffer = dh->outboundspace; + } + + + if (!dh->outbound_pending) + { + gpgme_ssize_t amt = gpgme_data_read (dh, buffer, buffer_size); if (amt < 0) return TRACE_ERR (gpg_error_from_syserror ()); if (amt == 0) @@ -617,10 +705,10 @@ _gpgme_data_outbound_handler (void *opaque, int fd) _gpgme_io_close (fd); return TRACE_ERR (0); } - dh->pending_len = amt; + dh->outbound_pending = amt; } - nwritten = _gpgme_io_write (fd, dh->pending, dh->pending_len); + nwritten = _gpgme_io_write (fd, buffer, dh->outbound_pending); if (nwritten == -1 && errno == EAGAIN) return TRACE_ERR (0); @@ -637,9 +725,9 @@ _gpgme_data_outbound_handler (void *opaque, int fd) if (nwritten <= 0) return TRACE_ERR (gpg_error_from_syserror ()); - if (nwritten < dh->pending_len) - memmove (dh->pending, dh->pending + nwritten, dh->pending_len - nwritten); - dh->pending_len -= nwritten; + if (nwritten < dh->outbound_pending) + memmove (buffer, buffer + nwritten, dh->outbound_pending - nwritten); + dh->outbound_pending -= nwritten; return TRACE_ERR (0); } diff --git a/src/data.h b/src/data.h index 90e0a883..648d976f 100644 --- a/src/data.h +++ b/src/data.h @@ -33,6 +33,22 @@ #include "gpgme.h" +/* Figure out the standard size for internal data buffers. */ +#ifdef PIPE_BUF +# define BUFFER_SIZE PIPE_BUF +#else +# ifdef _POSIX_PIPE_BUF +# define BUFFER_SIZE _POSIX_PIPE_BUF +# else +# ifdef HAVE_W32_SYSTEM +# define BUFFER_SIZE 4096 +# else +# define BUFFER_SIZE 512 +# endif +# endif +#endif + + /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and @@ -76,28 +92,33 @@ struct gpgme_data gpgme_data_encoding_t encoding; unsigned int propidx; /* Index into the property table. */ -#ifdef PIPE_BUF -#define BUFFER_SIZE PIPE_BUF -#else -#ifdef _POSIX_PIPE_BUF -#define BUFFER_SIZE _POSIX_PIPE_BUF -#else -#ifdef HAVE_W32_SYSTEM -#define BUFFER_SIZE 4096 -#else -#define BUFFER_SIZE 512 -#endif -#endif -#endif - char pending[BUFFER_SIZE]; - int pending_len; - /* File name of the data object. */ char *file_name; /* Hint on the to be expected total size of the data. */ gpgme_off_t size_hint; + /* If no 0 the size of an allocated inbound or outpund buffers. The + * value is at least BUFFER_SIZE and capped at 1MiB. */ + unsigned int io_buffer_size; + + /* If not NULL a malloced buffer used for inbound data used instead + * of the handler's static buffer. Its size is io_buffer_size. */ + char *inbound_buffer; + + /* A default memory space for the outbound handler and the number of + * actual pending bytes. If outbound_buffer is not NULL, this is a + * malloced buffer used instead of the outboundspace. Its malloced + * size is io_buffer_size. */ + char outboundspace[BUFFER_SIZE]; + unsigned int outbound_pending; + char *outbound_buffer; + + /* If set sensitive data is conveyed via the internal buffer. This + * flags overwrites the memory of the buffers with zero before they + * are released. */ + unsigned int sensitive:1; + union { /* For gpgme_data_new_from_fd. */ diff --git a/src/util.h b/src/util.h index bc78c9b8..89075848 100644 --- a/src/util.h +++ b/src/util.h @@ -96,6 +96,9 @@ int _gpgme_ttyname_r (int fd, char *buf, size_t buflen); /*-- conversion.c --*/ +/* Make sure to to erase the memory (PTR,LEN). */ +void _gpgme_wipememory (void *ptr, size_t len); + /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ diff --git a/tests/run-decrypt.c b/tests/run-decrypt.c index f1a9fcc0..cf719925 100644 --- a/tests/run-decrypt.c +++ b/tests/run-decrypt.c @@ -89,6 +89,8 @@ show_usage (int ex) " --no-symkey-cache disable the use of that cache\n" " --ignore-mdc-error allow decryption of legacy data\n" " --unwrap remove only the encryption layer\n" + " --large-buffers use large I/O buffer\n" + " --sensitive mark data objects as sensitive\n" " --diagnostics print diagnostics\n" , stderr); exit (ex); @@ -114,6 +116,8 @@ main (int argc, char **argv) int no_symkey_cache = 0; int ignore_mdc_error = 0; int raw_output = 0; + int large_buffers = 0; + int sensitive = 0; int diagnostics = 0; if (argc) @@ -185,6 +189,16 @@ main (int argc, char **argv) diagnostics = 1; argc--; argv++; } + else if (!strcmp (*argv, "--large-buffers")) + { + large_buffers = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--sensitive")) + { + sensitive = 1; + argc--; argv++; + } else if (!strcmp (*argv, "--unwrap")) { flags |= GPGME_DECRYPT_UNWRAP; @@ -288,6 +302,26 @@ main (int argc, char **argv) gpgme_strerror (err)); exit (1); } + if (large_buffers) + { + err = gpgme_data_set_flag (out, "io-buffer-size", "1000000"); + if (err) + { + fprintf (stderr, PGM ": error setting io-buffer-size (out): %s\n", + gpgme_strerror (err)); + exit (1); + } + } + if (sensitive) + { + err = gpgme_data_set_flag (out, "sensitive", "1"); + if (err) + { + fprintf (stderr, PGM ": error setting sensitive flag (out): %s\n", + gpgme_strerror (err)); + exit (1); + } + } err = gpgme_op_decrypt_ext (ctx, flags, in, out); result = gpgme_op_decrypt_result (ctx);