Hi,

After further tracing around the sources, I think I've figure out the
root cause of the fault: Tcl interpreter (not Tcl.so, which is
compiled from Tcl.xs) has changed global environment variable of the
process while Perl is not aware of it.
In more detail:
1. Tcl allocated a chunk of memory space to store string 'FOO=bar'
while the code 'set env(FOO) bar' gets executed
2. Tcl modifies the global variable 'environ' (exported by glibc with
type: char**). Then it points to a pointer array with one element
points to the newly allocated string 'FOO=bar'.
3. On exiting of Perl, the destructor of the Tcl interpreter object
gets invoked. The memory chunk with string 'FOO=bar' gets freed by
Tcl. However, the global variable 'environ' still points to the same
location and is still has the element points to the address 'FOO=bar'.
4. Finally Perl thinks the strings pointed by 'environ' was allocated
by itself and calls free() on each element, where one points to string
'FOO=bar', who has already been freed...

To demo the Perl/Tcl behavior, execute

    perl -MTcl -e 'Tcl::new->Eval("set env(FOO) bar"); print
"FOO=$ENV{FOO}\n"; system("echo FOO=\$FOO");'

We get the following output before segmentation fault

    FOO=
    FOO=bar

Here Perl's $ENV{FOO} is still undefined while the true environment of
the process is practically affected because the child process inherits
'FOO=bar'.
Moreover, if we have $ENV{FOO}="bar1" after Eval("set env...."), we
will get segmentation fault on it immediately, before exiting of Perl.

So far I am not able to come out a simple way to get it fixed. I think
either Tcl should not change the environment globally, or there should
be some way to inform Perl that environment variables has been
changed.

SJ

S.J. Luo <sja...@gmail.com> 於 2020年3月22日 週日 下午10:06寫道:

>
> Hi,
> I'd like to share my finding.
> I also duplicated the error on CentOS 7, who comes with perl 5.16
> installed and tcl-8.5.
> while on my Cygwin build there is no problem.
> After some tracing, I found the error point is at the perl_destruct()
> calling Perl_safesysfree() in perl.c:
>
>     
> https://perl5.git.perl.org/perl5.git/blob/06d742c02d396d6515581002f376b55ab1972c1b:/perl.c#l811
>
> There is a 'char **environ' for the environment variable string list
> and calling Perl_safesysfree(environ[i]) to free the memory space.
> However, I found the pointer value passed into Perl_safesysfree()
> differs from that reported by gdb 'p environ[0]' command.
> Here I have a demo of the gdb tracing result. There are two
> 'environ[0]'s: One is at address 0x7fffffffe70e (report by gdb)
> and the other is at 0x618330 (actually passed into Perl_safesysfree).
> Both of them have the same string content "USER=...".
> However, later has one additional item in its string list: "FOO=bar"
> while gdb reported one does not have it.
> Freeing this additional string leads to the error.
> Its seems to be issue of compiler optimization. I am going to digging
> into it later.
>
> --------------------
> (gdb) set breakpoint pending on
> (gdb) b perl.c:811
> Breakpoint 1 (perl.c:811) pending.
> (gdb) r
> Starting program: /usr/bin/perl -MTcl -e Tcl::new-\>Eval\(\"set\
> env\(FOO\)\ bar\"\)
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> [New Thread 0x7fffefa8f700 (LWP 6045)]
> [Thread 0x7fffefa8f700 (LWP 6045) exited]
>
> Breakpoint 1, perl_destruct (my_perl=0x603010) at perl.c:811
> 811                 safesysfree(environ[i]);
> Missing separate debuginfos, use: debuginfo-install
> libgcc-4.8.5-39.el7.x86_64 nss-softokn-freebl-3.44.0-8.el7_7.x86_64
> tcl-8.5.13-8.el7.x86
> (gdb) p environ
> $1 = (char **) 0x7fffffffe460
> (gdb) p environ[0]
> $2 = 0x7fffffffe70e "USER=crystal"
> (gdb) p environ[1]
> $3 = 0x7fffffffe71b "LOGNAME=crystal"
> (gdb) step
> Perl_safesysfree (where=0x618330) at util.c:254
> 254         if (where) {
> (gdb) p (char*)where
> $4 = 0x618330 "USER=crystal"
> (gdb) finish
> Run till exit from #0  Perl_safesysfree (where=0x618330) at util.c:254
> perl_destruct (my_perl=0x603010) at perl.c:810
> 810             for (i = 0; environ[i]; i++)
> (gdb) step
>
> Breakpoint 1, perl_destruct (my_perl=0x603010) at perl.c:811
> 811                 safesysfree(environ[i]);
> (gdb) step
> Perl_safesysfree (where=0x618350) at util.c:254
> 254         if (where) {
> (gdb) p (char*)where
> $5 = 0x618350 "LOGNAME=crystal"
> --------------------
> SJ
>
> Konovalov, Vadim <vadim.konova...@dell.com> wrote
> >
> > You're ingenious!
> >
> > vad@bonita:~$ perl -MTcl -we 'my $i=new Tcl;$i->Init;$i->Eval("set env(FOO) 
> > bar");'
> > free(): invalid size
> > Aborted (core dumped)
> > vad@bonita:~$
> >
> > and even $i->Init not needed:
> >
> > vad@bonita:~$ perl -MTcl -we 'Tcl::new->Eval("set env(FOO) bar");'
> > free(): invalid size
> > Aborted (core dumped)
> >
> > Haven't looked deeper though, but the picture now is much more clear! 
> > Thanks!
> >
> > -----Original Message-----
> > From: Christopher Chavez <chrischa...@gmx.us>
> > Sent: Tuesday, March 17, 2020 6:08 AM
> > To: tcltk@perl.org
> > Subject: Re: Possible bug in Tcl.pm exposed by Tktable?
> >
> >
> > [EXTERNAL EMAIL]
> >
> > On 3/16/2020 4:09 AM, Christopher Chavez wrote:
> > > To partially answer my own question:
> > > there are some compile-time techniques to try like -DPURIFY
> > > https://wiki.tcl-lang.org/page/How+to+debug+memory+faults+in+Tcl+and+e
> > > xtensions
> > After build from upstream sources (Tcl/Tk core-8-6-branch and TkTable 2.11 
> > -- no Debian/Ubuntu Tcl/Tk packages) with -DPURIFY, the error is revealed 
> > to be a double free. The addresses involved correspond to strings allocated 
> > for environment variable(s) set by Tktable (tkTableInitScript.h).
> >
> > A simpler program without Tk or Tktable reveals the same issue:
> >
> > use Tcl;
> >
> > my $i = new Tcl;
> > $i->Init;
> > $i->Eval('set env(FOO) bar');
> >
> >
> > Command line output:
> >
> > free(): double free detected in tcache 2 Aborted (core dumped)
> >
> > Valgrind output:
> >
> > ==13666== Invalid free() / delete / delete[] / realloc()
> > ==13666==    at 0x48369AB: free (vg_replace_malloc.c:530)
> > ==13666==    by 0x162304: perl_destruct (in /usr/bin/perl)
> > ==13666==    by 0x13C3DB: main (in /usr/bin/perl)
> > ==13666==  Address 0x5229a20 is 0 bytes inside a block of size 8 free'd
> > ==13666==    at 0x48369AB: free (vg_replace_malloc.c:530)
> > ==13666==    by 0x543EE86: TclpFree (tclAlloc.c:722)
> > ==13666==    by 0x5517935: TclFinalizeEnvironment (tclEnv.c:768)
> > ==13666==    by 0x5519268: Tcl_Finalize (tclEvent.c:1151)
> > ==13666==    by 0x485123D: XS_Tcl__Finalize (Tcl.xs:1449)
> > ==13666==    by 0x1F4360: Perl_pp_entersub (in /usr/bin/perl)
> > ==13666==    by 0x1EA685: Perl_runops_standard (in /usr/bin/perl)
> > ==13666==    by 0x15DF61: Perl_call_sv (in /usr/bin/perl)
> > ==13666==    by 0x160AC3: Perl_call_list (in /usr/bin/perl)
> > ==13666==    by 0x16235E: perl_destruct (in /usr/bin/perl)
> > ==13666==    by 0x13C3DB: main (in /usr/bin/perl)
> > ==13666==  Block was alloc'd at
> > ==13666==    at 0x4837D7B: realloc (vg_replace_malloc.c:826)
> > ==13666==    by 0x543EEAA: TclpRealloc (tclAlloc.c:747)
> > ==13666==    by 0x5456E8D: Tcl_Realloc (tclCkalloc.c:1147)
> > ==13666==    by 0x55172A9: TclSetEnv (tclEnv.c:317)
> > ==13666==    by 0x5517688: EnvTraceProc (tclEnv.c:636)
> > ==13666==    by 0x55A0B69: TclCallVarTraces (tclTrace.c:2678)
> > ==13666==    by 0x55A0860: TclObjCallVarTraces (tclTrace.c:2564)
> > ==13666==    by 0x55AAFDB: TclPtrSetVarIdx (tclVar.c:2001)
> > ==13666==    by 0x55AA957: Tcl_ObjSetVar2 (tclVar.c:1770)
> > ==13666==    by 0x55AA609: Tcl_SetObjCmd (tclVar.c:1529)
> > ==13666==    by 0x544A42A: Dispatch (tclBasic.c:4456)
> > ==13666==    by 0x544A4B0: TclNRRunCallbacks (tclBasic.c:4492)
> > ==13666==    by 0x5449D83: Tcl_EvalObjv (tclBasic.c:4215)
> > ==13666==    by 0x544C1AB: TclEvalEx (tclBasic.c:5361)
> > ==13666==    by 0x544B571: Tcl_EvalEx (tclBasic.c:5026)
> > ==13666==    by 0x48525A8: XS_Tcl_Eval (Tcl.xs:1097)
> > ==13666==    by 0x1F4360: Perl_pp_entersub (in /usr/bin/perl)
> > ==13666==    by 0x1EA685: Perl_runops_standard (in /usr/bin/perl)
> > ==13666==    by 0x166116: perl_run (in /usr/bin/perl)
> > ==13666==    by 0x13C401: main (in /usr/bin/perl)
> > ==13666==
> > ==13666== Invalid free() / delete / delete[] / realloc()
> > ==13666==    at 0x48369AB: free (vg_replace_malloc.c:530)
> > ==13666==    by 0x162322: perl_destruct (in /usr/bin/perl)
> > ==13666==    by 0x13C3DB: main (in /usr/bin/perl)
> > ==13666==  Address 0x5229810 is 0 bytes inside a block of size 376 free'd
> > ==13666==    at 0x48369AB: free (vg_replace_malloc.c:530)
> > ==13666==    by 0x543EE86: TclpFree (tclAlloc.c:722)
> > ==13666==    by 0x5517983: TclFinalizeEnvironment (tclEnv.c:776)
> > ==13666==    by 0x5519268: Tcl_Finalize (tclEvent.c:1151)
> > ==13666==    by 0x485123D: XS_Tcl__Finalize (Tcl.xs:1449)
> > ==13666==    by 0x1F4360: Perl_pp_entersub (in /usr/bin/perl)
> > ==13666==    by 0x1EA685: Perl_runops_standard (in /usr/bin/perl)
> > ==13666==    by 0x15DF61: Perl_call_sv (in /usr/bin/perl)
> > ==13666==    by 0x160AC3: Perl_call_list (in /usr/bin/perl)
> > ==13666==    by 0x16235E: perl_destruct (in /usr/bin/perl)
> > ==13666==    by 0x13C3DB: main (in /usr/bin/perl)
> > ==13666==  Block was alloc'd at
> > ==13666==    at 0x483577F: malloc (vg_replace_malloc.c:299)
> > ==13666==    by 0x543EE6C: TclpAlloc (tclAlloc.c:699)
> > ==13666==    by 0x5456D99: Tcl_Alloc (tclCkalloc.c:1059)
> > ==13666==    by 0x5517074: TclSetEnv (tclEnv.c:263)
> > ==13666==    by 0x5517688: EnvTraceProc (tclEnv.c:636)
> > ==13666==    by 0x55A0B69: TclCallVarTraces (tclTrace.c:2678)
> > ==13666==    by 0x55A0860: TclObjCallVarTraces (tclTrace.c:2564)
> > ==13666==    by 0x55AAFDB: TclPtrSetVarIdx (tclVar.c:2001)
> > ==13666==    by 0x55AA957: Tcl_ObjSetVar2 (tclVar.c:1770)
> > ==13666==    by 0x55AA609: Tcl_SetObjCmd (tclVar.c:1529)
> > ==13666==    by 0x544A42A: Dispatch (tclBasic.c:4456)
> > ==13666==    by 0x544A4B0: TclNRRunCallbacks (tclBasic.c:4492)
> > ==13666==    by 0x5449D83: Tcl_EvalObjv (tclBasic.c:4215)
> > ==13666==    by 0x544C1AB: TclEvalEx (tclBasic.c:5361)
> > ==13666==    by 0x544B571: Tcl_EvalEx (tclBasic.c:5026)
> > ==13666==    by 0x48525A8: XS_Tcl_Eval (Tcl.xs:1097)
> > ==13666==    by 0x1F4360: Perl_pp_entersub (in /usr/bin/perl)
> > ==13666==    by 0x1EA685: Perl_runops_standard (in /usr/bin/perl)
> > ==13666==    by 0x166116: perl_run (in /usr/bin/perl)
> > ==13666==    by 0x13C401: main (in /usr/bin/perl)
> >
> >
> >
> > (Note that I built Tcl/Tk with threads, since that is how Debian/Ubuntu 
> > Tcl/Tk are built; not yet sure if that affects this issue. Using system 
> > Perl vs self-compiled Perl should not be a factor, since Travis CI uses 
> > Perlbrew whereas I've used the system Perl.)
> >
> >
> > Christopher A. Chavez

Reply via email to