summaryrefslogtreecommitdiffstats
path: root/tests/cp/cp-a-selinux.sh
blob: 0f94c23472d1a1766e3180e2ae1e42ee61681ff5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/bin/sh
# Ensure that cp -Z, -a and cp --preserve=context work properly.
# In particular, test on a writable NFS partition.
# Check also locally if --preserve=context, -a and --preserve=all
# does work

# Copyright (C) 2007-2020 Free Software Foundation, Inc.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ cp
require_root_
require_selinux_

cwd=$(pwd)
cleanup_() { cd /; umount "$cwd/mnt"; }

# This context is special: it works even when mcstransd isn't running.
ctx='root:object_r:tmp_t'
mls_enabled_ && ctx="$ctx:s0"

# Check basic functionality - before check on fixed context mount
touch c || framework_failure_
chcon $ctx c || skip_ "Failed to set context: $ctx"
cp -a c d 2>err || framework_failure_
cp --preserve=context c e || framework_failure_
cp --preserve=all c f || framework_failure_
ls -Z d | grep $ctx || fail=1
# there must be no stderr output for -a
compare /dev/null err || fail=1
ls -Z e | grep $ctx || fail=1
ls -Z f | grep $ctx || fail=1
rm -f f

# Check handling of existing dirs which requires specific handling
# due to recursion, and was handled incorrectly in coreutils-8.22
# Note standard permissions are updated for existing directories
# in the destination, so SELinux contexts should be updated too.
mkdir -p backup/existing_dir/ || framework_failure_
ls -Zd backup/existing_dir > ed_ctx || fail=1
grep $ctx ed_ctx && framework_failure_
touch backup/existing_dir/file || framework_failure_
chcon $ctx backup/existing_dir/file || framework_failure_
# Set the dir context to ensure it is reset
mkdir -p --context="$ctx" restore/existing_dir || framework_failure_
# Copy and ensure existing directories updated
cp -a backup/. restore/ || fail=1
ls -Zd restore/existing_dir > ed_ctx || fail=1
grep $ctx ed_ctx &&
  { ls -lZd restore/existing_dir; fail=1; }

# Check context preserved with directories created with --parents,
# which was not handled before coreutils-8.27
mkdir -p parents/a/b || framework_failure_
ls -Zd parents/a/b > ed_ctx || fail=1
grep $ctx ed_ctx && framework_failure_
touch parents/a/b/file || framework_failure_
chcon $ctx parents/a/b || framework_failure_
# Set the dir context to ensure it is reset
mkdir -p --context="$ctx" parents_dest/parents/a || framework_failure_
# Copy and ensure existing directories updated
cp -r --parents --preserve=context parents/a/b/file parents_dest || fail=1
# Check new context
ls -Zd parents_dest/parents/a/b > ed_ctx || fail=1
grep $ctx ed_ctx ||
  { ls -lZd parents_dest/parents/a/b; fail=1; }
# Check updated context
ls -Zd parents_dest/parents/a > ed_ctx || fail=1
grep $ctx ed_ctx &&
  { ls -lZd parents_dest/parents/a; fail=1; }

# Check restorecon (-Z) functionality for file and directory
# Also make a dir with our known context
mkdir c_d || framework_failure_
chcon $ctx c_d || framework_failure_
# Get the type of this known context for file and dir for tracing
old_type_f=$(get_selinux_type c)
old_type_d=$(get_selinux_type c_d)
# Setup copies for manipulation with restorecon
# and get the adjusted type for comparison
cp -a c Z1 || fail=1
cp -a c_d Z1_d || fail=1
if restorecon Z1 Z1_d 2>restorecon.err \
   && compare /dev/null restorecon.err; then
  new_type_f=$(get_selinux_type Z1)
  new_type_d=$(get_selinux_type Z1_d)

  # Ensure -Z sets the type like restorecon does
  cp -Z c Z2 || fail=1
  cpZ_type_f=$(get_selinux_type Z2)
  test "$cpZ_type_f" = "$new_type_f" || fail=1

  # Ensure -Z overrides -a and that dirs are handled too
  cp -aZ c Z3 || fail=1
  cp -aZ c_d Z3_d || fail=1
  cpaZ_type_f=$(get_selinux_type Z3)
  cpaZ_type_d=$(get_selinux_type Z3_d)
  test "$cpaZ_type_f" = "$new_type_f" || fail=1
  test "$cpaZ_type_d" = "$new_type_d" || fail=1

  # Ensure -Z sets the type for existing files
  mkdir -p existing/c_d || framework_failure_
  touch existing/c || framework_failure_
  cp -aZ c c_d existing || fail=1
  cpaZ_type_f=$(get_selinux_type existing/c)
  cpaZ_type_d=$(get_selinux_type existing/c_d)
  test "$cpaZ_type_f" = "$new_type_f" || fail=1
  test "$cpaZ_type_d" = "$new_type_d" || fail=1
fi

skip=0
# Create a file system, then mount it with the context=... option.
dd if=/dev/zero of=blob bs=8192 count=200    || skip=1
mkdir mnt                                    || skip=1
mkfs -t ext2 -F blob ||
  skip_ "failed to create an ext2 file system"

mount -oloop,context=$ctx blob mnt           || skip=1
test $skip = 1 \
  && skip_ "insufficient mount/ext2 support"

cd mnt                                       || framework_failure_

# Create files with hopefully different contexts
echo > ../f                                  || framework_failure_
echo > g                                     || framework_failure_
test "$(stat -c%C ../f)" = "$(stat -c%C g)" &&
  skip_ "files on separate file systems have the same security context"

# /bin/cp from coreutils-6.7-3.fc7 would fail this test by letting cp
# succeed (giving no diagnostics), yet leaving the destination file empty.
cp -a ../f g 2>err || fail=1
test -s g       || fail=1     # The destination file must not be empty.
compare /dev/null err || fail=1

# =====================================================
# Here, we expect cp to succeed and not warn with "Operation not supported"
rm -f g
echo > g
cp --preserve=all ../f g 2>err || fail=1
test -s g || fail=1
grep "Operation not supported" err && fail=1

# =====================================================
# The same as above except destination does not exist
rm -f g
cp --preserve=all ../f g 2>err || fail=1
test -s g || fail=1
grep "Operation not supported" err && fail=1

# An alternative to the following approach would be to run in a confined
# domain (maybe creating/loading it) that lacks the required permissions
# to the file type.
# Note: this test could also be run by a regular (non-root) user in an
# NFS mounted directory.  When doing that, I get this diagnostic:
# cp: failed to set the security context of 'g' to 'system_u:object_r:nfs_t': \
#   Operation not supported
cat <<\EOF > exp || framework_failure_
cp: failed to set the security context of
EOF

rm -f g
echo > g
# =====================================================
# Here, we expect cp to fail, because it cannot set the SELinux
# security context through NFS or a mount with fixed context.
cp --preserve=context ../f g 2> out && fail=1
# Here, we *do* expect the destination to be empty.
compare /dev/null g || fail=1
sed "s/ .g'.*//" out > k
mv k out
compare exp out || fail=1

rm -f g
echo > g
# Check if -a option doesn't silence --preserve=context option diagnostics
cp -a --preserve=context ../f g 2> out2 && fail=1
# Here, we *do* expect the destination to be empty.
compare /dev/null g || fail=1
sed "s/ .g'.*//" out2 > k
mv k out2
compare exp out2 || fail=1

for no_g_cmd in '' 'rm -f g'; do
  # restorecon equivalent.  Note even though the context
  # returned from matchpathcon() will not match $ctx
  # the resulting ENOTSUP warning will be suppressed.

   # With absolute path
  $no_g_cmd
  cp -Z ../f $(realpath g) || fail=1
   # With relative path
  $no_g_cmd
  cp -Z ../f g || fail=1
   # -Z overrides -a
  $no_g_cmd
  cp -Z -a ../f g || fail=1
   # -Z doesn't take an arg
  $no_g_cmd
  returns_ 1 cp -Z "$ctx" ../f g || fail=1

  # Explicit context
  $no_g_cmd
   # Explicitly defaulting to the global $ctx should work
  cp --context="$ctx" ../f g || fail=1
   # --context overrides -a
  $no_g_cmd
  cp -a --context="$ctx" ../f g || fail=1
done

# Mutually exclusive options
returns_ 1 cp -Z --preserve=context ../f g || fail=1
returns_ 1 cp --preserve=context -Z ../f g || fail=1
returns_ 1 cp --preserve=context --context="$ctx" ../f g || fail=1

Exit $fail