]> git.proxmox.com Git - mirror_lxc.git/blob - src/tests/lxc-test-usernsexec
Merge pull request #4003 from brauner/2021-10-19.fixes
[mirror_lxc.git] / src / tests / lxc-test-usernsexec
1 #!/bin/bash
2 #
3 # This is a bash test case to test lxc-usernsexec.
4 # It basically supports usring lxc-usernsexec to execute itself
5 # and then create files and check that their ownership is as expected.
6 #
7 # It requires that the current user has at least 1 value in subuid and /etc/subgid
8 TEMP_D=""
9 VERBOSITY=0
10 set -f
11
12 fail() { echo "$@" 1>&2; exit 1; }
13 error() { echo "$@" 1>&2; }
14 skip() {
15 error "SKIP:" "$@"
16 exit 0
17 }
18 debug() {
19 local level=${1}; shift;
20 [ "${level}" -gt "${VERBOSITY}" ] && return
21 error "${@}"
22 }
23
24 collect_owners() {
25 # collect_owners([--dir=dir], file1, file2 ...)
26 # set _RET to a space delimited array of
27 # <file1>:owner:group <file2>:owner:group ...
28 local out="" ret="" dir=""
29 if [ "${1#--dir=}" != "$1" ]; then
30 dir="${1#--dir=}"
31 shift
32 fi
33 for arg in "$@"; do
34 # drop the :* so that input can be same as touch_files.
35 out=$(stat --format "%n:%u:%g" "${dir}${arg}") || {
36 error "failed to stat ${arg}"
37 return 1;
38 }
39 ret="$ret ${out##*/}"
40 done
41 _RET="${ret# }"
42 }
43
44 cleanup() {
45 if [ -d "$TEMP_D" ]; then
46 rm -Rf "$TEMP_D"
47 fi
48 }
49
50 touch_files() {
51 # touch_files tok [tok ...]
52 # tok is filename:chown_id:chown_gid
53 # if chown_id or chown_gid is empty, then chown will do the right thing
54 # and only change the provided value.
55 local args="" tok="" fname="" uidgid=""
56 args=( "$@" )
57 for tok in "$@"; do
58 fname=${tok%%:*}
59 uidgid=${tok#$fname}
60 uidgid=${uidgid#:}
61 : > "$fname" || { error "failed to create $fname"; return 1; }
62 [ -z "$uidgid" ] && continue
63 chown $uidgid "$fname" || { error "failed to chmod '$uidgid' $fname ($?)"; return 1; }
64 done
65 }
66
67 inside_cleanup() {
68 local f=""
69 rm -f "${FILES[@]}"
70 echo "$STATUS" >&5
71 echo "$STATUS" >&6
72 }
73
74 set_files() {
75 local x=""
76 FILES=( )
77 for x in "$@"; do
78 FILES[${#FILES[@]}]="${x%%:*}"
79 done
80 }
81
82 inside() {
83 # this what gets run inside the usernsexec environment.
84 # basically expects arguments of <filename>:uid:gid
85 # it will create the file, and then chmod it to the provided uid:gid
86 # it writes to file descriptor 5 a single line with space delimited
87 # exit_value uid gid [<filename>:<owner>:<group> ... ]
88 STATUS=127
89 trap inside_cleanup EXIT
90 local uid="" gid="" x=""
91
92 uid=$(id -u) || fail "failed execution of id -u"
93 gid=$(id -g) || fail "failed execution of id -g"
94
95 set_files "$@"
96
97 touch_files "$@" || fail "failed to create files"
98
99 collect_owners "${FILES[@]}" || fail "failed to collect owners"
100 result="$_RET"
101
102 # tell caller we are done.
103 echo "0" "$uid" "$gid" "$result" >&5
104 STATUS=0
105
106 # let the caller do things while the files are around.
107 read -t 30 x <&6
108
109 exit
110 }
111
112 runtest() {
113 # runtest(mydir, nsexec_args, [inside [...]])
114 # - use 'mydir' as a working dir.
115 # - execute lxc-usernsexec $nsexec_args -- <self> inside <inside args>
116 #
117 # write to stdout
118 # exit_value inside_exit_value inside_uid:inside_gid <results>
119 #
120 # where results are a list of space separated
121 # filename:uid:gid
122 # for each file passed in inside_args
123 [ $# -ge 3 ] || { error "runtest expects 2 args"; return 1; }
124 local mydir="$1" nsexec_args="$2"
125 shift 2
126 local ret inside_owners t=""
127 KIDPID=""
128
129 mkfifo "${mydir}/5" && exec 5<>"${mydir}/5" || return
130 mkfifo "${mydir}/6" && exec 6<>"${mydir}/6" || return
131 mkdir --mode=777 "${mydir}/work" || return
132 cd "${mydir}/work"
133
134 set_files "$@"
135
136 local results="" oresults="" iresults="" iuid="" igid="" n=0
137
138 error "$" $USERNSEXEC ${nsexec_args} -- "$MYPATH" inside "$*"
139 ${USERNSEXEC} ${nsexec_args} -- "$MYPATH" inside "$@" &
140 KIDPID=$!
141
142 [ -d "/proc/$KIDPID" ] || {
143 wait $KIDPID
144 fail "kid $KIDPID died quickly $?"
145 }
146
147 # if lxc-usernsexec fails to execute MYPATH inside, then
148 # the read below would timeout. To avoid a long timeout,
149 # we do a short timeout and check the pid is alive.
150 while ! read -t 1 ret iuid igid inside_owners <&5; do
151 n=$((n+1))
152 if [ ! -d "/proc/$KIDPID" ]; then
153 wait $KIDPID
154 fail "kid $KIDPID is gone $?"
155 fi
156 [ $n -ge 30 ] && fail "child never wrote to pipe"
157 done
158 iresults=( $inside_owners )
159
160 collect_owners "--dir=${mydir}/work/" "${FILES[@]}" || return
161 oresults=( $_RET )
162
163 echo 0 >&6
164 wait
165
166 ret=$?
167
168 results=( )
169 for((i=0;i<${#iresults[@]};i++)); do
170 results[$i]="${oresults[$i]}:${iresults[$i]#*:}"
171 done
172
173 echo 0 $ret "$iuid:$igid" "${results[@]}"
174 }
175
176 runcheck() {
177 local name="$1" expected="$2" nsexec_args="$3" found=""
178 shift 3
179 mkdir "${TEMP_D}/$name" || fail "failed mkdir <TEMP_D>/$name.d"
180 local err="${TEMP_D}/$name.err"
181 out=$("$MYPATH" runtest "${TEMP_D}/$name" "$nsexec_args" "$@" 2>"$err") || {
182 error "$name: FAIL - runtest failed $?"
183 [ -n "$out" ] && error " $out"
184 sed 's,^, ,' "$err" 1>&2
185 ERRORS="${ERRORS} $name"
186 return 1
187 }
188 set -- $out
189 local parentrc=$1 kidrc=$2 iuidgid="$3" found=""
190 shift 3
191 found="$*"
192 [ "$parentrc" = "0" -a "$kidrc" = "0" ] || {
193 error "$name: FAIL - parentrc=$parentrc kidrc=$kidrc found=$found"
194 ERRORS="${ERRORS} $name"
195 return 1
196 }
197 [ "$expected" = "$found" ] && {
198 error "$name: PASS"
199 PASS="${PASSES} $name"
200 return 0
201 }
202 echo "$name: FAIL expected '$expected' != found '$found'"
203 FAILS="${FAILS} $name"
204 return 1
205 }
206
207 setup_Usage() {
208 cat <<EOF
209 ${0} setup_and_run [-- run-args]
210
211 setup the system by creating a user (default is '${asuser:-test-userns}')
212 and then run test as that user. Must be root.
213
214 If user exists, then do not create the user.
215
216 -v | --verbose - be more verbose
217 --create-subuid=UID:RANGE
218 --create-subgid=UID:RANGE if adding subuid/subgid use this START:RANGE
219 example (default) 3000000000:5
220 EOF
221 }
222
223 setup_and_run() {
224 local short_opts="hv"
225 local long_opts="help,user:,create-subuid:,create-subgid:,verbose"
226 local getopt_out=""
227 getopt_out=$(getopt --name "${0##*/}" \
228 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
229 eval set -- "${getopt_out}" ||
230 { bad_Usage; return; }
231
232 local cur="" next="" asuser="test-userns"
233 local create_subuid="3000000000:5" create_subgid="3000000000:5"
234 while [ $# -ne 0 ]; do
235 cur="$1"; next="$2";
236 case "$cur" in
237 -h|--help) setup_Usage ; exit 0;;
238 --user) asuser="$next"; shift;;
239 --create-subuid) create_subuid=$next; shift;;
240 --create-subgid) create_subgid=$next; shift;;
241 -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
242 --) shift; break;;
243 esac
244 shift;
245 done
246
247 local pt_args=""
248 pt_args=( "$@" )
249
250 if [ "$(id -u)" != "0" ]; then
251 error "Sorry, setup_and_run has to be done as root, not uid=$(id -u)"
252 return 1
253 fi
254
255 local home="/home/$asuser"
256 if [ ! -d "$home" ]; then
257 debug 1 "creating user $asuser"
258 useradd "$asuser" --create-home "--home-dir=$home" || {
259 error "failed to create $asuser"
260 return 1
261 }
262 else
263 debug 1 "$asuser existed"
264 fi
265
266 local subuid="" subgid=""
267 subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$asuser" /etc/subuid) || {
268 error "failed to read /etc/subuid for $asuser"
269 return 1
270 }
271
272 if [ -n "$subuid" ]; then
273 debug 1 "$asuser already had subuid=$subuid"
274 else
275 debug 1 "adding $asuser:$create_subuid to /etc/subuid"
276 echo "$asuser:$create_subuid" >> /etc/subuid || {
277 error "failed to add $asuser to /etc/subuid"
278 }
279 fi
280
281 subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$asuser" /etc/subgid) || {
282 error "failed to read /etc/subgid for $asuser"
283 return 1
284 }
285 if [ -n "$subgid" ]; then
286 debug 1 "$asuser already had subgid=$subgid"
287 else
288 debug 1 "adding $asuser:$create_subgid to /etc/subgid"
289 echo "$asuser:$create_subgid" >> /etc/subgid || {
290 error "failed to add $asuser to /etc/subgid"
291 }
292 fi
293
294 debug 0 "as $asuser executing ${MYPATH} ${pt_args[*]}"
295 sudo -Hu "$asuser" ASAN_OPTIONS=${ASAN_OPTIONS:-} UBSAN_OPTIONS=${UBSAN_OPTIONS:-} "${MYPATH}" "${pt_args[@]}"
296 }
297
298 USERNSEXEC=${USERNSEXEC:-lxc-usernsexec}
299 MYPATH=$(readlink -f "$0") || { echo "failed to get full path to self: $0"; exit 1; }
300 export MYPATH
301
302 if [ "$1" = "inside" ]; then
303 shift
304 inside "$@"
305 exit
306 elif [ "$1" = "runtest" ]; then
307 shift
308 runtest "$@"
309 exit
310 elif [ "$1" = "setup_and_run" ]; then
311 shift
312 setup_and_run "$@"
313 exit
314 fi
315
316 name=$(id --user --name) || fail "failed to get username"
317 if [ "$name" = "root" ]; then
318 setup_and_run "$@"
319 exit
320 fi
321
322 subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subuid) &&
323 [ -n "$subuid" ] || fail "did not find $name in /etc/subuid"
324
325 subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subgid) &&
326 [ -n "$subgid" ] || fail "did not find $name in /etc/subgid"
327
328
329 uid=$(id --user) || fail "failed to get uid"
330 gid=$(id --group) || fail "failed to get gid"
331
332 mapuid="u:0:$uid:1"
333 mapgid="g:0:$gid:1"
334
335 ver=$(dpkg-query --show lxc-utils | awk '{print $2}')
336 error "uid=$uid gid=$gid name=$name subuid=$subuid subgid=$subgid ver=$ver"
337 error "lxc-utils=$ver kver=$(uname -r)"
338 error "USERNSEXEC=$USERNSEXEC"
339
340 TEMP_D=$(mktemp -d)
341 trap cleanup EXIT
342
343 PASSES=""; FAILS=""; ERRORS=""
344 runcheck nouidgid "f0:$subuid:$subgid:0:0" "" f0
345
346 runcheck myuidgid "f0:$uid:$gid:0:0" \
347 "-m$mapuid -m$mapgid" f0
348
349 runcheck subuidgid \
350 "f0:$subuid:$subgid:0:0" \
351 "-mu:0:$subuid:1 -mg:0:$subgid:1" f0:0:0
352
353 runcheck bothsets "f0:$uid:$gid:0:0 f1:$subuid:$subgid:1:1 f2:$uid:$subgid:0:1" \
354 "-m$mapuid -m$mapgid -mu:1:$subuid:1 -mg:1:$subgid:1" \
355 f0 f1:1:1 f2::1
356
357 runcheck mismatch "f0:$uid:$subgid:0:0 f1:$subuid:$gid:15:31" \
358 "-mu:0:$uid:1 -mg:0:$subgid:1 -mu:15:$subuid:1 -mg:31:$gid:1" \
359 f0 f1:15:31
360
361 FAILS=${FAILS# }
362 ERRORS=${ERRORS# }
363 PASSES=${PASSES# }
364
365 [ -z "${FAILS}" ] || error "FAILS: ${FAILS}"
366 [ -z "${ERRORS}" ] || error "ERRORS: ${ERRORS}"
367 [ -z "${FAILS}" -a -z "${ERRORS}" ] || exit 1
368 exit 0