Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segmentation fault in fmt::print() (memory corruption, invalid write of size 1) #642

Closed
stze opened this issue Feb 1, 2018 · 1 comment

Comments

@stze
Copy link

stze commented Feb 1, 2018

Dear fmtlib team —

I have detected a segmentation fault in fmt::print() function using an invalid format specifier.

Version

0555cea

How to reproduce

  1. Use the following sample program (bug.cc):
#include "fmt/format.h"

int main(int argc, char** argv)
{
        try {
                char buf[] = "\x54\x6F\x6F\x20\x51\x20\x58\x20\x72\x20\x72\x20\x7B\x3A\x3C\x37"
                             "\x37\x37\x37\x37\x2E\x37\x70\x7D\x7D\x7D\x7D\x7D\x7D\x7D\x62\xB7"
                             "\x37\x37\x6F\x6F\x62\x76\x6F\x62\x61\x72\x20\x51\x20\x58\x20\x72"
                             "\x20\x72\x20\x00\x00\x80\x00\x37\x37\x37\x37\x2E\x37\x70\x7D\x7D"
                             "\x7D\x7D\x97\x7D\x7D\x7D\x7D\x7D\x7D\x7D\x7D\x7D\x7D\x7D";
                // alternative invalid format specifier (also SIGSEGV)
                //char buf[] = "<766{:<766766.60p}.68p}{";

                fmt::print(buf, "foo");
        } catch(fmt::FormatError) {

        }

        return 0;
}
  1. Compile bug.cc
$ clang++ -o bug bug.cc build/fmt/libfmt.a
  1. Execute
$ ./bug
Segmentation fault (core dumped)

gdb

Stopped reason: SIGSEGV
0x00007ffff718ccd5 in _int_malloc () from /lib64/libc.so.6
#0  0x00007ffff718ccd5 in _int_malloc () from /lib64/libc.so.6
#1  0x00007ffff7190813 in malloc () from /lib64/libc.so.6
#2  0x00007ffff71765ac in __GI__IO_file_doallocate () from /lib64/libc.so.6
#3  0x00007ffff7186599 in __GI__IO_doallocbuf () from /lib64/libc.so.6
#4  0x00007ffff71856b8 in __GI__IO_file_overflow () from /lib64/libc.so.6
#5  0x00007ffff7183c5d in __GI__IO_file_xsputn () from /lib64/libc.so.6
#6  0x00007ffff7177c27 in fwrite () from /lib64/libc.so.6
#7  0x00000000004037cd in fmt::print (f=0x7ffff74de740 <_IO_2_1_stdout_>, format_str=..., args=...)
    at /home/stze/Documents/repositories/fmt/fmt/format.cc:446
#8  0x0000000000401891 in fmt::print<char [4]> (arg0=..., v0=...) at ./fmt/format.h:3783
#9  main (argc=<optimized out>, argv=<optimized out>) at bug.cc:20
#10 0x00007ffff712100a in __libc_start_main () from /lib64/libc.so.6
#11 0x000000000040175a in _start ()

valgrind

