1#!/bin/sh 2# Ensure that cp -Z, -a and cp --preserve=context work properly. 3# In particular, test on a writable NFS partition. 4# Check also locally if --preserve=context, -a and --preserve=all 5# does work 6 7# Copyright (C) 2007-2023 Free Software Foundation, Inc. 8 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <https://www.gnu.org/licenses/>. 21 22. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src 23print_ver_ cp 24require_root_ 25require_selinux_ 26 27cwd=$(pwd) 28cleanup_() { cd /; umount "$cwd/mnt"; } 29 30# This context is special: it works even when mcstransd isn't running. 31ctx='root:object_r:tmp_t' 32mls_enabled_ && ctx="$ctx:s0" 33 34# Check basic functionality - before check on fixed context mount 35touch c || framework_failure_ 36chcon $ctx c || skip_ "Failed to set context: $ctx" 37cp -a c d 2>err || framework_failure_ 38cp --preserve=context c e || framework_failure_ 39cp --preserve=all c f || framework_failure_ 40ls -Z d | grep $ctx || fail=1 41# there must be no stderr output for -a 42compare /dev/null err || fail=1 43ls -Z e | grep $ctx || fail=1 44ls -Z f | grep $ctx || fail=1 45rm -f f 46 47# Check handling of existing dirs which requires specific handling 48# due to recursion, and was handled incorrectly in coreutils-8.22 49# Note standard permissions are updated for existing directories 50# in the destination, so SELinux contexts should be updated too. 51mkdir -p backup/existing_dir/ || framework_failure_ 52ls -Zd backup/existing_dir > ed_ctx || fail=1 53grep $ctx ed_ctx && framework_failure_ 54touch backup/existing_dir/file || framework_failure_ 55chcon $ctx backup/existing_dir/file || framework_failure_ 56# Set the dir context to ensure it is reset 57mkdir -p --context="$ctx" restore/existing_dir || framework_failure_ 58# Copy and ensure existing directories updated 59cp -a backup/. restore/ || fail=1 60ls -Zd restore/existing_dir > ed_ctx || fail=1 61grep $ctx ed_ctx && 62 { ls -lZd restore/existing_dir; fail=1; } 63 64# Check context preserved with directories created with --parents, 65# which was not handled before coreutils-8.27 66mkdir -p parents/a/b || framework_failure_ 67ls -Zd parents/a/b > ed_ctx || fail=1 68grep $ctx ed_ctx && framework_failure_ 69touch parents/a/b/file || framework_failure_ 70chcon $ctx parents/a/b || framework_failure_ 71# Set the dir context to ensure it is reset 72mkdir -p --context="$ctx" parents_dest/parents/a || framework_failure_ 73# Copy and ensure existing directories updated 74cp -r --parents --preserve=context parents/a/b/file parents_dest || fail=1 75# Check new context 76ls -Zd parents_dest/parents/a/b > ed_ctx || fail=1 77grep $ctx ed_ctx || 78 { ls -lZd parents_dest/parents/a/b; fail=1; } 79# Check updated context 80ls -Zd parents_dest/parents/a > ed_ctx || fail=1 81grep $ctx ed_ctx && 82 { ls -lZd parents_dest/parents/a; fail=1; } 83 84# Check restorecon (-Z) functionality for file and directory 85# Also make a dir with our known context 86mkdir c_d || framework_failure_ 87chcon $ctx c_d || framework_failure_ 88# Get the type of this known context for file and dir for tracing 89old_type_f=$(get_selinux_type c) 90old_type_d=$(get_selinux_type c_d) 91# Setup copies for manipulation with restorecon 92# and get the adjusted type for comparison 93cp -a c Z1 || fail=1 94cp -a c_d Z1_d || fail=1 95if restorecon Z1 Z1_d 2>restorecon.err \ 96 && compare /dev/null restorecon.err; then 97 new_type_f=$(get_selinux_type Z1) 98 new_type_d=$(get_selinux_type Z1_d) 99 100 # Ensure -Z sets the type like restorecon does 101 cp -Z c Z2 || fail=1 102 cpZ_type_f=$(get_selinux_type Z2) 103 test "$cpZ_type_f" = "$new_type_f" || fail=1 104 105 # Ensure -Z overrides -a and that dirs are handled too 106 cp -aZ c Z3 || fail=1 107 cp -aZ c_d Z3_d || fail=1 108 cpaZ_type_f=$(get_selinux_type Z3) 109 cpaZ_type_d=$(get_selinux_type Z3_d) 110 test "$cpaZ_type_f" = "$new_type_f" || fail=1 111 test "$cpaZ_type_d" = "$new_type_d" || fail=1 112 113 # Ensure -Z sets the type for existing files 114 mkdir -p existing/c_d || framework_failure_ 115 touch existing/c || framework_failure_ 116 cp -aZ c c_d existing || fail=1 117 cpaZ_type_f=$(get_selinux_type existing/c) 118 cpaZ_type_d=$(get_selinux_type existing/c_d) 119 test "$cpaZ_type_f" = "$new_type_f" || fail=1 120 test "$cpaZ_type_d" = "$new_type_d" || fail=1 121fi 122 123skip=0 124# Create a file system, then mount it with the context=... option. 125dd if=/dev/zero of=blob bs=8192 count=200 || skip=1 126mkdir mnt || skip=1 127mkfs -t ext2 -F blob || 128 skip_ "failed to create an ext2 file system" 129 130mount -oloop,context=$ctx blob mnt || skip=1 131test $skip = 1 \ 132 && skip_ "insufficient mount/ext2 support" 133 134cd mnt || framework_failure_ 135 136# Create files with hopefully different contexts 137echo > ../f || framework_failure_ 138echo > g || framework_failure_ 139test "$(stat -c%C ../f)" = "$(stat -c%C g)" && 140 skip_ "files on separate file systems have the same security context" 141 142# /bin/cp from coreutils-6.7-3.fc7 would fail this test by letting cp 143# succeed (giving no diagnostics), yet leaving the destination file empty. 144cp -a ../f g 2>err || fail=1 145test -s g || fail=1 # The destination file must not be empty. 146compare /dev/null err || fail=1 147 148# ===================================================== 149# Here, we expect cp to succeed and not warn with "Operation not supported" 150rm -f g 151echo > g 152cp --preserve=all ../f g 2>err || fail=1 153test -s g || fail=1 154grep "Operation not supported" err && fail=1 155 156# ===================================================== 157# The same as above except destination does not exist 158rm -f g 159cp --preserve=all ../f g 2>err || fail=1 160test -s g || fail=1 161grep "Operation not supported" err && fail=1 162 163# An alternative to the following approach would be to run in a confined 164# domain (maybe creating/loading it) that lacks the required permissions 165# to the file type. 166# Note: this test could also be run by a regular (non-root) user in an 167# NFS mounted directory. When doing that, I get this diagnostic: 168# cp: failed to set the security context of 'g' to 'system_u:object_r:nfs_t': \ 169# Operation not supported 170cat <<\EOF > exp || framework_failure_ 171cp: failed to set the security context of 172EOF 173 174rm -f g 175echo > g 176# ===================================================== 177# Here, we expect cp to fail, because it cannot set the SELinux 178# security context through NFS or a mount with fixed context. 179cp --preserve=context ../f g 2> out && fail=1 180# Here, we *do* expect the destination to be empty. 181compare /dev/null g || fail=1 182sed "s/ .g'.*//" out > k 183mv k out 184compare exp out || fail=1 185 186rm -f g 187echo > g 188# Check if -a option doesn't silence --preserve=context option diagnostics 189cp -a --preserve=context ../f g 2> out2 && fail=1 190# Here, we *do* expect the destination to be empty. 191compare /dev/null g || fail=1 192sed "s/ .g'.*//" out2 > k 193mv k out2 194compare exp out2 || fail=1 195 196for no_g_cmd in '' 'rm -f g'; do 197 # restorecon equivalent. Note even though the context 198 # returned from matchpathcon() will not match $ctx 199 # the resulting ENOTSUP warning will be suppressed. 200 201 # With absolute path 202 $no_g_cmd 203 cp -Z ../f $(realpath g) || fail=1 204 # With relative path 205 $no_g_cmd 206 cp -Z ../f g || fail=1 207 # -Z overrides -a 208 $no_g_cmd 209 cp -Z -a ../f g || fail=1 210 # -Z doesn't take an arg 211 $no_g_cmd 212 returns_ 1 cp -Z "$ctx" ../f g || fail=1 213 214 # Explicit context 215 $no_g_cmd 216 # Explicitly defaulting to the global $ctx should work 217 cp --context="$ctx" ../f g || fail=1 218 # --context overrides -a 219 $no_g_cmd 220 cp -a --context="$ctx" ../f g || fail=1 221done 222 223# Mutually exclusive options 224returns_ 1 cp -Z --preserve=context ../f g || fail=1 225returns_ 1 cp --preserve=context -Z ../f g || fail=1 226returns_ 1 cp --preserve=context --context="$ctx" ../f g || fail=1 227 228Exit $fail 229