Skip to content

Commit afca9a4

Browse files
committed
Merge branch 'ml/replace-auto-execok' into js/fix-open-exec
Signed-off-by: Taylor Blau <[email protected]>
2 parents 44e3935 + a1ccd25 commit afca9a4

File tree

4 files changed

+141
-109
lines changed

4 files changed

+141
-109
lines changed

git-gui.sh

Lines changed: 136 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -77,97 +77,126 @@ proc is_Cygwin {} {
7777

7878
######################################################################
7979
##
80-
## PATH lookup
80+
## PATH lookup. Sanitize $PATH, assure exec/open use only that
81+
82+
if {[is_Windows]} {
83+
set _path_sep {;}
84+
set _search_exe .exe
85+
} else {
86+
set _path_sep {:}
87+
set _search_exe {}
88+
}
89+
90+
if {[is_Windows]} {
91+
set gitguidir [file dirname [info script]]
92+
regsub -all ";" $gitguidir "\\;" gitguidir
93+
set env(PATH) "$gitguidir;$env(PATH)"
94+
}
8195
8296
set _search_path {}
83-
proc _which {what args} {
84-
global env _search_exe _search_path
97+
set _path_seen [dict create]
98+
foreach p [split $env(PATH) $_path_sep] {
99+
# Keep only absolute paths, getting rid of ., empty, etc.
100+
if {[file pathtype $p] ne {absolute}} {
101+
continue
102+
}
103+
# Keep only the first occurence of any duplicates.
104+
set norm_p [file normalize $p]
105+
if {[dict exists $_path_seen $norm_p]} {
106+
continue
107+
}
108+
dict set _path_seen $norm_p 1
109+
lappend _search_path $norm_p
110+
}
111+
unset _path_seen
85112
86-
if {$_search_path eq {}} {
87-
if {[is_Windows]} {
88-
set gitguidir [file dirname [info script]]
89-
regsub -all ";" $gitguidir "\\;" gitguidir
90-
set env(PATH) "$gitguidir;$env(PATH)"
91-
set _search_path [split $env(PATH) {;}]
92-
# Skip empty `PATH` elements
93-
set _search_path [lsearch -all -inline -not -exact \
94-
$_search_path ""]
95-
set _search_exe .exe
113+
set env(PATH) [join $_search_path $_path_sep]
114+
115+
if {[is_Windows]} {
116+
proc _which {what args} {
117+
global _search_exe _search_path
118+
119+
if {[lsearch -exact $args -script] >= 0} {
120+
set suffix {}
121+
} elseif {[string match *$_search_exe [string tolower $what]]} {
122+
# The search string already has the file extension
123+
set suffix {}
96124
} else {
97-
set _search_path [split $env(PATH) :]
98-
set _search_exe {}
125+
set suffix $_search_exe
99126
}
100-
}
101-
102-
if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
103-
set suffix {}
104-
} else {
105-
set suffix $_search_exe
106-
}
107127
108-
foreach p $_search_path {
109-
set p [file join $p $what$suffix]
110-
if {[file exists $p]} {
111-
return [file normalize $p]
128+
foreach p $_search_path {
129+
set p [file join $p $what$suffix]
130+
if {[file exists $p]} {
131+
return [file normalize $p]
132+
}
112133
}
134+
return {}
113135
}
114-
return {}
115-
}
116136
117-
proc sanitize_command_line {command_line from_index} {
118-
set i $from_index
119-
while {$i < [llength $command_line]} {
120-
set cmd [lindex $command_line $i]
121-
if {[llength [file split $cmd]] < 2} {
122-
set fullpath [_which $cmd]
123-
if {$fullpath eq ""} {
124-
throw {NOT-FOUND} "$cmd not found in PATH"
137+
proc sanitize_command_line {command_line from_index} {
138+
set i $from_index
139+
while {$i < [llength $command_line]} {
140+
set cmd [lindex $command_line $i]
141+
if {[llength [file split $cmd]] < 2} {
142+
set fullpath [_which $cmd]
143+
if {$fullpath eq ""} {
144+
throw {NOT-FOUND} "$cmd not found in PATH"
145+
}
146+
lset command_line $i $fullpath
147+
}
148+
149+
# handle piped commands, e.g. `exec A | B`
150+
for {incr i} {$i < [llength $command_line]} {incr i} {
151+
if {[lindex $command_line $i] eq "|"} {
152+
incr i
153+
break
154+
}
125155
}
126-
lset command_line $i $fullpath
127156
}
157+
return $command_line
158+
}
159+
160+
# Override `exec` to avoid unsafe PATH lookup
128161
129-
# handle piped commands, e.g. `exec A | B`
130-
for {incr i} {$i < [llength $command_line]} {incr i} {
131-
if {[lindex $command_line $i] eq "|"} {
162+
rename exec real_exec
163+
164+
proc exec {args} {
165+
# skip options
166+
for {set i 0} {$i < [llength $args]} {incr i} {
167+
set arg [lindex $args $i]
168+
if {$arg eq "--"} {
132169
incr i
133170
break
134171
}
172+
if {[string range $arg 0 0] ne "-"} {
173+
break
174+
}
135175
}
176+
set args [sanitize_command_line $args $i]
177+
uplevel 1 real_exec $args
136178
}
137-
return $command_line
138-
}
139179
140-
# Override `exec` to avoid unsafe PATH lookup
180+
# Override `open` to avoid unsafe PATH lookup
141181
142-
rename exec real_exec
182+
rename open real_open
143183
144-
proc exec {args} {
145-
# skip options
146-
for {set i 0} {$i < [llength $args]} {incr i} {
147-
set arg [lindex $args $i]
148-
if {$arg eq "--"} {
149-
incr i
150-
break
151-
}
152-
if {[string range $arg 0 0] ne "-"} {
153-
break
184+
proc open {args} {
185+
set arg0 [lindex $args 0]
186+
if {[string range $arg0 0 0] eq "|"} {
187+
set command_line [string trim [string range $arg0 1 end]]
188+
lset args 0 "| [sanitize_command_line $command_line 0]"
154189
}
190+
uplevel 1 real_open $args
155191
}
156-
set args [sanitize_command_line $args $i]
157-
uplevel 1 real_exec $args
158-
}
159192
160-
# Override `open` to avoid unsafe PATH lookup
161-
162-
rename open real_open
193+
} else {
194+
# On non-Windows platforms, auto_execok, exec, and open are safe, and will
195+
# use the sanitized search path. But, we need _which for these.
163196
164-
proc open {args} {
165-
set arg0 [lindex $args 0]
166-
if {[string range $arg0 0 0] eq "|"} {
167-
set command_line [string trim [string range $arg0 1 end]]
168-
lset args 0 "| [sanitize_command_line $command_line 0]"
197+
proc _which {what args} {
198+
return [lindex [auto_execok $what] 0]
169199
}
170-
uplevel 1 real_open $args
171200
}
172201
173202
# Wrap exec/open to sanitize arguments
@@ -354,15 +383,37 @@ if {$_trace >= 0} {
354383
# branches).
355384
set _last_merged_branch {}
356385
357-
proc shellpath {} {
358-
global _shellpath env
359-
if {[string match @@* $_shellpath]} {
360-
if {[info exists env(SHELL)]} {
361-
return $env(SHELL)
362-
} else {
363-
return /bin/sh
364-
}
386+
# for testing, allow unconfigured _shellpath
387+
if {[string match @@* $_shellpath]} {
388+
if {[info exists env(SHELL)]} {
389+
set _shellpath $env(SHELL)
390+
} else {
391+
set _shellpath /bin/sh
365392
}
393+
}
394+
395+
if {[is_Windows]} {
396+
set _shellpath [exec cygpath -m $_shellpath]
397+
}
398+
399+
if {![file executable $_shellpath] || \
400+
!([file pathtype $_shellpath] eq {absolute})} {
401+
set errmsg "The defined shell ('$_shellpath') is not usable, \
402+
it must be an absolute path to an executable."
403+
puts stderr $errmsg
404+
405+
catch {wm withdraw .}
406+
tk_messageBox \
407+
-icon error \
408+
-type ok \
409+
-title "git-gui: configuration error" \
410+
-message $errmsg
411+
exit 1
412+
}
413+
414+
415+
proc shellpath {} {
416+
global _shellpath
366417
return $_shellpath
367418
}
368419
@@ -574,32 +625,13 @@ proc _git_cmd {name} {
574625
return $v
575626
}
576627

577-
# Test a file for a hashbang to identify executable scripts on Windows.
578-
proc is_shellscript {filename} {
579-
if {![file exists $filename]} {return 0}
580-
set f [safe_open_file $filename r]
581-
fconfigure $f -encoding binary
582-
set magic [read $f 2]
583-
close $f
584-
return [expr {$magic eq "#!"}]
585-
}
586-
587-
# Run a command connected via pipes on stdout.
628+
# Run a shell command connected via pipes on stdout.
588629
# This is for use with textconv filters and uses sh -c "..." to allow it to
589-
# contain a command with arguments. On windows we must check for shell
590-
# scripts specifically otherwise just call the filter command.
630+
# contain a command with arguments. We presume this
631+
# to be a shellscript that the configured shell (/bin/sh by default) knows
632+
# how to run.
591633
proc open_cmd_pipe {cmd path} {
592-
global env
593-
if {![file executable [shellpath]]} {
594-
set exe [auto_execok [lindex $cmd 0]]
595-
if {[is_shellscript [lindex $exe 0]]} {
596-
set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
597-
} else {
598-
set run [concat $exe [lrange $cmd 1 end] $path]
599-
}
600-
} else {
601-
set run [list [shellpath] -c "$cmd \"\$0\"" $path]
602-
}
634+
set run [list [shellpath] -c "$cmd \"\$0\"" $path]
603635
set run [make_arglist_safe $run]
604636
return [open |$run r]
605637
}
@@ -2746,17 +2778,16 @@ if {![is_bare]} {
27462778

27472779
if {[is_Windows]} {
27482780
# Use /git-bash.exe if available
2749-
set normalized [file normalize $::argv0]
2750-
regsub "/mingw../libexec/git-core/git-gui$" \
2751-
$normalized "/git-bash.exe" cmdLine
2752-
if {$cmdLine != $normalized && [file exists $cmdLine]} {
2753-
set cmdLine [list "Git Bash" $cmdLine]
2781+
set _git_bash [exec cygpath -m /git-bash.exe]
2782+
if {[file executable $_git_bash]} {
2783+
set _bash_cmdline [list "Git Bash" $_git_bash]
27542784
} else {
2755-
set cmdLine [list "Git Bash" bash --login -l]
2785+
set _bash_cmdline [list "Git Bash" bash --login -l]
27562786
}
27572787
.mbar.repository add command \
27582788
-label [mc "Git Bash"] \
2759-
-command {safe_exec_bg [concat [list [auto_execok start]] $cmdLine]}
2789+
-command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]}
2790+
unset _git_bash
27602791
}
27612792

27622793
if {[is_Windows] || ![is_bare]} {

lib/shortcut.tcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ proc do_windows_shortcut {} {
1212
set fn ${fn}.lnk
1313
}
1414
# Use git-gui.exe if available (ie: git-for-windows)
15-
set cmdLine [auto_execok git-gui.exe]
15+
set cmdLine [list [_which git-gui]]
1616
if {$cmdLine eq {}} {
1717
set cmdLine [list [info nameofexecutable] \
1818
[file normalize $::argv0]]

lib/sshkey.tcl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ proc make_ssh_key {w} {
8383
set sshkey_title [mc "Generating..."]
8484
$w.header.gen configure -state disabled
8585

86-
set cmdline [list sh -c {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
86+
set cmdline [list [shellpath] -c \
87+
{echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
8788

8889
if {[catch { set sshkey_fd [safe_open_command $cmdline] } err]} {
8990
error_popup [mc "Could not start ssh-keygen:\n\n%s" $err]

lib/tools.tcl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ proc tools_exec {fullname} {
110110

111111
set cmdline $repo_config(guitool.$fullname.cmd)
112112
if {[is_config_true "guitool.$fullname.noconsole"]} {
113-
tools_run_silent [list sh -c $cmdline] \
113+
tools_run_silent [list [shellpath] -c $cmdline] \
114114
[list tools_complete $fullname {}]
115115
} else {
116116
regsub {/} $fullname { / } title
117117
set w [console::new \
118118
[mc "Tool: %s" $title] \
119119
[mc "Running: %s" $cmdline]]
120-
console::exec $w [list sh -c $cmdline] \
120+
console::exec $w [list [shellpath] -c $cmdline] \
121121
[list tools_complete $fullname $w]
122122
}
123123

0 commit comments

Comments
 (0)