Slightly older version of master from https://github.com/j6t/gitk

Merge branch 'js/fix-open-exec'

This addresses CVE-2025-27613, Gitk can create and truncate a user's
files:

When a user clones an untrusted repository and runs gitk without
additional command arguments, files for which the user has write
permission can be created and truncated. The option "Support per-file
encoding" must have been enabled before in Gitk's Preferences. This
option is disabled by default.

The same happens when "Show origin of this line" is used in the main
window (regardless of whether "Support per-file encoding" is enabled or
not).

* js/fix-open-exec:
gitk: sanitize 'open' arguments: revisit recently updated 'open' calls
gitk: sanitize 'open' arguments: command pipeline
gitk: collect construction of blameargs into a single conditional
gitk: sanitize 'open' arguments: simple commands, readable and writable
gitk: sanitize 'open' arguments: simple commands with redirections
gitk: sanitize 'open' arguments: simple commands
gitk: sanitize 'exec' arguments: redirect to process
gitk: sanitize 'exec' arguments: redirections and background
gitk: sanitize 'exec' arguments: redirections
gitk: sanitize 'exec' arguments: 'eval exec'
gitk: sanitize 'exec' arguments: simple cases
gitk: have callers of diffcmd supply pipe symbol when necessary
gitk: treat file names beginning with "|" as relative paths

Signed-off-by: Johannes Sixt <j6t@kdbg.org>

+171 -93
+171 -93
gitk
··· 113 113 114 114 # End of safe PATH lookup stuff 115 115 116 + # Wrap exec/open to sanitize arguments 117 + 118 + # unsafe arguments begin with redirections or the pipe or background operators 119 + proc is_arg_unsafe {arg} { 120 + regexp {^([<|>&]|2>)} $arg 121 + } 122 + 123 + proc make_arg_safe {arg} { 124 + if {[is_arg_unsafe $arg]} { 125 + set arg [file join . $arg] 126 + } 127 + return $arg 128 + } 129 + 130 + proc make_arglist_safe {arglist} { 131 + set res {} 132 + foreach arg $arglist { 133 + lappend res [make_arg_safe $arg] 134 + } 135 + return $res 136 + } 137 + 138 + # executes one command 139 + # no redirections or pipelines are possible 140 + # cmd is a list that specifies the command and its arguments 141 + # calls `exec` and returns its value 142 + proc safe_exec {cmd} { 143 + eval exec [make_arglist_safe $cmd] 144 + } 145 + 146 + # executes one command with redirections 147 + # no pipelines are possible 148 + # cmd is a list that specifies the command and its arguments 149 + # redir is a list that specifies redirections (output, background, constant(!) commands) 150 + # calls `exec` and returns its value 151 + proc safe_exec_redirect {cmd redir} { 152 + eval exec [make_arglist_safe $cmd] $redir 153 + } 154 + 155 + proc safe_open_file {filename flags} { 156 + # a file name starting with "|" would attempt to run a process 157 + # but such a file name must be treated as a relative path 158 + # hide the "|" behind "./" 159 + if {[string index $filename 0] eq "|"} { 160 + set filename [file join . $filename] 161 + } 162 + open $filename $flags 163 + } 164 + 165 + # opens a command pipeline for reading 166 + # cmd is a list that specifies the command and its arguments 167 + # calls `open` and returns the file id 168 + proc safe_open_command {cmd} { 169 + open |[make_arglist_safe $cmd] r 170 + } 171 + 172 + # opens a command pipeline for reading and writing 173 + # cmd is a list that specifies the command and its arguments 174 + # calls `open` and returns the file id 175 + proc safe_open_command_rw {cmd} { 176 + open |[make_arglist_safe $cmd] r+ 177 + } 178 + 179 + # opens a command pipeline for reading with redirections 180 + # cmd is a list that specifies the command and its arguments 181 + # redir is a list that specifies redirections 182 + # calls `open` and returns the file id 183 + proc safe_open_command_redirect {cmd redir} { 184 + set cmd [make_arglist_safe $cmd] 185 + open |[concat $cmd $redir] r 186 + } 187 + 188 + # opens a pipeline with several commands for reading 189 + # cmds is a list of lists, each of which specifies a command and its arguments 190 + # calls `open` and returns the file id 191 + proc safe_open_pipeline {cmds} { 192 + set cmd {} 193 + foreach subcmd $cmds { 194 + set cmd [concat $cmd | [make_arglist_safe $subcmd]] 195 + } 196 + open $cmd r 197 + } 198 + 199 + # End exec/open wrappers 200 + 116 201 proc hasworktree {} { 117 202 return [expr {[exec git rev-parse --is-bare-repository] == "false" && 118 203 [exec git rev-parse --is-inside-git-dir] == "false"}] ··· 238 323 set mlist {} 239 324 set nr_unmerged 0 240 325 if {[catch { 241 - set fd [open "| git ls-files -u" r] 326 + set fd [safe_open_command {git ls-files -u}] 242 327 } err]} { 243 328 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" 244 329 exit 1 ··· 400 485 } elseif {[lsearch -exact $revs --all] >= 0} { 401 486 lappend revs HEAD 402 487 } 403 - if {[catch {set ids [eval exec git rev-parse $revs]} err]} { 488 + if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} { 404 489 # we get stdout followed by stderr in $err 405 490 # for an unknown rev, git rev-parse echoes it and then errors out 406 491 set errlines [split $err "\n"] ··· 478 563 set args $viewargs($view) 479 564 if {$viewargscmd($view) ne {}} { 480 565 if {[catch { 481 - set str [exec sh -c $viewargscmd($view)] 566 + set str [safe_exec [list sh -c $viewargscmd($view)]] 482 567 } err]} { 483 568 error_popup "[mc "Error executing --argscmd command:"] $err" 484 569 return 0 ··· 516 601 } 517 602 518 603 if {[catch { 519 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 520 - --parents --boundary $args --stdin \ 521 - [list "<<[join [concat $revs "--" $files] "\n"]"]] r] 604 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 605 + --parents --boundary $args --stdin] \ 606 + [list "<<[join [concat $revs "--" $files] "\n"]"]] 522 607 } err]} { 523 608 error_popup "[mc "Error executing git log:"] $err" 524 609 return 0 ··· 552 637 set pid [pid $fd] 553 638 554 639 if {$::tcl_platform(platform) eq {windows}} { 555 - exec taskkill /pid $pid 640 + safe_exec [list taskkill /pid $pid] 556 641 } else { 557 - exec kill $pid 642 + safe_exec [list kill $pid] 558 643 } 559 644 } 560 645 catch {close $fd} ··· 669 754 set args $vorigargs($view) 670 755 } 671 756 if {[catch { 672 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 673 - --parents --boundary $args --stdin \ 674 - [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]] r] 757 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 758 + --parents --boundary $args --stdin] \ 759 + [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]] 675 760 } err]} { 676 761 error_popup "[mc "Error executing git log:"] $err" 677 762 return ··· 1638 1723 # and if we already know about it, using the rewritten 1639 1724 # parent as a substitute parent for $id's children. 1640 1725 if {![catch { 1641 - set rwid [exec git rev-list --first-parent --max-count=1 \ 1642 - $id -- $vfilelimit($view)] 1726 + set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \ 1727 + $id -- $vfilelimit($view)]] 1643 1728 }]} { 1644 1729 if {$rwid ne {} && [info exists varcid($view,$rwid)]} { 1645 1730 # use $rwid in place of $id ··· 1759 1844 global tclencoding 1760 1845 1761 1846 # Invoke git-log to handle automatic encoding conversion 1762 - set fd [open [concat | git log --no-color --pretty=raw -1 $id] r] 1847 + set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]] 1763 1848 # Read the results using i18n.logoutputencoding 1764 1849 fconfigure $fd -translation lf -eofchar {} 1765 1850 if {$tclencoding != {}} { ··· 1895 1980 foreach v {tagids idtags headids idheads otherrefids idotherrefs} { 1896 1981 unset -nocomplain $v 1897 1982 } 1898 - set refd [open [list | git show-ref -d] r] 1983 + set refd [safe_open_command [list git show-ref -d]] 1899 1984 if {$tclencoding != {}} { 1900 1985 fconfigure $refd -encoding $tclencoding 1901 1986 } ··· 1943 2028 set selectheadid {} 1944 2029 if {$selecthead ne {}} { 1945 2030 catch { 1946 - set selectheadid [exec git rev-parse --verify $selecthead] 2031 + set selectheadid [safe_exec [list git rev-parse --verify $selecthead]] 1947 2032 } 1948 2033 } 1949 2034 } ··· 2207 2292 {mc "Reread re&ferences" command rereadrefs} 2208 2293 {mc "&List references" command showrefs -accelerator F2} 2209 2294 {xx "" separator} 2210 - {mc "Start git &gui" command {exec git gui &}} 2295 + {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}} 2211 2296 {xx "" separator} 2212 2297 {mc "&Quit" command doquit -accelerator Meta1-Q} 2213 2298 }} ··· 2994 3079 set remove_tmp 0 2995 3080 if {[catch { 2996 3081 set try_count 0 2997 - while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} { 3082 + while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} { 2998 3083 if {[incr try_count] > 50} { 2999 3084 error "Unable to write config file: $config_file_tmp exists" 3000 3085 } ··· 3710 3795 set tmpdir $gitdir 3711 3796 } 3712 3797 set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"] 3713 - if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} { 3798 + if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} { 3714 3799 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] 3715 3800 } 3716 3801 if {[catch {file mkdir $gitktmpdir} err]} { ··· 3732 3817 proc save_file_from_commit {filename output what} { 3733 3818 global nullfile 3734 3819 3735 - if {[catch {exec git show $filename -- > $output} err]} { 3820 + if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} { 3736 3821 if {[string match "fatal: bad revision *" $err]} { 3737 3822 return $nullfile 3738 3823 } ··· 3797 3882 3798 3883 if {$difffromfile ne {} && $difftofile ne {}} { 3799 3884 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] 3800 - if {[catch {set fl [open |$cmd r]} err]} { 3885 + if {[catch {set fl [safe_open_command $cmd]} err]} { 3801 3886 file delete -force $diffdir 3802 3887 error_popup "$extdifftool: [mc "command failed:"] $err" 3803 3888 } else { ··· 3901 3986 # Find the SHA1 ID of the blob for file $fname in the index 3902 3987 # at stage 0 or 2 3903 3988 proc index_sha1 {fname} { 3904 - set f [open [list | git ls-files -s $fname] r] 3989 + set f [safe_open_command [list git ls-files -s $fname]] 3905 3990 while {[gets $f line] >= 0} { 3906 3991 set info [lindex [split $line "\t"] 0] 3907 3992 set stage [lindex $info 2] ··· 3961 4046 # being given an absolute path... 3962 4047 set f [make_relative $f] 3963 4048 lappend cmdline $base_commit $f 3964 - if {[catch {eval exec $cmdline &} err]} { 4049 + if {[catch {safe_exec_redirect $cmdline [list &]} err]} { 3965 4050 error_popup "[mc "git gui blame: command failed:"] $err" 3966 4051 } 3967 4052 } ··· 3989 4074 # must be a merge in progress... 3990 4075 if {[catch { 3991 4076 # get the last line from .git/MERGE_HEAD 3992 - set f [open [file join $gitdir MERGE_HEAD] r] 4077 + set f [safe_open_file [file join $gitdir MERGE_HEAD] r] 3993 4078 set id [lindex [split [read $f] "\n"] end-1] 3994 4079 close $f 3995 4080 } err]} { ··· 4012 4097 } 4013 4098 set line [lindex $h 1] 4014 4099 } 4015 - set blameargs {} 4016 - if {$from_index ne {}} { 4017 - lappend blameargs | git cat-file blob $from_index 4018 - } 4019 - lappend blameargs | git blame -p -L$line,+1 4100 + set blamefile [file join $cdup $flist_menu_file] 4020 4101 if {$from_index ne {}} { 4021 - lappend blameargs --contents - 4102 + set blameargs [list \ 4103 + [list git cat-file blob $from_index] \ 4104 + [list git blame -p -L$line,+1 --contents - -- $blamefile]] 4022 4105 } else { 4023 - lappend blameargs $id 4106 + set blameargs [list \ 4107 + [list git blame -p -L$line,+1 $id -- $blamefile]] 4024 4108 } 4025 - lappend blameargs -- [file join $cdup $flist_menu_file] 4026 4109 if {[catch { 4027 - set f [open $blameargs r] 4110 + set f [safe_open_pipeline $blameargs] 4028 4111 } err]} { 4029 4112 error_popup [mc "Couldn't start git blame: %s" $err] 4030 4113 return ··· 4949 5032 # must be "containing:", i.e. we're searching commit info 4950 5033 return 4951 5034 } 4952 - set cmd [concat | git diff-tree -r -s --stdin $gdtargs] 4953 - set filehighlight [open $cmd r+] 5035 + set cmd [concat git diff-tree -r -s --stdin $gdtargs] 5036 + set filehighlight [safe_open_command_rw $cmd] 4954 5037 fconfigure $filehighlight -blocking 0 4955 5038 filerun $filehighlight readfhighlight 4956 5039 set fhl_list {} ··· 5379 5462 global viewmainheadid vfilelimit viewinstances mainheadid 5380 5463 5381 5464 catch { 5382 - set rfd [open [concat | git rev-list -1 $mainheadid \ 5383 - -- $vfilelimit($view)] r] 5465 + set rfd [safe_open_command [concat git rev-list -1 $mainheadid \ 5466 + -- $vfilelimit($view)]] 5384 5467 set j [reg_instance $rfd] 5385 5468 lappend viewinstances($view) $j 5386 5469 fconfigure $rfd -blocking 0 ··· 5445 5528 if {!$showlocalchanges || !$hasworktree} return 5446 5529 incr lserial 5447 5530 if {[package vcompare $git_version "1.7.2"] >= 0} { 5448 - set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD" 5531 + set cmd "git diff-index --cached --ignore-submodules=dirty HEAD" 5449 5532 } else { 5450 - set cmd "|git diff-index --cached HEAD" 5533 + set cmd "git diff-index --cached HEAD" 5451 5534 } 5452 5535 if {$vfilelimit($curview) ne {}} { 5453 5536 set cmd [concat $cmd -- $vfilelimit($curview)] 5454 5537 } 5455 - set fd [open $cmd r] 5538 + set fd [safe_open_command $cmd] 5456 5539 fconfigure $fd -blocking 0 5457 5540 set i [reg_instance $fd] 5458 5541 filerun $fd [list readdiffindex $fd $lserial $i] ··· 5477 5560 } 5478 5561 5479 5562 # now see if there are any local changes not checked in to the index 5480 - set cmd "|git diff-files" 5563 + set cmd "git diff-files" 5481 5564 if {$vfilelimit($curview) ne {}} { 5482 5565 set cmd [concat $cmd -- $vfilelimit($curview)] 5483 5566 } 5484 - set fd [open $cmd r] 5567 + set fd [safe_open_command $cmd] 5485 5568 fconfigure $fd -blocking 0 5486 5569 set i [reg_instance $fd] 5487 5570 filerun $fd [list readdifffiles $fd $serial $i] ··· 7270 7353 global web_browser 7271 7354 7272 7355 if {$web_browser eq {}} return 7273 - # Use eval here in case $web_browser is a command plus some arguments 7274 - if {[catch {eval exec $web_browser [list $url] &} err]} { 7356 + # Use concat here in case $web_browser is a command plus some arguments 7357 + if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} { 7275 7358 error_popup "[mc "Error starting web browser:"] $err" 7276 7359 } 7277 7360 } ··· 7777 7860 if {![info exists treefilelist($id)]} { 7778 7861 if {![info exists treepending]} { 7779 7862 if {$id eq $nullid} { 7780 - set cmd [list | git ls-files] 7863 + set cmd [list git ls-files] 7781 7864 } elseif {$id eq $nullid2} { 7782 - set cmd [list | git ls-files --stage -t] 7865 + set cmd [list git ls-files --stage -t] 7783 7866 } else { 7784 - set cmd [list | git ls-tree -r $id] 7867 + set cmd [list git ls-tree -r $id] 7785 7868 } 7786 - if {[catch {set gtf [open $cmd r]}]} { 7869 + if {[catch {set gtf [safe_open_command $cmd]}]} { 7787 7870 return 7788 7871 } 7789 7872 set treepending $id ··· 7847 7930 return 7848 7931 } 7849 7932 if {$diffids eq $nullid} { 7850 - if {[catch {set bf [open $f r]} err]} { 7933 + if {[catch {set bf [safe_open_file $f r]} err]} { 7851 7934 puts "oops, can't read $f: $err" 7852 7935 return 7853 7936 } 7854 7937 } else { 7855 7938 set blob [lindex $treeidlist($diffids) $i] 7856 - if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} { 7939 + if {[catch {set bf [safe_open_command [concat git cat-file blob $blob]]} err]} { 7857 7940 puts "oops, error reading blob $blob: $err" 7858 7941 return 7859 7942 } ··· 8003 8086 if {$i >= 0} { 8004 8087 if {[llength $ids] > 1 && $j < 0} { 8005 8088 # comparing working directory with some specific revision 8006 - set cmd [concat | git diff-index $flags] 8089 + set cmd [concat git diff-index $flags] 8007 8090 if {$i == 0} { 8008 8091 lappend cmd -R [lindex $ids 1] 8009 8092 } else { ··· 8011 8094 } 8012 8095 } else { 8013 8096 # comparing working directory with index 8014 - set cmd [concat | git diff-files $flags] 8097 + set cmd [concat git diff-files $flags] 8015 8098 if {$j == 1} { 8016 8099 lappend cmd -R 8017 8100 } ··· 8020 8103 if {[package vcompare $git_version "1.7.2"] >= 0} { 8021 8104 set flags "$flags --ignore-submodules=dirty" 8022 8105 } 8023 - set cmd [concat | git diff-index --cached $flags] 8106 + set cmd [concat git diff-index --cached $flags] 8024 8107 if {[llength $ids] > 1} { 8025 8108 # comparing index with specific revision 8026 8109 if {$j == 0} { ··· 8036 8119 if {$log_showroot} { 8037 8120 lappend flags --root 8038 8121 } 8039 - set cmd [concat | git diff-tree -r $flags $ids] 8122 + set cmd [concat git diff-tree -r $flags $ids] 8040 8123 } 8041 8124 return $cmd 8042 8125 } ··· 8048 8131 if {$limitdiffs && $vfilelimit($curview) ne {}} { 8049 8132 set cmd [concat $cmd -- $vfilelimit($curview)] 8050 8133 } 8051 - if {[catch {set gdtf [open $cmd r]}]} return 8134 + if {[catch {set gdtf [safe_open_command $cmd]}]} return 8052 8135 8053 8136 set treepending $ids 8054 8137 set treediff {} ··· 8168 8251 if {$limitdiffs && $vfilelimit($curview) ne {}} { 8169 8252 set cmd [concat $cmd -- $vfilelimit($curview)] 8170 8253 } 8171 - if {[catch {set bdf [open $cmd r]} err]} { 8254 + if {[catch {set bdf [safe_open_command $cmd]} err]} { 8172 8255 error_popup [mc "Error getting diffs: %s" $err] 8173 8256 return 8174 8257 } ··· 8886 8969 set id [lindex $matches 0] 8887 8970 } 8888 8971 } else { 8889 - if {[catch {set id [exec git rev-parse --verify $sha1string]}]} { 8972 + if {[catch {set id [safe_exec [list git rev-parse --verify $sha1string]]}]} { 8890 8973 error_popup [mc "Revision %s is not known" $sha1string] 8891 8974 return 8892 8975 } ··· 9192 9275 9193 9276 if {![info exists patchids($id)]} { 9194 9277 set cmd [diffcmd [list $id] {-p --root}] 9195 - # trim off the initial "|" 9196 - set cmd [lrange $cmd 1 end] 9197 9278 if {[catch { 9198 - set x [eval exec $cmd | git patch-id] 9279 + set x [safe_exec_redirect $cmd [list | git patch-id]] 9199 9280 set patchids($id) [lindex $x 0] 9200 9281 }]} { 9201 9282 set patchids($id) "error" ··· 9291 9372 set fna [file join $tmpdir "commit-[string range $a 0 7]"] 9292 9373 set fnb [file join $tmpdir "commit-[string range $b 0 7]"] 9293 9374 if {[catch { 9294 - exec git diff-tree -p --pretty $a >$fna 9295 - exec git diff-tree -p --pretty $b >$fnb 9375 + safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna] 9376 + safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb] 9296 9377 } err]} { 9297 9378 error_popup [mc "Error writing commit to file: %s" $err] 9298 9379 return 9299 9380 } 9300 9381 if {[catch { 9301 - set fd [open "| diff -U$diffcontext $fna $fnb" r] 9382 + set fd [safe_open_command "diff -U$diffcontext $fna $fnb"] 9302 9383 } err]} { 9303 9384 error_popup [mc "Error diffing commits: %s" $err] 9304 9385 return ··· 9438 9519 set newid [$patchtop.tosha1 get] 9439 9520 set fname [$patchtop.fname get] 9440 9521 set cmd [diffcmd [list $oldid $newid] -p] 9441 - # trim off the initial "|" 9442 - set cmd [lrange $cmd 1 end] 9443 - lappend cmd >$fname & 9444 - if {[catch {eval exec $cmd} err]} { 9522 + if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} { 9445 9523 error_popup "[mc "Error creating patch:"] $err" $patchtop 9446 9524 } 9447 9525 catch {destroy $patchtop} ··· 9510 9588 } 9511 9589 if {[catch { 9512 9590 if {$msg != {}} { 9513 - exec git tag -a -m $msg $tag $id 9591 + safe_exec [list git tag -a -m $msg $tag $id] 9514 9592 } else { 9515 - exec git tag $tag $id 9593 + safe_exec [list git tag $tag $id] 9516 9594 } 9517 9595 } err]} { 9518 9596 error_popup "[mc "Error creating tag:"] $err" $mktagtop ··· 9580 9658 if {$autosellen < 40} { 9581 9659 lappend cmd --abbrev=$autosellen 9582 9660 } 9583 - set reference [eval exec $cmd $rowmenuid] 9661 + set reference [safe_exec [concat $cmd $rowmenuid]] 9584 9662 9585 9663 clipboard clear 9586 9664 clipboard append $reference ··· 9630 9708 set id [$wrcomtop.sha1 get] 9631 9709 set cmd "echo $id | [$wrcomtop.cmd get]" 9632 9710 set fname [$wrcomtop.fname get] 9633 - if {[catch {exec sh -c $cmd >$fname &} err]} { 9711 + if {[catch {safe_exec_redirect [list sh -c $cmd] [list >$fname &]} err]} { 9634 9712 error_popup "[mc "Error writing commit:"] $err" $wrcomtop 9635 9713 } 9636 9714 catch {destroy $wrcomtop} ··· 9734 9812 nowbusy newbranch 9735 9813 update 9736 9814 if {[catch { 9737 - eval exec git branch $cmdargs 9815 + safe_exec [concat git branch $cmdargs] 9738 9816 } err]} { 9739 9817 notbusy newbranch 9740 9818 error_popup $err ··· 9775 9853 nowbusy renamebranch 9776 9854 update 9777 9855 if {[catch { 9778 - eval exec git branch $cmdargs 9856 + safe_exec [concat git branch $cmdargs] 9779 9857 } err]} { 9780 9858 notbusy renamebranch 9781 9859 error_popup $err ··· 9816 9894 } 9817 9895 } 9818 9896 9819 - eval exec git citool $tool_args & 9897 + safe_exec_redirect [concat git citool $tool_args] [list &] 9820 9898 9821 9899 array unset env GIT_AUTHOR_* 9822 9900 array set env $save_env ··· 9839 9917 update 9840 9918 # Unfortunately git-cherry-pick writes stuff to stderr even when 9841 9919 # no error occurs, and exec takes that as an indication of error... 9842 - if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { 9920 + if {[catch {safe_exec [list sh -c "git cherry-pick -r $rowmenuid 2>&1"]} err]} { 9843 9921 notbusy cherrypick 9844 9922 if {[regexp -line \ 9845 9923 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \ ··· 9901 9979 nowbusy revert [mc "Reverting"] 9902 9980 update 9903 9981 9904 - if [catch {exec git revert --no-edit $rowmenuid} err] { 9982 + if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] { 9905 9983 notbusy revert 9906 9984 if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\ 9907 9985 $err match files] { ··· 9977 10055 bind $w <Visibility> "grab $w; focus $w" 9978 10056 tkwait window $w 9979 10057 if {!$confirm_ok} return 9980 - if {[catch {set fd [open \ 9981 - [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} { 10058 + if {[catch {set fd [safe_open_command_redirect \ 10059 + [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} { 9982 10060 error_popup $err 9983 10061 } else { 9984 10062 dohidelocalchanges ··· 10049 10127 10050 10128 # check the tree is clean first?? 10051 10129 set newhead $headmenuhead 10052 - set command [list | git checkout] 10130 + set command [list git checkout] 10053 10131 if {[string match "remotes/*" $newhead]} { 10054 10132 set remote $newhead 10055 10133 set newhead [string range $newhead [expr [string last / $newhead] + 1] end] ··· 10063 10141 } else { 10064 10142 lappend command $newhead 10065 10143 } 10066 - lappend command 2>@1 10067 10144 nowbusy checkout [mc "Checking out"] 10068 10145 update 10069 10146 dohidelocalchanges 10070 10147 if {[catch { 10071 - set fd [open $command r] 10148 + set fd [safe_open_command_redirect $command [list 2>@1]] 10072 10149 } err]} { 10073 10150 notbusy checkout 10074 10151 error_popup $err ··· 10134 10211 } 10135 10212 nowbusy rmbranch 10136 10213 update 10137 - if {[catch {exec git branch -D $head} err]} { 10214 + if {[catch {safe_exec [list git branch -D $head]} err]} { 10138 10215 notbusy rmbranch 10139 10216 error_popup $err 10140 10217 return ··· 10325 10402 set cachedarcs 0 10326 10403 set allccache [file join $gitdir "gitk.cache"] 10327 10404 if {![catch { 10328 - set f [open $allccache r] 10405 + set f [safe_open_file $allccache r] 10329 10406 set allcwait 1 10330 10407 getcache $f 10331 10408 }]} return ··· 10334 10411 if {$allcwait} { 10335 10412 return 10336 10413 } 10337 - set cmd [list | git rev-list --parents] 10414 + set cmd [list git rev-list --parents] 10338 10415 set allcupdate [expr {$seeds ne {}}] 10339 10416 if {!$allcupdate} { 10340 10417 set ids "--all" ··· 10362 10439 if {$ids ne {}} { 10363 10440 if {$ids eq "--all"} { 10364 10441 set cmd [concat $cmd "--all"] 10442 + set fd [safe_open_command $cmd] 10365 10443 } else { 10366 - set cmd [concat $cmd --stdin [list "<<[join $ids "\n"]"]] 10444 + set cmd [concat $cmd --stdin] 10445 + set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]] 10367 10446 } 10368 - set fd [open $cmd r] 10369 10447 fconfigure $fd -blocking 0 10370 10448 incr allcommits 10371 10449 nowbusy allcommits ··· 10755 10833 set cachearc 0 10756 10834 set cachedarcs $nextarc 10757 10835 catch { 10758 - set f [open $allccache w] 10836 + set f [safe_open_file $allccache w] 10759 10837 puts $f [list 1 $cachedarcs] 10760 10838 run writecache $f 10761 10839 } ··· 11458 11536 11459 11537 if {![info exists cached_tagcontent($tag)]} { 11460 11538 catch { 11461 - set cached_tagcontent($tag) [exec git cat-file -p $tag] 11539 + set cached_tagcontent($tag) [safe_exec [list git cat-file -p $tag]] 11462 11540 } 11463 11541 } 11464 11542 $ctext insert end "[mc "Tag"]: $tag\n" bold ··· 12369 12447 set r $path_attr_cache($attr,$path) 12370 12448 } else { 12371 12449 set r "unspecified" 12372 - if {![catch {set line [exec git check-attr $attr -- $path]}]} { 12450 + if {![catch {set line [safe_exec [list git check-attr $attr -- $path]]}]} { 12373 12451 regexp "(.*): $attr: (.*)" $line m f r 12374 12452 } 12375 12453 set path_attr_cache($attr,$path) $r ··· 12396 12474 while {$newlist ne {}} { 12397 12475 set head [lrange $newlist 0 [expr {$lim - 1}]] 12398 12476 set newlist [lrange $newlist $lim end] 12399 - if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} { 12477 + if {![catch {set rlist [safe_exec [concat git check-attr $attr -- $head]]}]} { 12400 12478 foreach row [split $rlist "\n"] { 12401 12479 if {[regexp "(.*): $attr: (.*)" $row m path value]} { 12402 12480 if {[string index $path 0] eq "\""} { ··· 12448 12526 12449 12527 # on OSX bring the current Wish process window to front 12450 12528 if {[tk windowingsystem] eq "aqua"} { 12451 - exec osascript -e [format { 12529 + safe_exec [list osascript -e [format { 12452 12530 tell application "System Events" 12453 12531 set frontmost of processes whose unix id is %d to true 12454 12532 end tell 12455 - } [pid] ] 12533 + } [pid] ]] 12456 12534 } 12457 12535 12458 12536 # Unset GIT_TRACE var if set ··· 12700 12778 if {$i >= [llength $argv] && $revtreeargs ne {}} { 12701 12779 # no -- on command line, but some arguments (other than --argscmd) 12702 12780 if {[catch { 12703 - set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs] 12781 + set f [safe_exec [concat git rev-parse --no-revs --no-flags $revtreeargs]] 12704 12782 set cmdline_files [split $f "\n"] 12705 12783 set n [llength $cmdline_files] 12706 12784 set revtreeargs [lrange $revtreeargs 0 end-$n]