Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ Modules 5.7.0 (not yet released)
shell meta-characters. Completion candidates were passed to ``compgen -W``
which evaluates command substitution syntax. (fix `CVE-2026-85013`_ found
by AISLE in partnership with Red Hat)
* Fix resolution of relative path entries in :envvar:`MODULEPATH` when a
module is loaded during a modulefile evaluation. Such entry was resolved
against the directory of the evaluating modulefile rather than against the
current working directory.

.. _CVE-2026-85013: https://github.com/envmodules/modules/security/advisories/GHSA-8hrw-p88g-qhmg

Expand Down
7 changes: 6 additions & 1 deletion doc/source/modulefile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2276,7 +2276,12 @@ reloaded or refreshed. This is especially important when the modulefile
updates an environment variable also altered by other modulefiles like
:envvar:`PATH`. As the order of the path elements in such variable defines
priority, it is important that this order does not change depending on the way
the modulefiles are loaded.
the modulefiles are loaded. Moreover when :mconfig:`conflict_unload` is
enabled, a conflicting loaded modulefile is unloaded at the time the
:mfcmd:`conflict`, :mfcmd:`family` or :mfcmd:`module unload<module>` command
is evaluated. Environment changes made prior this command by the loading
modulefile are overridden by this unload. For instance a variable set by the
loading modulefile ends up unset if it is also set by the unloaded modulefile.

:command:`module` keeps environment consistent which means a modulefile cannot
be loaded if its requirements are not loaded or if a conflicting module is
Expand Down
11 changes: 7 additions & 4 deletions tcl/mfcmd.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -1336,11 +1336,14 @@ proc is-saved {args} {
# test at least one of the directories passed as argument is set in MODULEPATH
proc is-used {args} {
set modpathlist [getModulePathList]
set rawmodpathlist [getModulePathList returnempty 0 0]
foreach path $args {
# transform given path in an absolute path to compare with dirs
# registered in the MODULEPATH env var which are returned absolute.
set abspath [getAbsolutePath $path]
if {$abspath in $modpathlist} {
# given path is first checked against the raw MODULEPATH content, as a
# relative entry there refers to the current working directory whereas
# given path is transformed in an absolute path relatively to the
# directory of the modulefile being evaluated
if {$path in $rawmodpathlist || [getAbsolutePath $path] in\
$modpathlist} {
return 1
}
}
Expand Down
4 changes: 3 additions & 1 deletion tcl/modfind.tcl.in
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,10 @@ proc getModulePathList {{behavior returnempty} {resolv_var 1} {set_abs 1}} {
if {$resolv_var} {
set modpath [resolvStringWithEnv $modpath]
}
# relative modulepath refers to current working directory of module
# command, not to the directory of the modulefile being evaluated
if {$set_abs} {
set modpath [getAbsolutePath $modpath]
set modpath [getAbsolutePath $modpath 1]
}
lappendNoDup modpath_list $modpath
}
Expand Down
5 changes: 3 additions & 2 deletions tcl/util.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ proc runCommand {cmd args} {
}
}

proc getAbsolutePath {path} {
proc getAbsolutePath {path {from_cwd 0}} {
# currently executing a modulefile or rc, so get the directory of this file
if {[currentState modulefile] ne {}} {
# unless resolution from current working directory is requested
if {!$from_cwd && [currentState modulefile] ne {}} {
set curdir [file dirname [currentState modulefile]]
# elsewhere get module command current working directory
} else {
Expand Down
18 changes: 18 additions & 0 deletions testsuite/modulefiles.4/bar/1
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ if {[info exists env(TESTSUITE_LCOMPAT)]} {
}
}
}
if {[info exists env(TESTSUITE_RELATIVE_MODULEPATH)]} {
switch -- $env(TESTSUITE_RELATIVE_MODULEPATH) {
load1 {
module load foo/1.0
}
prereq1 {
prereq foo/1.0
}
depon1 {
depends-on foo/1.0
}
isused1 {
setenv TS_ISUSED_RAW [is-used $env(MODULEPATH)]
setenv TS_ISUSED_REL [is-used ..]
setenv TS_ISUSED_UNK [is-used [file tail $env(MODULEPATH)]]
}
}
}
16 changes: 16 additions & 0 deletions testsuite/modulefiles.4/conun/1
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ if {[info exists env(TESTSUITE_CONFLICT_UNLOAD)]} {
depun_of_conun_is_sticky1 - depun_of_conun_is_super_sticky1 {
conflict conun
}
setenv_before_conflict1 {
setenv TSCONUN conun
conflict foo
}
setenv_after_conflict1 {
conflict foo
setenv TSCONUN conun
}
setenv_before_unload1 {
setenv TSCONUN conun
module unload foo
}
setenv_before_family1 {
setenv TSCONUN conun
family conun
}
implicit_default_off1 {
conflict conun
}
Expand Down
7 changes: 7 additions & 0 deletions testsuite/modulefiles.4/foo/1.0
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ if {[info exists env(TESTSUITE_CONFLICT_UNLOAD)]} {
depre_of_conun_is_conflict_of_reqlo1 {
conflict bar/1
}
setenv_before_conflict1 - setenv_after_conflict1 - setenv_before_unload1 {
setenv TSCONUN foo
}
setenv_before_family1 {
setenv TSCONUN foo
family conun
}
}
}
if {[info exists env(TESTSUITE_MODULEPATH_OPT)]} {
Expand Down
55 changes: 55 additions & 0 deletions testsuite/modules.50-cmds/660-conflict_unload.exp
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,61 @@ set tserr [msg_top_load_conun conun/1 {} conun/2 {} {} bar/1]
testouterr_cmd bash {load conun/1} $ans $tserr


#
# environment variable set by loading module prior conflict declaration
#

unsetenv_loaded_module
unsetenv_var __MODULES_LMPREREQ
unsetenv_var __MODULES_LMCONFLICT
unsetenv_var __MODULES_LMTAG
unsetenv_var __MODULES_LMALTNAME
unsetenv_var MODULES_FAMILY_CONUN

setenv_loaded_module [list foo/1.0] [list $mp/foo/1.0]
setenv_var TSCONUN foo

# conflicting module unload occurs when conflict is evaluated, so variable
# set by loading module before that is unset by this unload
setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_conflict1
set ans [list]
lappend ans [list set __MODULES_LMCONFLICT conun/1&foo]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list unset TSCONUN]
set tserr [msg_top_load_conun conun/1 {} {foo/1.0} {} {} {} {}]
testouterr_cmd bash {load conun/1} $ans $tserr

setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_unload1
testouterr_cmd bash {load conun/1} $ans $tserr

# variable is preserved if set after conflict declaration
setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_after_conflict1
set ans [list]
lappend ans [list set __MODULES_LMCONFLICT conun/1&foo]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list set TSCONUN conun]
testouterr_cmd bash {load conun/1} $ans $tserr

setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_family1
setenv_var MODULES_FAMILY_CONUN foo
setenv_var __MODULES_LMALTNAME foo/1.0&al|conun
set ans [list]
lappend ans [list set MODULES_FAMILY_CONUN conun]
lappend ans [list set __MODULES_LMCONFLICT conun/1&conun]
lappend ans [list set __MODULES_LMALTNAME conun/1&al|conun]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list unset TSCONUN]
lappend ans [list set LMOD_FAMILY_CONUN conun]
testouterr_cmd bash {load conun/1} $ans $tserr

unsetenv_var MODULES_FAMILY_CONUN
unsetenv_var __MODULES_LMALTNAME
unsetenv_var TSCONUN


#
# Disabled implicit default
#
Expand Down
66 changes: 66 additions & 0 deletions testsuite/modules.50-cmds/745-modulepath-relative.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
##############################################################################
# Modules Revision 3.0
# Providing a flexible user environment
#
# File: modules.50-cmds/%M%
# Revision: %I%
# First Edition: 2026/09/15
# Last Mod.: %U%, %G%
#
# Authors: Xavier Delaruelle, xavier.delaruelle@cea.fr
#
# Description: Testuite testsequence
# Command: load
# Modulefiles: bar, foo
# Sub-Command: is-used
#
# Comment: %C{
# Test module load made during a modulefile evaluation when
# modulepath is set as a relative path
# }C%
#
##############################################################################

skip_if_quick_mode

set mp $modpath.4
set mpre $modpathre.4

# modulepath relative to current working directory of module command
set relmp [file tail $env(TESTSUITEDIR)]/[file tail $mp]

setenv_var MODULES_AUTO_HANDLING 1

set ans [list]
lappend ans [list set __MODULES_LMPREREQ bar/1&foo/1.0]
lappend ans [list set _LMFILES_ $mp/foo/1.0:$mp/bar/1]
lappend ans [list set LOADEDMODULES foo/1.0:bar/1]
lappend ans [list set __MODULES_LMTAG foo/1.0&auto-loaded]
set tserr [msg_top_load bar/1 {} foo/1.0 {}]

# is-used given the raw relative entry or a path relative to the modulefile
# directory, which is not the current working directory
set ans2 [list]
lappend ans2 [list set TS_ISUSED_UNK 0]
lappend ans2 [list set TS_ISUSED_RAW 1]
lappend ans2 [list set TS_ISUSED_REL 1]
lappend ans2 [list set _LMFILES_ $mp/bar/1]
lappend ans2 [list set LOADEDMODULES bar/1]

foreach modulepath [list $relmp ./$relmp] {
setenv_path_var MODULEPATH $modulepath
foreach tscase {load1 prereq1 depon1} {
setenv_var TESTSUITE_RELATIVE_MODULEPATH $tscase
testouterr_cmd bash {load bar/1} $ans $tserr
}

setenv_var TESTSUITE_RELATIVE_MODULEPATH isused1
testouterr_cmd bash {load bar/1} $ans2 {}
}


#
# Cleanup
#

reset_test_env