Skip to content

Commit ac59cd1

Browse files
committed
Protect Tcl sub-interpreter creation with quarantine mechanism
Environment variables put in quarantine are restored to their original value early at modulecmd.tcl start so that modulercs and modulefiles see the environment they expect. But the Tcl sub-interpreters used to evaluate these scripts are created after this restore and their initialization reads variables like TCL_LIBRARY from the environment. If such a variable designates a broken Tcl installation, for instance activated by a loaded module, sub-interpreter creation fails which prevents modulerc and modulefile evaluation. Set quarantine variables to their defined runtime value or unset them during sub-interpreter creation, then restore their original value once interp is created. A variable set during creation then unset afterward is also unset in the created interp: the env entry recorded in this interp at creation time is not removed by the unset made in the master interp and a read attempt on it would raise a "no such variable" error. Variable name validation of the run_quarantine configuration option is factored into the getQuarantineVarList procedure, shared with the quarantine release code run at start time. As this procedure clears duplicate entries, a variable listed twice is no more unset when released from quarantine. Fixes #623 Assisted-by: Claude:claude-fable-5 Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
1 parent 0970a7e commit ac59cd1

6 files changed

Lines changed: 149 additions & 19 deletions

File tree

NEWS.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ Modules 5.7.0 (not yet released)
238238
:mconfig:`ignore_cache_subcmds` is changed with :subcmd:`config`
239239
sub-command, it sets the :envvar:`MODULES_IGNORE_CACHE_SUBCMDS`
240240
environment variable. (fix issue #563)
241+
* Protect the creation of the Tcl sub-interpreters used to evaluate modulercs
242+
and modulefiles with the quarantine mechanism. Environment variables listed
243+
in the :mconfig:`run_quarantine` configuration option are unset or set to
244+
the value of their corresponding :envvar:`MODULES_RUNENV_\<VAR\>` variable
245+
during this creation step, then restored to their original value. (fix
246+
issue #623)
241247

242248

243249
.. _5.6 release notes:

doc/source/module.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5880,6 +5880,15 @@ ENVIRONMENT
58805880
Original values of these environment variables set in quarantine are passed
58815881
to :file:`modulecmd.tcl` via :envvar:`__MODULES_QUAR_\<VAR\>` variables.
58825882

5883+
Each variable found in :envvar:`MODULES_RUN_QUARANTINE` is also set in
5884+
quarantine by :file:`modulecmd.tcl` during the creation of the Tcl
5885+
sub-interpreters used to evaluate modulercs and modulefiles. Variables are
5886+
unset or set to the value of their corresponding
5887+
:envvar:`MODULES_RUNENV_\<VAR\>` variable during this creation step, then
5888+
their original value is restored. It protects sub-interpreter initialization
5889+
from side-effect coming from current definition of these variables (e.g., a
5890+
``TCL_LIBRARY`` variable pointing at a broken Tcl installation).
5891+
58835892
This environment variable value supersedes the default value set in the
58845893
:mconfig:`run_quarantine` configuration option. It can be defined with the
58855894
:subcmd:`config` sub-command.
@@ -5888,6 +5897,9 @@ ENVIRONMENT
58885897

58895898
.. versionadded:: 4.1
58905899

5900+
.. versionchanged:: 5.7
5901+
Quarantine also applied during Tcl sub-interpreter creation
5902+
58915903
.. envvar:: MODULES_RUNENV_<VAR>
58925904

58935905
Value to set to environment variable :envvar:`<VAR>` for

tcl/envmngt.tcl.in

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,21 @@ proc isEnvVarDefined {var_name} {
14381438
return [info exists ::env($var_name)]
14391439
}
14401440

1441+
# Get list of valid variable names found in run_quarantine configuration
1442+
# option, cleared from duplicate entries. Warn about invalid names if asked
1443+
proc getQuarantineVarList {{report_bad 0}} {
1444+
set var_list {}
1445+
foreach var [split [getConf run_quarantine]] {
1446+
if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $var]} {
1447+
lappendNoDup var_list $var
1448+
} elseif {$report_bad && [string length $var]} {
1449+
reportWarning "Bad variable name set in MODULES_RUN_QUARANTINE\
1450+
($var)"
1451+
}
1452+
}
1453+
return $var_list
1454+
}
1455+
14411456
proc setEnvVarIfUndefined {var_name value} {
14421457
if {![isEnvVarDefined $var_name]} {
14431458
set ::env($var_name) $value

tcl/interp.tcl.in

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,41 @@ proc nimp {cmd args} {
2929
}
3030

3131
proc createInterp {itrp} {
32+
# set quarantine variables to their runtime value or unset them during
33+
# interp creation to protect this initialization step from their current
34+
# definition (e.g., a TCL_LIBRARY variable pointing at a broken Tcl
35+
# installation)
36+
set quar_var_list [getQuarantineVarList]
37+
foreach var $quar_var_list {
38+
if {[info exists ::env($var)]} {
39+
set quar_orig_val($var) $::env($var)
40+
}
41+
if {[info exists ::env(MODULES_RUNENV_$var)]} {
42+
reportDebug "Set '$var' environment variable in quarantine\
43+
($::env(MODULES_RUNENV_$var))"
44+
set ::env($var) $::env(MODULES_RUNENV_$var)
45+
} else {
46+
reportDebug "Unset '$var' environment variable in quarantine"
47+
unset -nocomplain ::env($var)
48+
}
49+
}
50+
3251
reportDebug "creating interp $itrp"
3352
interp create $itrp
53+
54+
# restore environment variables put in quarantine once interp is created
55+
foreach var $quar_var_list {
56+
if {[info exists quar_orig_val($var)]} {
57+
set ::env($var) $quar_orig_val($var)
58+
} else {
59+
unset -nocomplain ::env($var)
60+
# also unset variable in created interp if it was set during
61+
# creation: this env entry is not removed by the unset above and a
62+
# read attempt on it would raise a "no such variable" error
63+
##nagelfar ignore Found constant
64+
interp eval $itrp [list unset -nocomplain ::env($var)]
65+
}
66+
}
3467
}
3568

3669
# Get identifier name of current Tcl modulefile interpreter. An interp is

tcl/main.tcl.in

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -904,25 +904,19 @@ if {[catch {
904904
# put back quarantine variables in env, if quarantine mechanism supported
905905
if {[info exists env(__MODULES_QUARANTINE_SET)] &&\
906906
$env(__MODULES_QUARANTINE_SET) eq {1}} {
907-
foreach var [split [getConf run_quarantine]] {
908-
# check variable name is valid
909-
if {[regexp {^[A-Za-z_][A-Za-z0-9_]*$} $var]} {
910-
set quarvar __MODULES_QUAR_${var}
911-
# put back value
912-
if {[info exists env($quarvar)]} {
913-
reportDebug "Release '$var' environment variable from\
914-
quarantine ($env($quarvar))"
915-
set env($var) $env($quarvar)
916-
unset env($quarvar)
917-
# or unset env var if no value found in quarantine
918-
} elseif {[info exists env($var)]} {
919-
reportDebug "Unset '$var' environment variable after\
920-
quarantine"
921-
unset env($var)
922-
}
923-
} elseif {[string length $var]} {
924-
reportWarning "Bad variable name set in MODULES_RUN_QUARANTINE\
925-
($var)"
907+
foreach var [getQuarantineVarList 1] {
908+
set quarvar __MODULES_QUAR_${var}
909+
# put back value
910+
if {[info exists env($quarvar)]} {
911+
reportDebug "Release '$var' environment variable from\
912+
quarantine ($env($quarvar))"
913+
set env($var) $env($quarvar)
914+
unset env($quarvar)
915+
# or unset env var if no value found in quarantine
916+
} elseif {[info exists env($var)]} {
917+
reportDebug "Unset '$var' environment variable after\
918+
quarantine"
919+
unset env($var)
926920
}
927921
}
928922
}

testsuite/modules.00-init/110-quar.exp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,73 @@ reset_test_env
188188
} elseif {$verbose} {
189189
send_user "\tSkipping quarantine mechanism checks\n"
190190
}
191+
192+
193+
#
194+
# Quarantine protection of Tcl sub-interpreter creation (issue #623)
195+
#
196+
197+
# fake Tcl library directories whose init.tcl script leaves a mark in
198+
# environment when sourced by a sub-interpreter then raises an error to make
199+
# this sub-interpreter fall back to the genuine Tcl library. ::argv only
200+
# exists in the master interpreter, which also sources this script when
201+
# TCL_LIBRARY designates one of these directories
202+
set fakelib1 [file join [pwd] faketcllib1]
203+
set fakelib2 [file join [pwd] faketcllib2]
204+
foreach {fakelib mark} [list $fakelib1 lib1 $fakelib2 lib2] {
205+
file mkdir $fakelib
206+
set fid [open $fakelib/init.tcl w]
207+
puts $fid "if {!\[info exists ::argv\]} {"
208+
puts $fid " set ::env(TESTSUITE_FAKE_TCL_INIT) $mark"
209+
puts $fid "}"
210+
puts $fid "error {fake broken Tcl library}"
211+
close $fid
212+
}
213+
214+
# modulefile reporting mark left by fake Tcl library and TCL_LIBRARY value
215+
set modfile [file join [pwd] faketcllib.mod]
216+
set fid [open $modfile w]
217+
puts $fid {#%Module
218+
if {[info exists env(TESTSUITE_FAKE_TCL_INIT)]} {
219+
puts stderr "TESTSUITE_FAKE_TCL_INIT=$env(TESTSUITE_FAKE_TCL_INIT)"
220+
}
221+
if {[info exists env(TCL_LIBRARY)]} {
222+
puts stderr "TCL_LIBRARY=$env(TCL_LIBRARY)"
223+
}}
224+
close $fid
225+
226+
setenv_var TCL_LIBRARY $fakelib1
227+
228+
# no quarantine: sub-interpreter initialization is affected by the broken
229+
# Tcl library installation found in environment
230+
set ans [list]
231+
lappend ans "TESTSUITE_FAKE_TCL_INIT=lib1"
232+
lappend ans "TCL_LIBRARY=$fakelib1"
233+
testouterr_cmd sh "source $modfile" OK [join $ans \n]
234+
235+
# variable in quarantine: sub-interpreter creation is protected but variable
236+
# is restored afterward for modulefile evaluation
237+
setenv_var MODULES_RUN_QUARANTINE TCL_LIBRARY
238+
testouterr_cmd sh "source $modfile" OK "TCL_LIBRARY=$fakelib1"
239+
240+
# defined runtime value is applied during sub-interpreter creation
241+
setenv_var MODULES_RUNENV_TCL_LIBRARY $fakelib2
242+
set ans [list]
243+
lappend ans "TESTSUITE_FAKE_TCL_INIT=lib2"
244+
lappend ans "TCL_LIBRARY=$fakelib1"
245+
testouterr_cmd sh "source $modfile" OK [join $ans \n]
246+
247+
# variable initially unset with a defined runtime value: sub-interpreter
248+
# should not keep trace of the variable set during its creation once this
249+
# variable is unset after this step
250+
unsetenv_var TCL_LIBRARY
251+
testouterr_cmd sh "source $modfile" OK "TESTSUITE_FAKE_TCL_INIT=lib2"
252+
253+
254+
#
255+
# Clean up variables used in this test case
256+
#
257+
258+
file delete -force $fakelib1 $fakelib2 $modfile
259+
260+
reset_test_env

0 commit comments

Comments
 (0)