Skip to content

Commit 45fc6dd

Browse files
committed
Merge remote-tracking branch 'assisted-by-ai/claude/audit-helper-scripts-security-nGGkn'
2 parents 025da60 + 4e0193f commit 45fc6dd

1 file changed

Lines changed: 59 additions & 29 deletions

File tree

usr/libexec/helper-scripts/source_folder.bsh

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,15 @@
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

108110
source_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

126138
source_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

137149
source_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

Comments
 (0)