Mailing List Archive

[PATCH GnuPG] Fix handling of I/O errors
Errors writing to stdout, stderr, or the status file must result in a
non-zero status code. Also BUG() if a CR or LF would be injected into
the status stream, escape bytes above 126, add a log_assert(), check
the return value of snprintf(), and escape backslashes in
write_status_printf(). Finally, ensure that errors writing to
the status file cause all status output to stop immediately.

GnuPG-bug-id: T6185
Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
g10/cpr.c | 188 ++++++++++++++++++++++++++++++++++++-----------------
g10/gpg.c | 3 +
g10/gpgv.c | 2 +
g10/main.h | 1 +
4 files changed, 134 insertions(+), 60 deletions(-)

diff --git a/g10/cpr.c b/g10/cpr.c
index 79eff314375f07380f7752736ddf2613dbe8b378..f60c745bf98a6536aa048f2685ef3341664b4bb8 100644
--- a/g10/cpr.c
+++ b/g10/cpr.c
@@ -49,14 +49,17 @@ progress_cb (void *ctx, const char *what, int printchar,
int current, int total)
{
char buf[50];
+ int res;

(void)ctx;

if ( printchar == '\n' && !strcmp (what, "primegen") )
- snprintf (buf, sizeof buf, "%.20s X 100 100", what );
+ res = snprintf (buf, sizeof buf, "%.20s X 100 100", what );
else
- snprintf (buf, sizeof buf, "%.20s %c %d %d",
- what, printchar=='\n'?'X':printchar, current, total );
+ res = snprintf (buf, sizeof buf, "%.20s %c %d %d",
+ what, printchar=='\n'?'X':printchar, current, total );
+ if ((unsigned)res >= sizeof buf)
+ abort();
write_status_text (STATUS_PROGRESS, buf);
}

@@ -140,24 +143,41 @@ write_status ( int no )
write_status_text( no, NULL );
}

+int
+check_status_error (void)
+{
+ if (!statusfp)
+ return 0;
+ if (!es_fflush (statusfp) && !es_ferror(statusfp))
+ return 0;
+ if (opt.exit_on_status_write_error)
+ g10_exit (1);
+ if (statusfp != es_stdout && statusfp != es_stderr)
+ es_fclose(statusfp);
+ statusfp = NULL;
+ return 1;
+}

/* Write a status line with code NO followed by the string TEXT and
* directly followed by the remaining strings up to a NULL. Embedded
- * CR and LFs in the strings (but not in TEXT) are C-style escaped.*/
+ * CR and LFs in the strings and TEXT are C-style escaped.*/
void
write_status_strings (int no, const char *text, ...)
{
va_list arg_ptr;
const char *s;
+ int err = 0;

if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */

- es_fputs ("[GNUPG:] ", statusfp);
- es_fputs (get_status_string (no), statusfp);
+ if (es_fputs ("[GNUPG:] ", statusfp) ||
+ es_fputs (get_status_string (no), statusfp))
+ goto bad;
if ( text )
{
- es_putc ( ' ', statusfp);
+ if ( es_putc ( ' ', statusfp) != ' ' )
+ goto bad;
va_start (arg_ptr, text);
s = text;
do
@@ -165,19 +185,24 @@ write_status_strings (int no, const char *text, ...)
for (; *s; s++)
{
if (*s == '\n')
- es_fputs ("\\n", statusfp);
+ err = es_fputs ("\\n", statusfp);
else if (*s == '\r')
- es_fputs ("\\r", statusfp);
+ err = es_fputs ("\\r", statusfp);
+ else if (*s == '\\')
+ err = es_fputs ("\\\\", statusfp);
else
- es_fputc (*(const byte *)s, statusfp);
+ err = es_fputc (*(const byte *)s, statusfp);
+ if (err == EOF)
+ goto bad;
}
}
while ((s = va_arg (arg_ptr, const char*)));
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+
+bad:
+ check_status_error();
}


@@ -190,6 +215,7 @@ write_status_strings2 (ctrl_t dummy, int no, ...)
{
va_list arg_ptr;
const char *s;
+ int err = 0;

(void)dummy;

@@ -198,29 +224,33 @@ write_status_strings2 (ctrl_t dummy, int no, ...)

va_start (arg_ptr, no);

- es_fputs ("[GNUPG:] ", statusfp);
- es_fputs (get_status_string (no), statusfp);
+ if (es_fputs ("[GNUPG:] ", statusfp) ||
+ es_fputs (get_status_string (no), statusfp))
+ goto bad;
while ((s = va_arg (arg_ptr, const char*)))
{
- if (*s)
- es_putc (' ', statusfp);
+ if (*s && es_putc (' ', statusfp) != ' ')
+ goto bad;
for (; *s; s++)
{
if (*s == '\n')
- es_fputs ("\\n", statusfp);
+ err = es_fputs ("\\n", statusfp);
else if (*s == '\r')
- es_fputs ("\\r", statusfp);
+ err = es_fputs ("\\r", statusfp);
+ else if (*s == '\\')
+ err = es_fputs ("\\\\", statusfp);
else
- es_fputc (*(const byte *)s, statusfp);
+ err = es_fputc (*(const byte *)s, statusfp);
+ if (err == EOF)
+ goto bad;
}
}
es_putc ('\n', statusfp);

va_end (arg_ptr);

- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
-
+bad:
+ check_status_error();
return 0;
}

@@ -239,45 +269,52 @@ write_status_printf (int no, const char *format, ...)
{
va_list arg_ptr;
char *buf;
+ int err = 0;

if (!statusfp || !status_currently_allowed (no) )
return; /* Not enabled or allowed. */

- es_fputs ("[GNUPG:] ", statusfp);
- es_fputs (get_status_string (no), statusfp);
+ va_start (arg_ptr, format);
+ if (es_fputs ("[GNUPG:] ", statusfp) ||
+ es_fputs (get_status_string (no), statusfp))
+ goto bad;
if (format)
{
- es_putc ( ' ', statusfp);
- va_start (arg_ptr, format);
+ if (es_fputc ( ' ', statusfp ) != ' ')
+ goto bad;
buf = gpgrt_vbsprintf (format, arg_ptr);
if (!buf)
- log_error ("error printing status line: %s\n",
- gpg_strerror (gpg_err_code_from_syserror ()));
+ {
+ log_error ("error printing status line: %s\n",
+ gpg_strerror (gpg_err_code_from_syserror ()));
+ goto bad;
+ }
else
{
- if (strpbrk (buf, "\r\n"))
+ if (strpbrk (buf, "\r\n\\"))
{
const byte *s;
- for (s=buf; *s; s++)
+ for (s=buf; *s && err != EOF; s++)
{
if (*s == '\n')
- es_fputs ("\\n", statusfp);
+ err = es_fputs ("\\n", statusfp);
else if (*s == '\r')
- es_fputs ("\\r", statusfp);
+ err = es_fputs ("\\r", statusfp);
+ else if (*s == '\\')
+ err = es_fputs ("\\\\", statusfp);
else
- es_fputc (*s, statusfp);
+ err = es_fputc (*s, statusfp);
}
}
- else
- es_fputs (buf, statusfp);
+ else if (es_fputs (buf, statusfp))
+ goto bad;
gpgrt_free (buf);
}
-
- va_end (arg_ptr);
}
es_putc ('\n', statusfp);
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+bad:
+ check_status_error();
+ va_end (arg_ptr);
}


@@ -288,10 +325,12 @@ write_status_error (const char *where, gpg_error_t err)
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */

+ if (!where || strpbrk(where, "\r\n"))
+ BUG();
+
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, err);
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+ check_status_error();
}


@@ -302,10 +341,12 @@ write_status_errcode (const char *where, int errcode)
if (!statusfp || !status_currently_allowed (STATUS_ERROR))
return; /* Not enabled or allowed. */

+ if (!where || strpbrk(where, "\r\n"))
+ BUG();
+
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_ERROR), where, gpg_err_code (errcode));
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+ check_status_error();
}


@@ -315,6 +356,9 @@ write_status_failure (const char *where, gpg_error_t err)
{
static int any_failure_printed;

+ if (!where || strpbrk(where, "\r\n"))
+ BUG();
+
if (!statusfp || !status_currently_allowed (STATUS_FAILURE))
return; /* Not enabled or allowed. */
if (any_failure_printed)
@@ -322,8 +366,7 @@ write_status_failure (const char *where, gpg_error_t err)
any_failure_printed = 1;
es_fprintf (statusfp, "[GNUPG:] %s %s %u\n",
get_status_string (STATUS_FAILURE), where, err);
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+ check_status_error();
}


@@ -342,14 +385,19 @@ write_status_text_and_buffer (int no, const char *string,
int lower_limit = ' ';
size_t n, count, dowrap;

- if (!statusfp || !status_currently_allowed (no))
- return; /* Not enabled or allowed. */
+ if (string && strpbrk(string, "\r\n"))
+ BUG();

if (wrap == -1)
{
lower_limit--;
wrap = 0;
}
+ else if (wrap < 0)
+ BUG();
+
+ if (!statusfp || !status_currently_allowed (no) )
+ return; /* Not enabled or allowed. */

text = get_status_string (no);
count = dowrap = first = 1;
@@ -357,16 +405,20 @@ write_status_text_and_buffer (int no, const char *string,
{
if (dowrap)
{
- es_fprintf (statusfp, "[GNUPG:] %s ", text);
+ if (es_fprintf (statusfp, "[GNUPG:] %s ", text) != strlen(text) + 10)
+ goto bad;
count = dowrap = 0;
if (first && string)
{
- es_fputs (string, statusfp);
- count += strlen (string);
+ size_t size = strlen (string);
+ if (es_fwrite (string, 1, size, statusfp) != size)
+ goto bad;
+ count += size;
/* Make sure that there is a space after the string. */
- if (*string && string[strlen (string)-1] != ' ')
+ if (*string && string[size-1] != ' ')
{
- es_putc (' ', statusfp);
+ if (es_putc (' ', statusfp) != ' ')
+ goto bad;
count++;
}
}
@@ -375,33 +427,38 @@ write_status_text_and_buffer (int no, const char *string,
for (esc=0, s=buffer, n=len; n; s++, n--)
{
if (*s == '%' || *(const byte*)s <= lower_limit
- || *(const byte*)s == 127 )
+ || *(const byte*)s >= 127 )
esc = 1;
if (wrap && ++count > wrap)
dowrap=1;
if (esc || dowrap)
break;
}
- if (s != buffer)
- es_fwrite (buffer, s-buffer, 1, statusfp);
+ log_assert(s >= buffer);
+ if (s != buffer && es_fwrite (buffer, 1, s-buffer, statusfp) != s-buffer)
+ goto bad;
if ( esc )
{
- es_fprintf (statusfp, "%%%02X", *(const byte*)s );
+ if (es_fprintf (statusfp, "%%%02X", *(const byte*)s ) != 4)
+ goto bad;
s++; n--;
}
buffer = s;
len = n;
if (dowrap && len)
- es_putc ('\n', statusfp);
+ {
+ es_putc ('\n', statusfp);
+ if (check_status_error())
+ return;
+ }
}
while (len);

es_putc ('\n',statusfp);
- if (es_fflush (statusfp) && opt.exit_on_status_write_error)
- g10_exit (0);
+bad:
+ check_status_error();
}

-
void
write_status_buffer (int no, const char *buffer, size_t len, int wrap)
{
@@ -703,3 +760,14 @@ cpr_get_answer_okay_cancel (const char *keyword,
}
}
}
+
+int
+check_io_error (void)
+{
+ return 0;
+ opt.exit_on_status_write_error = 0;
+ if (es_fflush(es_stdout) || es_fflush(es_stderr) ||
+ check_status_error() || es_ferror(es_stdin) ||
+ es_ferror(es_stdout) || es_ferror(es_stderr))
+ log_error("I/O error");
+}
diff --git a/g10/gpg.c b/g10/gpg.c
index b9a81510fc967eac557a9fceefbdcef481c11f3b..1982b39cc85a91dc867514b393bc0eb5139a4d09 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -5365,6 +5365,9 @@ main (int argc, char **argv)
release_armor_context (afx);
FREE_STRLIST(remusr);
FREE_STRLIST(locusr);
+
+ check_io_error();
+
g10_exit(0);
return 8; /*NEVER REACHED*/
}
diff --git a/g10/gpgv.c b/g10/gpgv.c
index ceded4af9e62f59acd970feb2d349aa47a4c352d..9d4f7b55ef07b05d2334afc987ed63848b83424a 100644
--- a/g10/gpgv.c
+++ b/g10/gpgv.c
@@ -281,6 +281,8 @@ main( int argc, char **argv )
keydb_release (ctrl->cached_getkey_kdb);
xfree (ctrl);

+ check_io_error();
+
/* cleanup */
g10_exit (0);
return 8; /*NOTREACHED*/
diff --git a/g10/main.h b/g10/main.h
index 62d2651be502a2fec710ac682fe5fc77cb47cf99..c08a839ff7c131a4e4e05e421390d4381c20bc65 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -216,6 +216,7 @@ void write_status_text_and_buffer ( int no, const char *text,
const char *buffer, size_t len, int wrap );

void write_status_begin_signing (gcry_md_hd_t md);
+int check_io_error (void);


int cpr_enabled(void);
--
2.39.1