malicious screen output can make mosh-server assertion fail at terminal.cc line 122 #667

Closed
kcwu opened this Issue Aug 21, 2015 · 10 comments

Comments

Projects
None yet
6 participants
@kcwu

kcwu commented Aug 21, 2015

How to reproduce:
Inside mosh, run following code

echo -e '0\e[1J\xcc\xb4'

then mosh-server will crash

Assertion failed: (this_cell == combining_cell), function print, file terminal.cc, line 122.

from gdb, the call stack

#0  0x00000008020c83ca in thr_kill () from /lib/libc.so.7
#1  0x00000008020c8378 in raise () from /lib/libc.so.7
#2  0x00000008020c6b99 in abort () from /lib/libc.so.7
#3  0x00000008020a6fe1 in __assert () from /lib/libc.so.7
#4  0x000000000041e43e in Terminal::Emulator::print (this=0x7fffffffe4c0, act=0x802c1b140) at terminal.cc:122
#5  0x000000000044a9dd in Parser::Print::act_on_terminal (this=0x802c1b140, emu=0x7fffffffe4c0) at parseraction.cc:56
#6  0x000000000044cd55 in Terminal::Complete::act (this=0x7fffffffe4a8, str=...) at completeterminal.cc:56

this issue is found by afl-fuzz

@eminence

This comment has been minimized.

Show comment
Hide comment
@eminence

eminence Aug 21, 2015

Member

I've not been able to reproduce this crash on mosh 1.2.4, or 1.2.5.

What version of mosh are you running (both client and server)?

Member

eminence commented Aug 21, 2015

I've not been able to reproduce this crash on mosh 1.2.4, or 1.2.5.

What version of mosh are you running (both client and server)?

@andersk

This comment has been minimized.

Show comment
Hide comment
@andersk

andersk Aug 21, 2015

Member

Confirmed with 1.2.5.

termemu: terminal.cc:122: void Terminal::Emulator::print(const Parser::Print*): Assertion `this_cell == combining_cell' failed.

Program received signal SIGABRT, Aborted.
0x00007ffff6b45267 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:55
55  ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff6b45267 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:55
#1  0x00007ffff6b46eca in __GI_abort () at abort.c:89
#2  0x00007ffff6b3e03d in __assert_fail_base (
    fmt=0x7ffff6ca0028 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", 
    assertion=assertion@entry=0x555555589955 "this_cell == combining_cell", 
    file=file@entry=0x55555558a743 "terminal.cc", line=line@entry=122, 
    function=function@entry=0x5555555899a0 <Terminal::Emulator::print(Parser::Print const*)::__PRETTY_FUNCTION__> "void Terminal::Emulator::print(const Parser::Print*)") at assert.c:92
#3  0x00007ffff6b3e0f2 in __GI___assert_fail (
    assertion=assertion@entry=0x555555589955 "this_cell == combining_cell", 
    file=file@entry=0x55555558a743 "terminal.cc", line=line@entry=122, 
    function=function@entry=0x5555555899a0 <Terminal::Emulator::print(Parser::Print const*)::__PRETTY_FUNCTION__> "void Terminal::Emulator::print(const Parser::Print*)") at assert.c:101
#4  0x000055555556d658 in Terminal::Emulator::print (this=0x7fffffff9568, 
    act=0x5555557a8a20) at terminal.cc:122
#5  0x000055555556858c in Parser::Print::act_on_terminal (
    this=<optimized out>, emu=<optimized out>) at parseraction.cc:56
#6  0x00005555555837d3 in Terminal::Complete::act (this=0x7fffffff9550, 
    str=...) at completeterminal.cc:56
#7  0x000055555555bc51 in emulate_terminal (fd=<optimized out>)
    at termemu.cc:296
#8  0x000055555555dbc5 in main (argc=1, argv=<optimized out>)
    at termemu.cc:156
Member

andersk commented Aug 21, 2015

Confirmed with 1.2.5.

termemu: terminal.cc:122: void Terminal::Emulator::print(const Parser::Print*): Assertion `this_cell == combining_cell' failed.

Program received signal SIGABRT, Aborted.
0x00007ffff6b45267 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:55
55  ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff6b45267 in __GI_raise (sig=sig@entry=6)
    at ../sysdeps/unix/sysv/linux/raise.c:55
#1  0x00007ffff6b46eca in __GI_abort () at abort.c:89
#2  0x00007ffff6b3e03d in __assert_fail_base (
    fmt=0x7ffff6ca0028 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", 
    assertion=assertion@entry=0x555555589955 "this_cell == combining_cell", 
    file=file@entry=0x55555558a743 "terminal.cc", line=line@entry=122, 
    function=function@entry=0x5555555899a0 <Terminal::Emulator::print(Parser::Print const*)::__PRETTY_FUNCTION__> "void Terminal::Emulator::print(const Parser::Print*)") at assert.c:92