==13377== Memcheck, a memory error detector
==13377== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13377== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==13377== Command: ./bug
==13377== 
==13377== Invalid write of size 1
==13377==    at 0x40EF36: void fmt::BasicWriter<char>::write_int<unsigned long, fmt::FormatSpec>(unsigned long, fmt::FormatSpec) (format.h:3080)
==13377==    by 0x408285: fmt::ArgVisitor<fmt::ArgFormatter<char>, void>::visit(fmt::internal::Arg const&) (format.h:0)
==13377==    by 0x4068F7: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(char const*&, fmt::internal::Arg const&) (format.h:4045)
==13377==    by 0x404BCC: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(fmt::BasicCStringRef<char>) (format.h:4066)
==13377==    by 0x403749: write (format.h:2819)
==13377==    by 0x403749: fmt::print(_IO_FILE*, fmt::BasicCStringRef<char>, fmt::ArgList) (format.cc:445)
==13377==    by 0x401890: print<char [4]> (format.h:3783)
==13377==    by 0x401890: main (bug.cc:20)
==13377==  Address 0x5b22c94 is 20 bytes inside a block of size 77,777 free'd
==13377==    at 0x4C311E8: operator delete(void*) (vg_replace_malloc.c:576)
==13377==    by 0x40475A: deallocate (new_allocator.h:125)
==13377==    by 0x40475A: fmt::internal::MemoryBuffer<char, 500ul, std::allocator<char> >::grow(unsigned long) (format.h:924)
==13377==    by 0x4098FA: resize (format.h:801)
==13377==    by 0x4098FA: grow_buffer (format.h:2670)
==13377==    by 0x4098FA: char* fmt::BasicWriter<char>::prepare_int_buffer<fmt::FormatSpec>(unsigned int, fmt::FormatSpec const&, char const*, unsigned int) (format.h:3006)
==13377==    by 0x40EEF3: void fmt::BasicWriter<char>::write_int<unsigned long, fmt::FormatSpec>(unsigned long, fmt::FormatSpec) (format.h:3074)
==13377==    by 0x408285: fmt::ArgVisitor<fmt::ArgFormatter<char>, void>::visit(fmt::internal::Arg const&) (format.h:0)
==13377==    by 0x4068F7: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(char const*&, fmt::internal::Arg const&) (format.h:4045)
==13377==    by 0x404BCC: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(fmt::BasicCStringRef<char>) (format.h:4066)
==13377==    by 0x403749: write (format.h:2819)
==13377==    by 0x403749: fmt::print(_IO_FILE*, fmt::BasicCStringRef<char>, fmt::ArgList) (format.cc:445)
==13377==    by 0x401890: print<char [4]> (format.h:3783)
==13377==    by 0x401890: main (bug.cc:20)
==13377==  Block was alloc'd at
==13377==    at 0x4C301CA: operator new(unsigned long) (vg_replace_malloc.c:334)
==13377==    by 0x4046CC: allocate (new_allocator.h:111)
==13377==    by 0x4046CC: allocate (alloc_traits.h:450)
==13377==    by 0x4046CC: fmt::internal::MemoryBuffer<char, 500ul, std::allocator<char> >::grow(unsigned long) (format.h:909)
==13377==    by 0x409842: reserve (format.h:812)
==13377==    by 0x409842: char* fmt::BasicWriter<char>::prepare_int_buffer<fmt::FormatSpec>(unsigned int, fmt::FormatSpec const&, char const*, unsigned int) (format.h:2997)
==13377==    by 0x40EEF3: void fmt::BasicWriter<char>::write_int<unsigned long, fmt::FormatSpec>(unsigned long, fmt::FormatSpec) (format.h:3074)
==13377==    by 0x408285: fmt::ArgVisitor<fmt::ArgFormatter<char>, void>::visit(fmt::internal::Arg const&) (format.h:0)
==13377==    by 0x4068F7: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(char const*&, fmt::internal::Arg const&) (format.h:4045)
==13377==    by 0x404BCC: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(fmt::BasicCStringRef<char>) (format.h:4066)
==13377==    by 0x403749: write (format.h:2819)
==13377==    by 0x403749: fmt::print(_IO_FILE*, fmt::BasicCStringRef<char>, fmt::ArgList) (format.cc:445)
==13377==    by 0x401890: print<char [4]> (format.h:3783)
==13377==    by 0x401890: main (bug.cc:20)
==13377== 
==13377== Syscall param write(buf) points to uninitialised byte(s)
==13377==    at 0x58348F4: write (in /usr/lib64/libc-2.26.so)
==13377==    by 0x57B13FC: _IO_file_write@@GLIBC_2.2.5 (in /usr/lib64/libc-2.26.so)
==13377==    by 0x57B1D9E: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.26.so)
==13377==    by 0x57A5C26: fwrite (in /usr/lib64/libc-2.26.so)
==13377==    by 0x4037CC: fmt::print(_IO_FILE*, fmt::BasicCStringRef<char>, fmt::ArgList) (format.cc:446)
==13377==    by 0x401890: print<char [4]> (format.h:3783)
==13377==    by 0x401890: main (bug.cc:20)
==13377==  Address 0x5b35caf is 15 bytes inside a block of size 116,665 alloc'd
==13377==    at 0x4C301CA: operator new(unsigned long) (vg_replace_malloc.c:334)
==13377==    by 0x4046CC: allocate (new_allocator.h:111)
==13377==    by 0x4046CC: allocate (alloc_traits.h:450)
==13377==    by 0x4046CC: fmt::internal::MemoryBuffer<char, 500ul, std::allocator<char> >::grow(unsigned long) (format.h:909)
==13377==    by 0x4098FA: resize (format.h:801)
==13377==    by 0x4098FA: grow_buffer (format.h:2670)
==13377==    by 0x4098FA: char* fmt::BasicWriter<char>::prepare_int_buffer<fmt::FormatSpec>(unsigned int, fmt::FormatSpec const&, char const*, unsigned int) (format.h:3006)
==13377==    by 0x40EEF3: void fmt::BasicWriter<char>::write_int<unsigned long, fmt::FormatSpec>(unsigned long, fmt::FormatSpec) (format.h:3074)
==13377==    by 0x408285: fmt::ArgVisitor<fmt::ArgFormatter<char>, void>::visit(fmt::internal::Arg const&) (format.h:0)
==13377==    by 0x4068F7: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(char const*&, fmt::internal::Arg const&) (format.h:4045)
==13377==    by 0x404BCC: fmt::BasicFormatter<char, fmt::ArgFormatter<char> >::format(fmt::BasicCStringRef<char>) (format.h:4066)
==13377==    by 0x403749: write (format.h:2819)
==13377==    by 0x403749: fmt::print(_IO_FILE*, fmt::BasicCStringRef<char>, fmt::ArgList) (format.cc:445)
==13377==    by 0x401890: print<char [4]> (format.h:3783)
==13377==    by 0x401890: main (bug.cc:20)
==13377== 
Too Q X r r 0x0 

             }}}b�77oobvobar Q X r r ==13377== 
==13377== HEAP SUMMARY:
==13377==     in use at exit: 0 bytes in 0 blocks
==13377==   total heap usage: 4 allocs, 4 frees, 268,170 bytes allocated
==13377== 
==13377== All heap blocks were freed -- no leaks are possible
==13377== 
==13377== For counts of detected and suppressed errors, rerun with: -v
==13377== Use --track-origins=yes to see where uninitialised values come from
==13377== ERROR SUMMARY: 7 errors from 2 contexts (suppressed: 0 from 0)

Expected behaviour

Throw FormatError exception

I found the issue with AFL.

Cheers
-Stephan Zeisberg

@vitaut
Copy link
Contributor

vitaut commented Feb 2, 2018

Fixed in 8cf30aa. Thanks a lot for catching this and the detailed report!

@vitaut vitaut closed this as completed Feb 2, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants