|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v2 1/4] xen/console: Don't treat NUL character as the end of the buffer
On Mon, 5 Aug 2019, Julien Grall wrote:
> After upgrading Debian to Buster, I have began to notice console
> mangling when using zsh in Dom0. This is happenning because output sent by
> zsh to the console may contain NULs in the middle of the buffer.
>
> The actual implementation of CONSOLEIO_write considers that a buffer
> always terminate with a NUL and therefore will ignore anything after it.
>
> In general, NULs are perfectly legitimate in terminal streams. For
> instance, this could be used for padding slow terminals. See terminfo(5)
> section `Delays and Padding`, or search for the pcre '\bpad'.
>
> Other use cases includes using the console for dumping non-human
> readable information (e.g debugger, file if no network...). With the
> current behavior, the resulting stream will end up to be corrupted.
>
> The documentation for CONSOLEIO_write is pretty limited (to not say
> inexistent). From the declaration, the hypercall takes a buffer and size.
> So this could lead to think the NUL character is allowed in the middle of
> the buffer.
>
> This patch updates the console API to pass the size along the buffer
> down so we can remove the reliance on buffer terminating by a NUL
> character.
>
> Signed-off-by: Julien Grall <julien.grall@xxxxxxx>
>
> ---
>
> This patch was originally sent standalone [1]. But the series grows to
> include another bug found in the console code and documentation.
>
> Cnhanges in v2:
> - Switch from unsigned int to size_t. So truncation is avoided. We
> can decide whether we want explicit truncation later on.
> - Remove unecessary leading NUL added in dump_console_ring_key
> - Remove unecessary decoration in sercon_puts
> - Fix loop in lfb_scroll_puts
> - use while() rather than do {} while()
>
> Change since the standalone version:
> - Fix early printk on Arm
> - Fix gdbstub
> - Remove unecessary leading NUL character added by Xen
> - Handle DomU console
> - Rework the commit message
>
> Below a small C program to repro the bug on Xen:
>
> int main(void)
> {
> write(1,
>
> "\r\33[0m\0\0\0\0\0\0\0\0\33[27m\33[24m\33[j\33[32mjulien\33[31m@\33[00m\33[36mjuno2-julieng:~\33[37m>",
> 75);
> write(1,
>
> "\33[K\33[32C\33[01;33m--juno2-julieng-13:44--\33[00m\33[37m\33[55D",
> 54);
> write(1, "\33[?2004h", 8);
>
> return 0;
> }
>
> Without this patch, the only --juno2-julieng-13:44-- will be printed in
> yellow.
>
> This patch was tested on Arm using serial console. I am not entirely
> sure whether the video and PV console is correct. I would appreciate help
> for testing here.
>
> [1] https://lists.xenproject.org/archives/html/xen-devel/2019-02/msg01932.html
Acked-by: Stefano Stabellini <sstabellini@xxxxxxxxxx>
> ---
> xen/arch/arm/early_printk.c | 5 ++--
> xen/common/gdbstub.c | 6 ++--
> xen/drivers/char/console.c | 59
> +++++++++++++++++++--------------------
> xen/drivers/char/consoled.c | 7 ++---
> xen/drivers/char/serial.c | 7 +++--
> xen/drivers/char/xen_pv_console.c | 10 +++----
> xen/drivers/video/lfb.c | 14 ++++++----
> xen/drivers/video/lfb.h | 4 +--
> xen/drivers/video/vga.c | 14 +++++-----
> xen/include/xen/console.h | 2 +-
> xen/include/xen/early_printk.h | 2 +-
> xen/include/xen/pv_console.h | 4 +--
> xen/include/xen/serial.h | 4 +--
> xen/include/xen/video.h | 4 +--
> 14 files changed, 71 insertions(+), 71 deletions(-)
>
> diff --git a/xen/arch/arm/early_printk.c b/xen/arch/arm/early_printk.c
> index 97466a12b1..333073d97e 100644
> --- a/xen/arch/arm/early_printk.c
> +++ b/xen/arch/arm/early_printk.c
> @@ -17,9 +17,10 @@
> void early_putch(char c);
> void early_flush(void);
>
> -void early_puts(const char *s)
> +void early_puts(const char *s, size_t nr)
> {
> - while (*s != '\0') {
> + while ( nr-- > 0 )
> + {
> if (*s == '\n')
> early_putch('\r');
> early_putch(*s);
> diff --git a/xen/common/gdbstub.c b/xen/common/gdbstub.c
> index 07095e1ec7..6234834a20 100644
> --- a/xen/common/gdbstub.c
> +++ b/xen/common/gdbstub.c
> @@ -68,7 +68,7 @@ static void gdb_smp_resume(void);
> static char __initdata opt_gdb[30];
> string_param("gdb", opt_gdb);
>
> -static void gdbstub_console_puts(const char *str);
> +static void gdbstub_console_puts(const char *str, size_t nr);
>
> /* value <-> char (de)serialzers */
> static char
> @@ -546,14 +546,14 @@ __gdb_ctx = {
> static struct gdb_context *gdb_ctx = &__gdb_ctx;
>
> static void
> -gdbstub_console_puts(const char *str)
> +gdbstub_console_puts(const char *str, size_t nr)
> {
> const char *p;
>
> gdb_start_packet(gdb_ctx);
> gdb_write_to_packet_char('O', gdb_ctx);
>
> - for ( p = str; *p != '\0'; p++ )
> + for ( p = str; nr > 0; p++, nr-- )
> {
> gdb_write_to_packet_char(hex2char((*p>>4) & 0x0f), gdb_ctx );
> gdb_write_to_packet_char(hex2char((*p) & 0x0f), gdb_ctx );
> diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
> index d728e737d1..752a11f6c5 100644
> --- a/xen/drivers/char/console.c
> +++ b/xen/drivers/char/console.c
> @@ -326,9 +326,9 @@ long read_console_ring(struct xen_sysctl_readconsole *op)
> static char serial_rx_ring[SERIAL_RX_SIZE];
> static unsigned int serial_rx_cons, serial_rx_prod;
>
> -static void (*serial_steal_fn)(const char *) = early_puts;
> +static void (*serial_steal_fn)(const char *, size_t nr) = early_puts;
>
> -int console_steal(int handle, void (*fn)(const char *))
> +int console_steal(int handle, void (*fn)(const char *, size_t nr))
> {
> if ( (handle == -1) || (handle != sercon_handle) )
> return 0;
> @@ -346,15 +346,15 @@ void console_giveback(int id)
> serial_steal_fn = NULL;
> }
>
> -static void sercon_puts(const char *s)
> +static void sercon_puts(const char *s, size_t nr)
> {
> if ( serial_steal_fn != NULL )
> - (*serial_steal_fn)(s);
> + serial_steal_fn(s, nr);
> else
> - serial_puts(sercon_handle, s);
> + serial_puts(sercon_handle, s, nr);
>
> /* Copy all serial output into PV console */
> - pv_console_puts(s);
> + pv_console_puts(s, nr);
> }
>
> static void dump_console_ring_key(unsigned char key)
> @@ -387,10 +387,9 @@ static void dump_console_ring_key(unsigned char key)
> sofar += len;
> c += len;
> }
> - buf[sofar] = '\0';
>
> - sercon_puts(buf);
> - video_puts(buf);
> + sercon_puts(buf, sofar);
> + video_puts(buf, sofar);
>
> free_xenheap_pages(buf, order);
> }
> @@ -528,7 +527,7 @@ static inline void xen_console_write_debug_port(const
> char *buf, size_t len)
> static long guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer, int
> count)
> {
> char kbuf[128];
> - int kcount = 0;
> + unsigned int kcount = 0;
> struct domain *cd = current->domain;
>
> while ( count > 0 )
> @@ -541,25 +540,22 @@ static long
> guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer, int count)
> kcount = min_t(int, count, sizeof(kbuf)-1);
> if ( copy_from_guest(kbuf, buffer, kcount) )
> return -EFAULT;
> - kbuf[kcount] = '\0';
>
> if ( is_hardware_domain(cd) )
> {
> /* Use direct console output as it could be interactive */
> spin_lock_irq(&console_lock);
>
> - sercon_puts(kbuf);
> - video_puts(kbuf);
> + sercon_puts(kbuf, kcount);
> + video_puts(kbuf, kcount);
>
> #ifdef CONFIG_X86
> if ( opt_console_xen )
> {
> - size_t len = strlen(kbuf);
> -
> if ( xen_guest )
> - xen_hypercall_console_write(kbuf, len);
> + xen_hypercall_console_write(kbuf, kcount);
> else
> - xen_console_write_debug_port(kbuf, len);
> + xen_console_write_debug_port(kbuf, kcount);
> }
> #endif
>
> @@ -576,19 +572,20 @@ static long
> guest_console_write(XEN_GUEST_HANDLE_PARAM(char) buffer, int count)
> char *kin = kbuf, *kout = kbuf, c;
>
> /* Strip non-printable characters */
> - for ( ; ; )
> + do
> {
> c = *kin++;
> - if ( c == '\0' || c == '\n' )
> + if ( c == '\n' )
> break;
> if ( isprint(c) || c == '\t' )
> *kout++ = c;
> - }
> + } while ( --kcount > 0 );
> +
> *kout = '\0';
> spin_lock(&cd->pbuf_lock);
> + kcount = kin - kbuf;
> if ( c == '\n' )
> {
> - kcount = kin - kbuf;
> cd->pbuf[cd->pbuf_idx] = '\0';
> guest_printk(cd, XENLOG_G_DEBUG "%s%s\n", cd->pbuf, kbuf);
> cd->pbuf_idx = 0;
> @@ -667,16 +664,16 @@ static bool_t console_locks_busted;
>
> static void __putstr(const char *str)
> {
> + size_t len = strlen(str);
> +
> ASSERT(spin_is_locked(&console_lock));
>
> - sercon_puts(str);
> - video_puts(str);
> + sercon_puts(str, len);
> + video_puts(str, len);
>
> #ifdef CONFIG_X86
> if ( opt_console_xen )
> {
> - size_t len = strlen(str);
> -
> if ( xen_guest )
> xen_hypercall_console_write(str, len);
> else
> @@ -1250,6 +1247,7 @@ void debugtrace_printk(const char *fmt, ...)
> char cntbuf[24];
> va_list args;
> unsigned long flags;
> + unsigned int nr;
>
> if ( debugtrace_bytes == 0 )
> return;
> @@ -1261,14 +1259,15 @@ void debugtrace_printk(const char *fmt, ...)
> ASSERT(debugtrace_buf[debugtrace_bytes - 1] == 0);
>
> va_start(args, fmt);
> - vsnprintf(buf, sizeof(buf), fmt, args);
> + nr = vscnprintf(buf, sizeof(buf), fmt, args);
> va_end(args);
>
> if ( debugtrace_send_to_console )
> {
> - snprintf(cntbuf, sizeof(cntbuf), "%u ", ++count);
> - serial_puts(sercon_handle, cntbuf);
> - serial_puts(sercon_handle, buf);
> + unsigned int n = snprintf(cntbuf, sizeof(cntbuf), "%u ", ++count);
> +
> + serial_puts(sercon_handle, cntbuf, n);
> + serial_puts(sercon_handle, buf, nr);
> }
> else
> {
> @@ -1381,7 +1380,7 @@ void panic(const char *fmt, ...)
> * **************************************************************
> */
>
> -static void suspend_steal_fn(const char *str) { }
> +static void suspend_steal_fn(const char *str, size_t nr) { }
> static int suspend_steal_id;
>
> int console_suspend(void)
> diff --git a/xen/drivers/char/consoled.c b/xen/drivers/char/consoled.c
> index 552abf5766..222e018442 100644
> --- a/xen/drivers/char/consoled.c
> +++ b/xen/drivers/char/consoled.c
> @@ -77,16 +77,13 @@ size_t consoled_guest_rx(void)
>
> if ( idx >= BUF_SZ )
> {
> - pv_console_puts(buf);
> + pv_console_puts(buf, BUF_SZ);
> idx = 0;
> }
> }
>
> if ( idx )
> - {
> - buf[idx] = '\0';
> - pv_console_puts(buf);
> - }
> + pv_console_puts(buf, idx);
>
> /* No need for a mem barrier because every character was already
> consumed */
> barrier();
> diff --git a/xen/drivers/char/serial.c b/xen/drivers/char/serial.c
> index 221a14c092..88cd876790 100644
> --- a/xen/drivers/char/serial.c
> +++ b/xen/drivers/char/serial.c
> @@ -223,11 +223,10 @@ void serial_putc(int handle, char c)
> spin_unlock_irqrestore(&port->tx_lock, flags);
> }
>
> -void serial_puts(int handle, const char *s)
> +void serial_puts(int handle, const char *s, size_t nr)
> {
> struct serial_port *port;
> unsigned long flags;
> - char c;
>
> if ( handle == -1 )
> return;
> @@ -238,8 +237,10 @@ void serial_puts(int handle, const char *s)
>
> spin_lock_irqsave(&port->tx_lock, flags);
>
> - while ( (c = *s++) != '\0' )
> + for ( ; nr > 0; nr--, s++ )
> {
> + char c = *s;
> +
> if ( (c == '\n') && (handle & SERHND_COOKED) )
> __serial_putc(port, '\r' | ((handle & SERHND_HI) ? 0x80 : 0x00));
>
> diff --git a/xen/drivers/char/xen_pv_console.c
> b/xen/drivers/char/xen_pv_console.c
> index cc1c1d743f..612784b074 100644
> --- a/xen/drivers/char/xen_pv_console.c
> +++ b/xen/drivers/char/xen_pv_console.c
> @@ -129,13 +129,13 @@ size_t pv_console_rx(struct cpu_user_regs *regs)
> return recv;
> }
>
> -static size_t pv_ring_puts(const char *buf)
> +static size_t pv_ring_puts(const char *buf, size_t nr)
> {
> XENCONS_RING_IDX cons, prod;
> size_t sent = 0, avail;
> bool put_r = false;
>
> - while ( buf[sent] != '\0' || put_r )
> + while ( sent < nr || put_r )
> {
> cons = ACCESS_ONCE(cons_ring->out_cons);
> prod = cons_ring->out_prod;
> @@ -156,7 +156,7 @@ static size_t pv_ring_puts(const char *buf)
> continue;
> }
>
> - while ( avail && (buf[sent] != '\0' || put_r) )
> + while ( avail && (sent < nr || put_r) )
> {
> if ( put_r )
> {
> @@ -185,7 +185,7 @@ static size_t pv_ring_puts(const char *buf)
> return sent;
> }
>
> -void pv_console_puts(const char *buf)
> +void pv_console_puts(const char *buf, size_t nr)
> {
> unsigned long flags;
>
> @@ -193,7 +193,7 @@ void pv_console_puts(const char *buf)
> return;
>
> spin_lock_irqsave(&tx_lock, flags);
> - pv_ring_puts(buf);
> + pv_ring_puts(buf, nr);
> spin_unlock_irqrestore(&tx_lock, flags);
> }
>
> diff --git a/xen/drivers/video/lfb.c b/xen/drivers/video/lfb.c
> index 5022195ae5..75b749b330 100644
> --- a/xen/drivers/video/lfb.c
> +++ b/xen/drivers/video/lfb.c
> @@ -53,14 +53,15 @@ static void lfb_show_line(
> }
>
> /* Fast mode which redraws all modified parts of a 2D text buffer. */
> -void lfb_redraw_puts(const char *s)
> +void lfb_redraw_puts(const char *s, size_t nr)
> {
> unsigned int i, min_redraw_y = lfb.ypos;
> - char c;
>
> /* Paste characters into text buffer. */
> - while ( (c = *s++) != '\0' )
> + for ( ; nr > 0; nr--, s++ )
> {
> + char c = *s;
> +
> if ( (c == '\n') || (lfb.xpos >= lfb.lfbp.text_columns) )
> {
> if ( ++lfb.ypos >= lfb.lfbp.text_rows )
> @@ -97,13 +98,14 @@ void lfb_redraw_puts(const char *s)
> }
>
> /* Slower line-based scroll mode which interacts better with dom0. */
> -void lfb_scroll_puts(const char *s)
> +void lfb_scroll_puts(const char *s, size_t nr)
> {
> unsigned int i;
> - char c;
>
> - while ( (c = *s++) != '\0' )
> + for ( ; nr > 0; nr--, s++ )
> {
> + char c = *s;
> +
> if ( (c == '\n') || (lfb.xpos >= lfb.lfbp.text_columns) )
> {
> unsigned int bytes = (lfb.lfbp.width *
> diff --git a/xen/drivers/video/lfb.h b/xen/drivers/video/lfb.h
> index ac40a66379..e743ccdd6b 100644
> --- a/xen/drivers/video/lfb.h
> +++ b/xen/drivers/video/lfb.h
> @@ -35,8 +35,8 @@ struct lfb_prop {
> unsigned int text_rows;
> };
>
> -void lfb_redraw_puts(const char *s);
> -void lfb_scroll_puts(const char *s);
> +void lfb_redraw_puts(const char *s, size_t nr);
> +void lfb_scroll_puts(const char *s, size_t nr);
> void lfb_carriage_return(void);
> void lfb_free(void);
>
> diff --git a/xen/drivers/video/vga.c b/xen/drivers/video/vga.c
> index 454457ade8..666f2e2509 100644
> --- a/xen/drivers/video/vga.c
> +++ b/xen/drivers/video/vga.c
> @@ -18,9 +18,9 @@ static int vgacon_keep;
> static unsigned int xpos, ypos;
> static unsigned char *video;
>
> -static void vga_text_puts(const char *s);
> -static void vga_noop_puts(const char *s) {}
> -void (*video_puts)(const char *) = vga_noop_puts;
> +static void vga_text_puts(const char *s, size_t nr);
> +static void vga_noop_puts(const char *s, size_t nr) {}
> +void (*video_puts)(const char *, size_t nr) = vga_noop_puts;
>
> /*
> * 'vga=<mode-specifier>[,keep]' where <mode-specifier> is one of:
> @@ -174,12 +174,12 @@ void __init video_endboot(void)
> }
> }
>
> -static void vga_text_puts(const char *s)
> +static void vga_text_puts(const char *s, size_t nr)
> {
> - char c;
> -
> - while ( (c = *s++) != '\0' )
> + for ( ; nr > 0; nr--, s++ )
> {
> + char c = *s;
> +
> if ( (c == '\n') || (xpos >= columns) )
> {
> if ( ++ypos >= lines )
> diff --git a/xen/include/xen/console.h b/xen/include/xen/console.h
> index b4f9463936..ba914f9e5b 100644
> --- a/xen/include/xen/console.h
> +++ b/xen/include/xen/console.h
> @@ -38,7 +38,7 @@ struct domain *console_input_domain(void);
> * Steal output from the console. Returns +ve identifier, else -ve error.
> * Takes the handle of the serial line to steal, and steal callback function.
> */
> -int console_steal(int handle, void (*fn)(const char *));
> +int console_steal(int handle, void (*fn)(const char *, size_t nr));
>
> /* Give back stolen console. Takes the identifier returned by console_steal.
> */
> void console_giveback(int id);
> diff --git a/xen/include/xen/early_printk.h b/xen/include/xen/early_printk.h
> index 2c3e1b3519..0f76c3a74f 100644
> --- a/xen/include/xen/early_printk.h
> +++ b/xen/include/xen/early_printk.h
> @@ -5,7 +5,7 @@
> #define __XEN_EARLY_PRINTK_H__
>
> #ifdef CONFIG_EARLY_PRINTK
> -void early_puts(const char *s);
> +void early_puts(const char *s, size_t nr);
> #else
> #define early_puts NULL
> #endif
> diff --git a/xen/include/xen/pv_console.h b/xen/include/xen/pv_console.h
> index cb92539666..4745f46f2d 100644
> --- a/xen/include/xen/pv_console.h
> +++ b/xen/include/xen/pv_console.h
> @@ -8,7 +8,7 @@
> void pv_console_init(void);
> void pv_console_set_rx_handler(serial_rx_fn fn);
> void pv_console_init_postirq(void);
> -void pv_console_puts(const char *buf);
> +void pv_console_puts(const char *buf, size_t nr);
> size_t pv_console_rx(struct cpu_user_regs *regs);
> evtchn_port_t pv_console_evtchn(void);
>
> @@ -17,7 +17,7 @@ evtchn_port_t pv_console_evtchn(void);
> static inline void pv_console_init(void) {}
> static inline void pv_console_set_rx_handler(serial_rx_fn fn) { }
> static inline void pv_console_init_postirq(void) { }
> -static inline void pv_console_puts(const char *buf) { }
> +static inline void pv_console_puts(const char *buf, size_t nr) { }
> static inline size_t pv_console_rx(struct cpu_user_regs *regs) { return 0; }
> evtchn_port_t pv_console_evtchn(void)
> {
> diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
> index f2994d4093..6548f0b0a9 100644
> --- a/xen/include/xen/serial.h
> +++ b/xen/include/xen/serial.h
> @@ -114,8 +114,8 @@ int serial_parse_handle(char *conf);
> /* Transmit a single character via the specified COM port. */
> void serial_putc(int handle, char c);
>
> -/* Transmit a NULL-terminated string via the specified COM port. */
> -void serial_puts(int handle, const char *s);
> +/* Transmit a string via the specified COM port. */
> +void serial_puts(int handle, const char *s, size_t nr);
>
> /*
> * An alternative to registering a character-receive hook. This function
> diff --git a/xen/include/xen/video.h b/xen/include/xen/video.h
> index 2e897f9df5..905f331112 100644
> --- a/xen/include/xen/video.h
> +++ b/xen/include/xen/video.h
> @@ -13,11 +13,11 @@
>
> #ifdef CONFIG_VIDEO
> void video_init(void);
> -extern void (*video_puts)(const char *);
> +extern void (*video_puts)(const char *, size_t nr);
> void video_endboot(void);
> #else
> #define video_init() ((void)0)
> -#define video_puts(s) ((void)0)
> +#define video_puts(s, nr) ((void)0)
> #define video_endboot() ((void)0)
> #endif
>
> --
> 2.11.0
>
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |