diff mbox series

[v2,1/4] display: ensure qxl log_buf is a nul terminated string

Message ID 20190118173103.4903-2-berrange@redhat.com
State New
Headers show
Series trace: make systemtap easier to use for simple logging | expand

Commit Message

Daniel P. Berrangé Jan. 18, 2019, 5:31 p.m. UTC
The QXL_IO_LOG command allows the guest to send log messages to the host
via a buffer in the QXLRam struct. QEMU prints these to the console if
the qxl 'guestdebug' option is set to non-zero. It will also feed them
to the trace subsystem if any backends are built-in.

In both cases the log_buf data will get treated as being as a nul
terminated string, by the printf '%s' format specifier and / or other
code reading the buffer.

QEMU does nothing to guarantee that the log_buf really is nul terminated,
so there is potential for out of bounds array access.

This would affect any QEMU which has the log, syslog or ftrace trace
backends built into QEMU. It can only be triggered if the 'qxl_io_log'
trace event is enabled, however, so they are not vulnerable without
specific administrative action to enable this.

It would also affect QEMU if the 'guestdebug' parameter is set to a
non-zero value, which again is not the default and requires explicit
admin opt-in.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 hw/display/qxl.c        | 3 ++-
 hw/display/trace-events | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

Comments

Eric Blake Jan. 18, 2019, 5:40 p.m. UTC | #1
On 1/18/19 11:31 AM, Daniel P. Berrangé wrote:
> The QXL_IO_LOG command allows the guest to send log messages to the host
> via a buffer in the QXLRam struct. QEMU prints these to the console if
> the qxl 'guestdebug' option is set to non-zero. It will also feed them
> to the trace subsystem if any backends are built-in.
> 
> In both cases the log_buf data will get treated as being as a nul
> terminated string, by the printf '%s' format specifier and / or other
> code reading the buffer.
> 
> QEMU does nothing to guarantee that the log_buf really is nul terminated,
> so there is potential for out of bounds array access.
> 
> This would affect any QEMU which has the log, syslog or ftrace trace
> backends built into QEMU. It can only be triggered if the 'qxl_io_log'
> trace event is enabled, however, so they are not vulnerable without
> specific administrative action to enable this.
> 
> It would also affect QEMU if the 'guestdebug' parameter is set to a
> non-zero value, which again is not the default and requires explicit
> admin opt-in.
> 
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  hw/display/qxl.c        | 3 ++-
>  hw/display/trace-events | 2 +-
>  2 files changed, 3 insertions(+), 2 deletions(-)

Reviewed-by: Eric Blake <eblake@redhat.com>

> +++ b/hw/display/qxl.c
> @@ -1763,7 +1763,8 @@ async_common:
>          qxl_set_mode(d, val, 0);
>          break;
>      case QXL_IO_LOG:
> -        trace_qxl_io_log(d->id, d->ram->log_buf);
> +        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';

May lose a character from the log, but the improved safety is worth it.

