classification
Title: UAF in Tkinter module
Type: crash Stage:
Components: Tkinter Versions: Python 3.6, Python 3.5, Python 2.7
process
Status: closed Resolution: third party
Dependencies: Superseder:
Assigned To: Nosy List: Emin Ghuliev, serhiy.storchaka
Priority: normal Keywords:

Created on 2016-06-07 18:29 by Emin Ghuliev, last changed 2016-07-05 06:09 by Emin Ghuliev. This issue is now closed.

Messages (6)
msg267714 - (view) Author: Emin Ghuliev (Emin Ghuliev) Date: 2016-06-07 18:28
/* This is used to get the application class for Tk 4.1 and up */
    argv0 = (char*)attemptckalloc(strlen(className) + 1); //<=== classname allocated
    if (!argv0) {
        PyErr_NoMemory();
        Py_DECREF(v);
        return NULL;
    }

    strcpy(argv0, className); <==== //classname copy to argv0
    if (Py_ISUPPER(Py_CHARMASK(argv0[0])))
        argv0[0] = Py_TOLOWER(Py_CHARMASK(argv0[0]));
    Tcl_SetVar(v->interp, "argv0", argv0, TCL_GLOBAL_ONLY); // argv0 passed to v->interp and freed;
    ckfree(argv0);

then v->interp passed to the Tcl_AppInit function
    if (Tcl_AppInit(v->interp) != TCL_OK)

in Tcl_AppInit call to (and passed the v->interp) the Tcl_DStringAppend. allocates the specified byte Tcl_DStringAppend function then heap memory passed to memcpy.

Realloc arguments
presentation in the native tcl allocator; 
    char * 
    Tcl_Realloc(ptr, size) 

disassemble:
gdb>  print /x $rdi
$4 = 0x7ffff03c8810
0x7ffff03c8814: 0x41414141 .......
gdb>  print /x $rsi
$2 = 0x3ffffe
   0x00007ffff3a07dfe <+46>:	call   0x7ffff3935040 <Tcl_Realloc>
after return to the caller function. Performed memory copy operation.

   0x00007ffff3a07e0a <+58>:	lea    rdi,[rax+rdx*1] < === destination buffer

   $rax = 0x7fffeffc5810 - $rdx = 0x100000
   $rax+$rdx = 0x7ffff00c5810



   0x00007ffff3a07e0e <+62>:	mov    rsi,r12 < === source buffer
   0x00007ffff3a07e11 <+65>:	movsxd rdx,ebp <=== 0xfffff
   0x00007ffff3a07e14 <+68>:	call   0x7ffff39155c0 <memcpy@plt>

 	copy to $rdi bytes to $rsi buffer with 0xfffff byte;

ASAN report.

=================================================================
==27988==ERROR: AddressSanitizer: heap-use-after-free on address 0x7f4e6ba64810 at pc 0x4665ea bp 0x7fff89a4ab80 sp 0x7fff89a4a340
READ of size 1048575 at 0x7f4e6ba64810 thread T0
==27988==WARNING: Trying to symbolize code, but external symbolizer is not initialized!
    #0 0x4665e9 (/home/eminus/Downloads/Python-2.7.11/python+0x4665e9)
    #1 0x7f4e6f0a3e18 (/usr/lib/x86_64-linux-gnu/libtcl8.6.so+0x116e18)
    #2 0x7f4e6f38744e (/usr/lib/x86_64-linux-gnu/libtk8.6.so+0x6244e)
    #3 0x7f4e6f6b6e4c (/home/eminus/Downloads/Python-2.7.11/build/lib.linux-x86_64-2.7/_tkinter.so+0x19e4c)
    #4 0x7f4e6f6a7fc5 (/home/eminus/Downloads/Python-2.7.11/build/lib.linux-x86_64-2.7/_tkinter.so+0xafc5)
    #5 0x5e1813 (/home/eminus/Downloads/Python-2.7.11/python+0x5e1813)
    #6 0x5d319c (/home/eminus/Downloads/Python-2.7.11/python+0x5d319c)
    #7 0x721353 (/home/eminus/Downloads/Python-2.7.11/python+0x721353)
    #8 0x4acb2a (/home/eminus/Downloads/Python-2.7.11/python+0x4acb2a)
    #9 0x4b6c62 (/home/eminus/Downloads/Python-2.7.11/python+0x4b6c62)
    #10 0x4acb2a (/home/eminus/Downloads/Python-2.7.11/python+0x4acb2a)
    #11 0x5f0823 (/home/eminus/Downloads/Python-2.7.11/python+0x5f0823)
    #12 0x4b0a08 (/home/eminus/Downloads/Python-2.7.11/python+0x4b0a08)
    #13 0x4acb2a (/home/eminus/Downloads/Python-2.7.11/python+0x4acb2a)
    #14 0x5e2d19 (/home/eminus/Downloads/Python-2.7.11/python+0x5e2d19)
    #15 0x5d319c (/home/eminus/Downloads/Python-2.7.11/python+0x5d319c)
    #16 0x5d2041 (/home/eminus/Downloads/Python-2.7.11/python+0x5d2041)
    #17 0x660980 (/home/eminus/Downloads/Python-2.7.11/python+0x660980)
    #18 0x65fc8a (/home/eminus/Downloads/Python-2.7.11/python+0x65fc8a)
    #19 0x48e46c (/home/eminus/Downloads/Python-2.7.11/python+0x48e46c)
    #20 0x7f4e72389ec4 (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
    #21 0x48c5bc (/home/eminus/Downloads/Python-2.7.11/python+0x48c5bc)

0x7f4e6ba64810 is located 16 bytes inside of 2097166-byte region [0x7f4e6ba64800,0x7f4e6bc6480e)
freed by thread T0 here:
    #0 0x4766d3 (/home/eminus/Downloads/Python-2.7.11/python+0x4766d3)
    #1 0x7f4e6f09b52d (/usr/lib/x86_64-linux-gnu/libtcl8.6.so+0x10e52d)

previously allocated by thread T0 here:
    #0 0x4764d9 (/home/eminus/Downloads/Python-2.7.11/python+0x4764d9)
    #1 0x7f4e6f09b0cc (/usr/lib/x86_64-linux-gnu/libtcl8.6.so+0x10e0cc)

SUMMARY: AddressSanitizer: heap-use-after-free ??:0 ??
Shadow bytes around the buggy address:
  0x0fea4d7448b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fea4d7448c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fea4d7448d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fea4d7448e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0fea4d7448f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0fea4d744900: fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0fea4d744910: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0fea4d744920: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0fea4d744930: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0fea4d744940: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0fea4d744950: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:     fa
  Heap right redzone:    fb
  Freed heap region:     fd
  Stack left redzone:    f1
  Stack mid redzone:     f2
  Stack right redzone:   f3
  Stack partial redzone: f4
  Stack after return:    f5
  Stack use after scope: f8
  Global redzone:        f9
  Global init order:     f6
  Poisoned by user:      f7
  ASan internal:         fe
==27988==ABORTING





PoC

from Tkinter import *

class Application(Frame):
    def say_hi(self):
        print ("hi there, everyone!")

    def createWidgets(self):
        self.QUIT = Button(self)
        self.QUIT["text"] = "QUIT"
        self.QUIT["fg"]   = "red"
        self.QUIT["command"] =  self.quit

        self.QUIT.pack({"side": "left"})

        self.hi_there = Button(self)
        self.hi_there["text"] = "Hello",
        self.hi_there["command"] = self.say_hi

        self.hi_there.pack({"side": "left"})


    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

root = Tk(screenName=None, baseName=None, className='A'*0xfffff, useTk=1)
app = Application(master=root)
app.mainloop()
root.destroy()
msg267727 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-06-07 19:02
Minimal reproducer:

from tkinter import Tk
Tk(className='A'*0xfffff)

This looks as Tcl/Tk problem.
msg267795 - (view) Author: Emin Ghuliev (Emin Ghuliev) Date: 2016-06-08 06:19
Yeah you're right but Python doesn't check the classname length.  Therefore then heap overflow occurred in the Tcl.
msg267798 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-06-08 06:52
What size is safe for className?
msg267800 - (view) Author: Emin Ghuliev (Emin Ghuliev) Date: 2016-06-08 07:02
the appropriate size should be chosen I)
msg267860 - (view) Author: Emin Ghuliev (Emin Ghuliev) Date: 2016-06-08 14:45
psuedocode