#3  0x00007ffff6b3e0f2 in __GI___assert_fail (
    assertion=assertion@entry=0x555555589955 "this_cell == combining_cell", 
    file=file@entry=0x55555558a743 "terminal.cc", line=line@entry=122, 
    function=function@entry=0x5555555899a0 <Terminal::Emulator::print(Parser::Print const*)::__PRETTY_FUNCTION__> "void Terminal::Emulator::print(const Parser::Print*)") at assert.c:101
#4  0x000055555556d658 in Terminal::Emulator::print (this=0x7fffffff9568, 
    act=0x5555557a8a20) at terminal.cc:122
#5  0x000055555556858c in Parser::Print::act_on_terminal (
    this=<optimized out>, emu=<optimized out>) at parseraction.cc:56
#6  0x00005555555837d3 in Terminal::Complete::act (this=0x7fffffff9550, 
    str=...) at completeterminal.cc:56
#7  0x000055555555bc51 in emulate_terminal (fd=<optimized out>)
    at termemu.cc:296
#8  0x000055555555dbc5 in main (argc=1, argv=<optimized out>)
    at termemu.cc:156
@cgull

This comment has been minimized.

Show comment
Hide comment
@cgull

cgull Aug 21, 2015

Member

I see it with printf '0\e[1J\xcc\xb4' on OS X and the 1.2.5 package distribution. Lunch & work now, more analysis later...

Member

cgull commented Aug 21, 2015

I see it with printf '0\e[1J\xcc\xb4' on OS X and the 1.2.5 package distribution. Lunch & work now, more analysis later...

@keithw

This comment has been minimized.

Show comment
Hide comment
@keithw

keithw Aug 22, 2015

Member

Thanks for fuzzing Mosh!