> +        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);
>          if (d->guestdebug) {
>              fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id,
>                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), d->ram->log_buf);
> diff --git a/hw/display/trace-events b/hw/display/trace-events
> index 5a48c6cb6a..387c6b8931 100644
> --- a/hw/display/trace-events
> +++ b/hw/display/trace-events
> @@ -72,7 +72,7 @@ qxl_interface_update_area_complete_rest(int qid, uint32_t num_updated_rects) "%d
>  qxl_interface_update_area_complete_overflow(int qid, int max) "%d max=%d"
>  qxl_interface_update_area_complete_schedule_bh(int qid, uint32_t num_dirty) "%d #dirty=%d"
>  qxl_io_destroy_primary_ignored(int qid, const char *mode) "%d %s"
> -qxl_io_log(int qid, const uint8_t *log_buf) "%d %s"
> +qxl_io_log(int qid, const char *log_buf) "%d %s"
>  qxl_io_read_unexpected(int qid) "%d"
>  qxl_io_unexpected_vga_mode(int qid, uint64_t addr, uint64_t val, const char *desc) "%d 0x%"PRIx64"=%"PRIu64" (%s)"
>  qxl_io_write(int qid, const char *mode, uint64_t addr, const char *aname, uint64_t val, unsigned size, int async) "%d %s addr=%"PRIu64 " (%s) val=%"PRIu64" size=%u async=%d"
>
Daniel P. Berrangé Jan. 18, 2019, 5:42 p.m. UTC | #2
On Fri, Jan 18, 2019 at 11:40:29AM -0600, Eric Blake wrote:
> On 1/18/19 11:31 AM, Daniel P. Berrangé wrote:
> > The QXL_IO_LOG command allows the guest to send log messages to the host
> > via a buffer in the QXLRam struct. QEMU prints these to the console if
> > the qxl 'guestdebug' option is set to non-zero. It will also feed them
> > to the trace subsystem if any backends are built-in.
> > 
> > In both cases the log_buf data will get treated as being as a nul
> > terminated string, by the printf '%s' format specifier and / or other
> > code reading the buffer.
> > 
> > QEMU does nothing to guarantee that the log_buf really is nul terminated,
> > so there is potential for out of bounds array access.
> > 
> > This would affect any QEMU which has the log, syslog or ftrace trace
> > backends built into QEMU. It can only be triggered if the 'qxl_io_log'
> > trace event is enabled, however, so they are not vulnerable without
> > specific administrative action to enable this.
> > 
> > It would also affect QEMU if the 'guestdebug' parameter is set to a
> > non-zero value, which again is not the default and requires explicit
> > admin opt-in.
> > 
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  hw/display/qxl.c        | 3 ++-
> >  hw/display/trace-events | 2 +-
> >  2 files changed, 3 insertions(+), 2 deletions(-)
> 
> Reviewed-by: Eric Blake <eblake@redhat.com>
> 
> > +++ b/hw/display/qxl.c
> > @@ -1763,7 +1763,8 @@ async_common:
> >          qxl_set_mode(d, val, 0);
> >          break;
> >      case QXL_IO_LOG:
> > -        trace_qxl_io_log(d->id, d->ram->log_buf);
> > +        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';
> 
> May lose a character from the log, but the improved safety is worth it.

In practice it won't.  The Xorg QXL driver and Windows XDDM DOD driver
don't use this feature at all. The Windows QXL miniport driver uses snprintf
when populating the buffer so will already have truncated if it was going
to overflow.

> 
> > +        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);
> >          if (d->guestdebug) {
> >              fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id,
> >                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), d->ram->log_buf);
> > diff --git a/hw/display/trace-events b/hw/display/trace-events
> > index 5a48c6cb6a..387c6b8931 100644
> > --- a/hw/display/trace-events
> > +++ b/hw/display/trace-events
> > @@ -72,7 +72,7 @@ qxl_interface_update_area_complete_rest(int qid, uint32_t num_updated_rects) "%d
> >  qxl_interface_update_area_complete_overflow(int qid, int max) "%d max=%d"
> >  qxl_interface_update_area_complete_schedule_bh(int qid, uint32_t num_dirty) "%d #dirty=%d"
> >  qxl_io_destroy_primary_ignored(int qid, const char *mode) "%d %s"
> > -qxl_io_log(int qid, const uint8_t *log_buf) "%d %s"
> > +qxl_io_log(int qid, const char *log_buf) "%d %s"
> >  qxl_io_read_unexpected(int qid) "%d"
> >  qxl_io_unexpected_vga_mode(int qid, uint64_t addr, uint64_t val, const char *desc) "%d 0x%"PRIx64"=%"PRIu64" (%s)"
> >  qxl_io_write(int qid, const char *mode, uint64_t addr, const char *aname, uint64_t val, unsigned size, int async) "%d %s addr=%"PRIu64 " (%s) val=%"PRIu64" size=%u async=%d"
> > 
> 
> -- 
> Eric Blake, Principal Software Engineer
> Red Hat, Inc.           +1-919-301-3226
> Virtualization:  qemu.org | libvirt.org
> 




Regards,
Daniel
Gerd Hoffmann Jan. 21, 2019, 7:14 a.m. UTC | #3
>      case QXL_IO_LOG:
> -        trace_qxl_io_log(d->id, d->ram->log_buf);
> +        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';
> +        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);
>          if (d->guestdebug) {

Reviewed-by: Gerd Hoffmann <kraxel@redhat.com>
Stefan Hajnoczi Jan. 21, 2019, 10:45 a.m. UTC | #4
On Fri, Jan 18, 2019 at 05:31:00PM +0000, Daniel P. Berrangé wrote:
> diff --git a/hw/display/qxl.c b/hw/display/qxl.c
> index 8e9a65e75b..eefdf4baac 100644
> --- a/hw/display/qxl.c
> +++ b/hw/display/qxl.c
> @@ -1763,7 +1763,8 @@ async_common:
>          qxl_set_mode(d, val, 0);
>          break;
>      case QXL_IO_LOG:
> -        trace_qxl_io_log(d->id, d->ram->log_buf);
> +        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';
> +        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);

This is a PCI BAR shared with the guest?  Then NUL termination is
subject to races with vcpu threads that modify log_buf[] while we access
it.

The safe way to do this is to copy in log_buf[] and then NUL-terminate
the local copy.
Daniel P. Berrangé Jan. 22, 2019, 2:17 p.m. UTC | #5
On Mon, Jan 21, 2019 at 10:45:45AM +0000, Stefan Hajnoczi wrote:
> On Fri, Jan 18, 2019 at 05:31:00PM +0000, Daniel P. Berrangé wrote:
> > diff --git a/hw/display/qxl.c b/hw/display/qxl.c
> > index 8e9a65e75b..eefdf4baac 100644
> > --- a/hw/display/qxl.c
> > +++ b/hw/display/qxl.c
> > @@ -1763,7 +1763,8 @@ async_common:
> >          qxl_set_mode(d, val, 0);
> >          break;
> >      case QXL_IO_LOG:
> > -        trace_qxl_io_log(d->id, d->ram->log_buf);
> > +        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';
> > +        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);
> 
> This is a PCI BAR shared with the guest?  Then NUL termination is
> subject to races with vcpu threads that modify log_buf[] while we access
> it.

Doh, yes, it is racy.

> The safe way to do this is to copy in log_buf[] and then NUL-terminate
> the local copy.

log_buf is 4k in size, which I don't really want to allocate on the
stack. Using malloc would impose a perf penalty on logging but not
sure if that's significant enough to worry about. Alternatively we
could just drop the tracepoint, given that you can already use the
'guestdebug' option to get these fprintf'd to stderr.

Regards,
Daniel
diff mbox series

Patch

diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 8e9a65e75b..eefdf4baac 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -1763,7 +1763,8 @@  async_common:
         qxl_set_mode(d, val, 0);
         break;
     case QXL_IO_LOG:
-        trace_qxl_io_log(d->id, d->ram->log_buf);
+        d->ram->log_buf[sizeof(d->ram->log_buf) - 1] = '\0';
+        trace_qxl_io_log(d->id, (const char *)d->ram->log_buf);
         if (d->guestdebug) {
             fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id,
                     qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), d->ram->log_buf);
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 5a48c6cb6a..387c6b8931 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -72,7 +72,7 @@  qxl_interface_update_area_complete_rest(int qid, uint32_t num_updated_rects) "%d
 qxl_interface_update_area_complete_overflow(int qid, int max) "%d max=%d"
 qxl_interface_update_area_complete_schedule_bh(int qid, uint32_t num_dirty) "%d #dirty=%d"
 qxl_io_destroy_primary_ignored(int qid, const char *mode) "%d %s"
-qxl_io_log(int qid, const uint8_t *log_buf) "%d %s"
+qxl_io_log(int qid, const char *log_buf) "%d %s"
 qxl_io_read_unexpected(int qid) "%d"
 qxl_io_unexpected_vga_mode(int qid, uint64_t addr, uint64_t val, const char *desc) "%d 0x%"PRIx64"=%"PRIu64" (%s)"
 qxl_io_write(int qid, const char *mode, uint64_t addr, const char *aname, uint64_t val, unsigned size, int async) "%d %s addr=%"PRIu64 " (%s) val=%"PRIu64" size=%u async=%d"