<+16>:	movsxd rdx,DWORD PTR [rbx+0x8]
<+20>:	lea    eax,[rdx+rbp*1]

newSize = length ($rdx) + dsPtr->length ($rbp)
gdb > print /x $rbp
$5 = 0xfffff
gdb > print /x $rdx
$6 = 0x100000

newsize = 0xfffff+0x100000 = 0x1fffff

<Tcl_DStringAppend+23>  cmp    eax,DWORD PTR [rbx+0xc] 		 ← $pc
<Tcl_DStringAppend+26>  jl     0x7ffff6194e38 <Tcl_DStringAppend+104>

newSize ($eax) >= dsPtr->spaceAvl

gdb > print /x $eax
$7 = 0x1fffff

gdb > x/x $rbx+0xc
0x7fffffffd0cc:	0x001ffffe

condition: 0x1fffff >= 0x001ffffe = True

	if (newSize >= dsPtr->spaceAvl) {
		<Tcl_DStringAppend+31>  lea    esi,[rax+rax*1] ; magic compiler optimization :) (newSize(0x1fffff)*2)
		/*							*/
		dsPtr->spaceAvl = newSize * 2;
		gdb > print /x $rax
		$4 = 0x1fffff
		$esi = 0x1fffff+0x1fffff (newSize(0x1fffff)*2) = 0x3ffffe
		/*							*/
		
		=> <+34>:	lea    rax,[rbx+0x10]
		   <+38>:	mov    DWORD PTR [rbx+0xc],esi
		   <+41>:	cmp    rdi,rax ; $rax = dsPtr->staticSpace and $rdi = dsPtr->string
		   <+44>:	je     0x7ffff6194e50 <Tcl_DStringAppend+128>
		
		condition : dsPtr->string == dsPtr->staticSpace = False then jump to '<Tcl_DStringAppend+46>  call   0x7ffff60c2040 <Tcl_Realloc>'

	        if (dsPtr->string == dsPtr->staticSpace) {	          
			char *newString = ckalloc(dsPtr->spaceAvl);
            		memcpy(newString, dsPtr->string, (size_t) dsPtr->length);
			dsPtr->string = newString;
		} 
		else {
			<Tcl_DStringAppend+46>  call   0x7ffff60c2040 <Tcl_Realloc>
			$rsi = 0x3ffffe
			$rdi = 0x7ffff333e020
			dsPtr->string = ckrealloc(dsPtr->string = 0x7ffff333e020, dsPtr->spaceAvl = 0x3ffffe);
		}
	}


disassemble: 
		 <Tcl_DStringAppend+58>  lea    rdi,[rax+rdx*1] 	; dsPtr->string + dsPtr->length
		 <Tcl_DStringAppend+62>  mov    rsi,r12			; bytes
		 <Tcl_DStringAppend+65>  movsxd rdx,ebp			; length
		 <Tcl_DStringAppend+68>  call   0x7ffff60a25c0 <memcpy@plt>
		 memcpy(dsPtr->string + dsPtr->length, bytes, length);
History
Date User Action Args
2016-07-05 06:09:47Emin Ghulievsetresolution: third party
2016-07-05 06:09:30Emin Ghulievsetstatus: open -> closed
2016-06-12 13:54:16Emin Ghulievsettitle: heap overflow in Tkinter module -> UAF in Tkinter module
2016-06-08 14:45:49Emin Ghulievsetmessages: + msg267860
2016-06-08 07:02:41Emin Ghulievsetmessages: + msg267800
2016-06-08 06:52:03serhiy.storchakasetmessages: + msg267798
2016-06-08 06:19:08Emin Ghulievsetmessages: + msg267795
2016-06-07 19:02:10serhiy.storchakasetversions: + Python 3.5, Python 3.6, - Python 3.3, Python 3.4
nosy: + serhiy.storchaka

messages: + msg267727

type: security -> crash
2016-06-07 18:29:00Emin Ghulievcreate