1#!/bin/sh
2# Copyright (c) 2009 IBM Corporation
3# Copyright (c) 2018 Petr Vorel <pvorel@suse.cz>
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License as
7# published by the Free Software Foundation; either version 2 of
8# the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it would be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18# Author: Mimi Zohar, zohar@ibm.vnet.ibm.com
19#
20# Verify that measurements are added to the measurement list based on policy.
21
22TST_NEEDS_CMDS="awk cut"
23TST_SETUP="setup"
24TST_CNT=3
25TST_NEEDS_DEVICE=1
26
27. ima_setup.sh
28
29setup()
30{
31	TEST_FILE="$PWD/test.txt"
32
33	POLICY="$IMA_DIR/policy"
34	[ -f "$POLICY" ] || tst_res TINFO "not using default policy"
35
36	DIGEST_INDEX=
37
38	local template="$(tail -1 $ASCII_MEASUREMENTS | cut -d' ' -f 3)"
39	local i
40
41	# https://www.kernel.org/doc/html/latest/security/IMA-templates.html#use
42	case "$template" in
43	ima|ima-ng|ima-sig) DIGEST_INDEX=4 ;;
44	*)
45		# using ima_template_fmt kernel parameter
46		local IFS="|"
47		i=4
48		for word in $template; do
49			if [ "$word" = 'd' -o "$word" = 'd-ng' ]; then
50				DIGEST_INDEX=$i
51				break
52			fi
53			i=$((i+1))
54		done
55	esac
56
57	[ -z "$DIGEST_INDEX" ] && tst_brk TCONF \
58		"Cannot find digest index (template: '$template')"
59
60	tst_res TINFO "IMA measurement tests assume tcb policy to be loaded (ima_policy=tcb)"
61}
62
63# TODO: find support for rmd128 rmd256 rmd320 wp256 wp384 tgr128 tgr160
64compute_digest()
65{
66	local algorithm="$1"
67	local file="$2"
68	local digest
69
70	digest="$(${algorithm}sum $file 2>/dev/null | cut -f1 -d ' ')"
71	if [ -n "$digest" ]; then
72		echo "$digest"
73		return 0
74	fi
75
76	digest="$(openssl $algorithm $file 2>/dev/null | cut -f2 -d ' ')"
77	if [ -n "$digest" ]; then
78		echo "$digest"
79		return 0
80	fi
81
82	# uncommon ciphers
83	local arg="$algorithm"
84	case "$algorithm" in
85	tgr192) arg="tiger" ;;
86	wp512) arg="whirlpool" ;;
87	esac
88
89	digest="$(rdigest --$arg $file 2>/dev/null | cut -f1 -d ' ')"
90	if [ -n "$digest" ]; then
91		echo "$digest"
92		return 0
93	fi
94	return 1
95}
96
97ima_check()
98{
99	local delimiter=':'
100	local algorithm digest expected_digest line
101
102	# need to read file to get updated $ASCII_MEASUREMENTS
103	cat $TEST_FILE > /dev/null
104
105	line="$(grep $TEST_FILE $ASCII_MEASUREMENTS | tail -1)"
106	if [ -z "$line" ]; then
107		tst_res TFAIL "cannot find measurement record for '$TEST_FILE'"
108		return
109	fi
110	tst_res TINFO "measurement record: '$line'"
111
112	digest=$(echo "$line" | cut -d' ' -f $DIGEST_INDEX)
113	if [ -z "$digest" ]; then
114		tst_res TFAIL "cannot find digest (index: $DIGEST_INDEX)"
115		return
116	fi
117
118	if [ "${digest#*$delimiter}" != "$digest" ]; then
119		algorithm=$(echo "$digest" | cut -d $delimiter -f 1)
120		digest=$(echo "$digest" | cut -d $delimiter -f 2)
121	else
122		case "${#digest}" in
123		32) algorithm="md5" ;;
124		40) algorithm="sha1" ;;
125		*)
126			tst_res TFAIL "algorithm must be either md5 or sha1 (digest: '$digest')"
127			return ;;
128		esac
129	fi
130	if [ -z "$algorithm" ]; then
131		tst_res TFAIL "cannot find algorithm"
132		return
133	fi
134	if [ -z "$digest" ]; then
135		tst_res TFAIL "cannot find digest"
136		return
137	fi
138
139	tst_res TINFO "computing digest for $algorithm algorithm"
140	expected_digest="$(compute_digest $algorithm $TEST_FILE)" || \
141		tst_brk TCONF "cannot compute digest for $algorithm algorithm"
142
143	if [ "$digest" = "$expected_digest" ]; then
144		tst_res TPASS "correct digest found"
145	else
146		tst_res TFAIL "digest not found"
147	fi
148}
149
150check_iversion_support()
151{
152	local device mount fs
153
154	tst_kvcmp -ge "4.16" && return 0
155
156	device="$(df . | sed -e 1d | cut -f1 -d ' ')"
157	mount="$(grep $device /proc/mounts | head -1)"
158	fs="$(echo $mount | awk '{print $3'})"
159
160	case "$fs" in
161	ext[2-4])
162		if ! echo "$mount" | grep -q -w "i_version"; then
163			tst_res TCONF "device '$device' is not mounted with iversion, please mount it with 'mount $device -o remount,iversion'"
164			return 1
165		fi
166		;;
167	xfs)
168		if dmesg | grep -q "XFS.*Mounting V[1-4] Filesystem"; then
169			tst_res TCONF "XFS Filesystem >= V5 required for iversion support"
170			return 1
171		fi
172		;;
173	'')
174		tst_res TWARN "could not find mount info for device '$device'"
175		;;
176	esac
177
178	return 0
179}
180
181test1()
182{
183	tst_res TINFO "verify adding record to the IMA measurement list"
184	ROD echo "$(date) this is a test file" \> $TEST_FILE
185	ima_check
186}
187
188test2()
189{
190
191	tst_res TINFO "verify updating record in the IMA measurement list"
192	check_iversion_support || return
193	ROD echo "$(date) modified file" \> $TEST_FILE
194	ima_check
195}
196
197test3()
198{
199	local user="nobody"
200	local dir="$PWD/user"
201	local file="$dir/test.txt"
202
203	# Default policy does not measure user files
204	tst_res TINFO "verify not measuring user files"
205	tst_check_cmds sudo
206
207	if ! id $user >/dev/null 2>/dev/null; then
208		tst_res TCONF "missing system user $user (wrong installation)"
209		return
210	fi
211
212	mkdir -m 0700 $dir
213	chown $user $dir
214	cd $dir
215	# need to read file to get updated $ASCII_MEASUREMENTS
216	sudo -n -u $user sh -c "echo $(date) user file > $file; cat $file > /dev/null"
217	cd ..
218
219	EXPECT_FAIL "grep $file $ASCII_MEASUREMENTS"
220}
221
222tst_run
223