I think tentatively the solution is going to be to get rid of the failing assertion (https://github.com/mobile-shell/mosh/blob/master/src/terminal/terminal.cc#L122) -- it's obviously not true that the only time we need fallback processing (combining character without a base character to combine it with) is for the first cell of a line, since sequences like J and K can erase the combining cell.

Member

keithw commented Aug 22, 2015

Thanks for fuzzing Mosh!

I think tentatively the solution is going to be to get rid of the failing assertion (https://github.com/mobile-shell/mosh/blob/master/src/terminal/terminal.cc#L122) -- it's obviously not true that the only time we need fallback processing (combining character without a base character to combine it with) is for the first cell of a line, since sequences like J and K can erase the combining cell.

@andersk

This comment has been minimized.

Show comment
Hide comment
@andersk

andersk Aug 24, 2015

Member

I set up my own afl-fuzz run over Complete::act, Complete::get_fb, Display::new_frame with asan and hardening. It found three copies of this assertion failure within 20 minutes. After removing terminal.cc line 122, it ran overnight without finding anything (17 cycles, 67M execs). So the good news is, this is likely the only problem to be found so easily.

Member

andersk commented Aug 24, 2015

I set up my own afl-fuzz run over Complete::act, Complete::get_fb, Display::new_frame with asan and hardening. It found three copies of this assertion failure within 20 minutes. After removing terminal.cc line 122, it ran overnight without finding anything (17 cycles, 67M execs). So the good news is, this is likely the only problem to be found so easily.

@kcwu

This comment has been minimized.

Show comment
Hide comment
@kcwu

kcwu Aug 24, 2015

I have the same observation. No issues found after removed line 122. (27 cycles, 180M execs)

kcwu commented Aug 24, 2015

I have the same observation. No issues found after removed line 122. (27 cycles, 180M execs)

@keithw

This comment has been minimized.

Show comment
Hide comment
@keithw

keithw Aug 24, 2015

Member

I'd love if you fuzzed the network protocol too (maybe after removing encryption and compression to make it easier).

Member

keithw commented Aug 24, 2015

I'd love if you fuzzed the network protocol too (maybe after removing encryption and compression to make it easier).

@keithw keithw closed this in 7ec19a5 Aug 24, 2015

@cgull

This comment has been minimized.

Show comment
Hide comment
@cgull

cgull Oct 18, 2015

Member

@kcwu, @andersk: could either of you share your Mosh configs? It's not exactly obvious how to get afl-fuzz to run it usefully.

Member

cgull commented Oct 18, 2015

@kcwu, @andersk: could either of you share your Mosh configs? It's not exactly obvious how to get afl-fuzz to run it usefully.

@RichiH

This comment has been minimized.

Show comment
Hide comment
@RichiH

RichiH Apr 10, 2016

@kcwu, @andersk: Reminder-poke

RichiH commented Apr 10, 2016

@kcwu, @andersk: Reminder-poke

@kcwu

This comment has been minimized.

Show comment
Hide comment
@kcwu

kcwu Apr 11, 2016

This is simplified from src/examples/termemu.c

#include "config.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include <assert.h>
#include <wctype.h>
#include <iostream>
#include <typeinfo>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/time.h>
#include <exception>

#if HAVE_PTY_H
#include <pty.h>
#elif HAVE_UTIL_H
#include <util.h>
#elif HAVE_LIBUTIL_H
#include <libutil.h>
#endif

#include "parser.h"
#include "completeterminal.h"
#include "swrite.h"
#include "fatal_assert.h"
#include "pty_compat.h"
#include "locale_utils.h"
#include "select.h"

const size_t buf_size = 16384;

static void emulate_terminal();

int main( int argc, char *argv[] )
{
  set_native_locale();
  fatal_assert( is_utf8_locale() );

retry:
    try {
      emulate_terminal( );
    } catch ( const std::exception &e ) {
      fprintf( stderr, "\r\nException caught: %s\r\n", e.what() );
    }

    if (getenv("AFL_PERSISTENT")) {
        raise(SIGSTOP);
        goto retry;
    }

  return 0;
}

static void emulate_terminal()
{
  int col = 80;
  int row = 24;

  /* open parser and terminal */
  Terminal::Complete complete( col, row );
  Terminal::Framebuffer state( col, row );

  /* open display */
  Terminal::Display display( false );

  swrite( STDOUT_FILENO, display.open().c_str() );
  bool initialized = false;

  while ( 1 ) {
      /* input from user */
      char buf[ buf_size ];

      /* fill buffer if possible */
      ssize_t bytes_read = read( STDIN_FILENO, buf, buf_size );
      if ( bytes_read == 0 ) { /* EOF */
        return;
      } else if ( bytes_read < 0 ) {
        perror( "read" );
        return;
      }

      std::string terminal_to_host = complete.act( std::string( buf, bytes_read ) );

    Terminal::Framebuffer new_frame( complete.get_fb() );


    display.downgrade( new_frame );
    std::string update = display.new_frame( initialized, state, new_frame );
    state = new_frame;

    initialized = true;
  }

  std::string update = display.new_frame( true, state, complete.get_fb() );
  swrite( STDOUT_FILENO, update.c_str() );

  swrite( STDOUT_FILENO, display.close().c_str() );
}

kcwu commented Apr 11, 2016

This is simplified from src/examples/termemu.c

#include "config.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>
#include <assert.h>
#include <wctype.h>
#include <iostream>
#include <typeinfo>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/time.h>
#include <exception>

#if HAVE_PTY_H
#include <pty.h>
#elif HAVE_UTIL_H
#include <util.h>
#elif HAVE_LIBUTIL_H
#include <libutil.h>
#endif

#include "parser.h"
#include "completeterminal.h"
#include "swrite.h"
#include "fatal_assert.h"
#include "pty_compat.h"
#include "locale_utils.h"
#include "select.h"

const size_t buf_size = 16384;

static void emulate_terminal();

int main( int argc, char *argv[] )
{
  set_native_locale();
  fatal_assert( is_utf8_locale() );

retry:
    try {
      emulate_terminal( );
    } catch ( const std::exception &e ) {
      fprintf( stderr, "\r\nException caught: %s\r\n", e.what() );
    }

    if (getenv("AFL_PERSISTENT")) {
        raise(SIGSTOP);
        goto retry;
    }

  return 0;
}

static void emulate_terminal()
{
  int col = 80;
  int row = 24;

  /* open parser and terminal */
  Terminal::Complete complete( col, row );
  Terminal::Framebuffer state( col, row );

  /* open display */
  Terminal::Display display( false );

  swrite( STDOUT_FILENO, display.open().c_str() );
  bool initialized = false;

  while ( 1 ) {
      /* input from user */
      char buf[ buf_size ];

      /* fill buffer if possible */
      ssize_t bytes_read = read( STDIN_FILENO, buf, buf_size );
      if ( bytes_read == 0 ) { /* EOF */
        return;
      } else if ( bytes_read < 0 ) {
        perror( "read" );
        return;
      }

      std::string terminal_to_host = complete.act( std::string( buf, bytes_read ) );

    Terminal::Framebuffer new_frame( complete.get_fb() );


    display.downgrade( new_frame );
    std::string update = display.new_frame( initialized, state, new_frame );
    state = new_frame;

    initialized = true;
  }

  std::string update = display.new_frame( true, state, complete.get_fb() );
  swrite( STDOUT_FILENO, update.c_str() );

  swrite( STDOUT_FILENO, display.close().c_str() );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment