7676# #
7777# # Expected ownership / permissions:
7878# #
79- # # 1) System configuration directories, such as /etc/... or /usr/local/etc /...
79+ # # 1) System configuration directories, such as /etc/..., /run/..., or /usr/...
8080# # - directories and files should be owned by user root and group root
81+ # # - directories must not be group-writable or world-writable
8182# # - files must not be writable by "others"
8283# # - group-writable files are acceptable only when the file group is root
8384# #
8485# # 2) Per-user configuration directories, such as ~/...
8586# # - directories and files should be owned by the invoking user
87+ # # - directories must not be group-writable or world-writable
8688# # - files must not be writable by "others"
8789# # - group-writable files are acceptable only when the file group matches the
8890# # invoking process's current effective group
@@ -106,55 +108,65 @@ source_config_error() {
106108}
107109
108110source_config_read_stat () {
109- local stat_path =" $1 "
110- local -n out_user =" $2 "
111- local -n out_group =" $3 "
112- local -n out_perms =" $4 "
113- local stat_output
111+ local _path =" $1 "
112+ local -n _out_user =" $2 "
113+ local -n _out_group =" $3 "
114+ local -n _out_perms =" $4 "
115+ local _output
114116
115117 # # Quote 'stat' man page:
116118 # # > Due to shell aliases and built-in stat functions, using an unadorned stat
117119 # # > interactively or in a script may get you different functionality than
118120 # # > that described here. Invoke it via env (i.e., env stat ...) to avoid
119121 # # > interference from the shell.
120- stat_output=" $( env LC_ALL=C stat -L --format ' %U %G %a' -- " $stat_path " 2> /dev/null) " || return 1
121- read -r out_user out_group out_perms <<< " $stat_output" || return 1
122+ # #
123+ # # Space-delimited parsing assumes user/group names contain no spaces.
124+ # # POSIX and useradd prohibit this in practice. Alternative delimiters
125+ # # were considered but each has its own drawback:
126+ # # NUL: cannot be stored in bash variables, requiring process
127+ # # substitution which discards the stat exit status.
128+ # # newline: same practical-impossibility tier as space.
129+ # # colon: prohibited by /etc/passwd format, but not a kernel guarantee.
130+ # # The current approach is fail-closed: a space in a name would cause the
131+ # # ownership comparison to fail, not to silently accept a wrong owner.
132+ _output=" $( env LC_ALL=C stat -L --format ' %U %G %a' -- " $_path " 2> /dev/null) " || return 1
133+ read -r _out_user _out_group _out_perms <<< " $_output" || return 1
122134
123135 return 0
124136}
125137
126138source_config_resolve_path () {
127- local input_path =" $1 "
128- local -n out_resolved_path =" $2 "
129- local resolved_path
139+ local _path =" $1 "
140+ local -n _out_resolved =" $2 "
141+ local _resolved
130142
131- resolved_path =" $( env readlink -f -- " $input_path " 2> /dev/null) " || return 1
132- out_resolved_path =" $resolved_path "
143+ _resolved =" $( env readlink -f -- " $_path " 2> /dev/null) " || return 1
144+ _out_resolved =" $_resolved "
133145
134146 return 0
135147}
136148
137149source_config_get_expected_owner_group () {
138- local path_to_check =" $1 "
139- local config_kind =" $2 "
140- local -n out_user =" $3 "
141- local -n out_group =" $4 "
150+ local _path =" $1 "
151+ local _kind =" $2 "
152+ local -n _out_user =" $3 "
153+ local -n _out_group =" $4 "
142154
143- case " $config_kind " in
155+ case " $_kind " in
144156 system)
145- out_user =' root'
146- out_group =' root'
157+ _out_user =' root'
158+ _out_group =' root'
147159 ;;
148160 user)
149- case " $path_to_check " in
161+ case " $_path " in
150162 " $HOME " |" $HOME " /* )
151163 # # Relies on Bash dynamic scoping to read source_config()'s locals.
152- out_user =" $invoking_user "
153- out_group =" $invoking_group "
164+ _out_user =" $invoking_user "
165+ _out_group =" $invoking_group "
154166 ;;
155167 * )
156- out_user =' root'
157- out_group =' root'
168+ _out_user =' root'
169+ _out_group =' root'
158170 ;;
159171 esac
160172 ;;
@@ -199,8 +211,12 @@ source_config_check_directory() {
199211 fi
200212
201213 case " $actual_perms " in
214+ * [2367]? )
215+ source_config_error " Config directory '$directory_path ' is group-writable (mode $actual_perms )."
216+ return 1
217+ ;;
202218 * 2|* 3|* 6|* 7)
203- source_config_error " Config directory '$directory_path ' is world-writable."
219+ source_config_error " Config directory '$directory_path ' is world-writable (mode $actual_perms ) ."
204220 return 1
205221 ;;
206222 esac
@@ -314,8 +330,14 @@ source_config() {
314330 # # Dynamic scoping: source_config_error() updates this local variable.
315331 local rc=0
316332
317- invoking_user=" $( id --user --name) "
318- invoking_group=" $( id --group --name) "
333+ invoking_user=" $( id --user --name) " || {
334+ source_config_error " Could not determine invoking user."
335+ return 1
336+ }
337+ invoking_group=" $( id --group --name) " || {
338+ source_config_error " Could not determine invoking group."
339+ return 1
340+ }
319341
320342 shopt -q nullglob && nullglob_was_set=1
321343 shopt -s nullglob
@@ -333,12 +355,20 @@ source_config() {
333355 esac
334356
335357 case " $folder_item " in
336- /etc/* |/usr/local/etc /* )
358+ /etc/* |/run/ * |/usr /* )
337359 trusted_user=' root'
338360 trusted_group=' root'
339361 config_kind=' system'
340362 ;;
341363 " $HOME " /* )
364+ case " $HOME " in
365+ /* )
366+ ;;
367+ * )
368+ source_config_error " HOME is not set to an absolute path."
369+ break
370+ ;;
371+ esac
342372 trusted_user=" $invoking_user "
343373 trusted_group=" $invoking_group "
344374 config_kind=' user'
0 commit comments