github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/tests/integration/mounts_sshfs.bats (about) 1 #!/usr/bin/env bats 2 3 load helpers 4 5 function setup() { 6 setup_busybox 7 } 8 9 function teardown() { 10 if [ -v DIR ]; then 11 # Some distros do not have fusermount installed 12 # as a dependency of fuse-sshfs, and good ol' umount works. 13 fusermount -u "$DIR" || umount "$DIR" 14 unset DIR 15 fi 16 17 teardown_bundle 18 } 19 20 function sshfs_has_flag() { 21 if [ -v DIR ]; then 22 awk '$2 == "'"$DIR"'" { print $4 }' </proc/self/mounts | grep -E "\b$1\b" 23 return "$?" 24 fi 25 } 26 27 function setup_sshfs() { 28 # Create a fuse-sshfs mount (or, failing that, a tmpfs mount). 29 local sshfs="sshfs 30 -o UserKnownHostsFile=/dev/null 31 -o StrictHostKeyChecking=no 32 -o PasswordAuthentication=no" 33 34 if ! [ -v DIR ]; then 35 DIR="$BATS_RUN_TMPDIR/fuse-sshfs" 36 mkdir -p "$DIR" 37 # Make sure we clear all superblock flags to make sure bind-mounts can 38 # unset these flags. 39 if ! $sshfs -o rw,suid,dev,exec,atime rootless@localhost: "$DIR"; then 40 # fallback to tmpfs if running in without sshfs 41 mount -t tmpfs -o rw,suid,dev,exec,diratime,strictatime tmpfs "$DIR" 42 fi 43 fi 44 # Reset atime flags. "diratime" is quite a strange flag, so we need to make 45 # sure it's cleared before we apply the requested flags. 46 mount --bind -o remount,diratime,atime,strictatime "$DIR" 47 # We need to set the mount flags separately on the mount because some mount 48 # flags (such as "ro") are set on the superblock if you do them in the 49 # initial mount, which means that they cannot be cleared by bind-mounts. 50 # 51 # This also lets us reconfigure the per-mount settings on each call. 52 mount --bind -o "remount,$1" "$DIR" 53 echo "configured $DIR with mount --bind -o remount,$1" >&2 54 awk '$2 == "'"$DIR"'"' </proc/self/mounts >&2 55 } 56 57 function setup_sshfs_bind_flags() { 58 host_flags="$1" # ro,nodev,nosuid 59 bind_flags="$2" # ro,nosuid,bind 60 61 setup_sshfs "$host_flags" 62 63 cat >"rootfs/find-tmp.awk" <<-'EOF' 64 #!/bin/awk -f 65 $2 == "/mnt" { print $4 } 66 EOF 67 chmod +x "rootfs/find-tmp.awk" 68 69 update_config '.process.args = ["sh", "-c", "/find-tmp.awk </proc/self/mounts"]' 70 update_config '.mounts = (.mounts | map(select(.destination != "/mnt"))) + [{ 71 "source": "'"$DIR"'", 72 "destination": "/mnt", 73 "type": "bind", 74 "options": '"$(jq -cRM 'split(",")' <<<"$bind_flags")"' 75 }]' 76 } 77 78 function pass_sshfs_bind_flags() { 79 setup_sshfs_bind_flags "$@" 80 81 runc run test_busybox 82 [ "$status" -eq 0 ] 83 mnt_flags="$output" 84 } 85 86 function fail_sshfs_bind_flags() { 87 setup_sshfs_bind_flags "$@" 88 89 runc run test_busybox 90 [ "$status" -ne 0 ] 91 [[ "$output" == *"runc run failed: unable to start container process: error during container init: error mounting"*"operation not permitted"* ]] 92 } 93 94 @test "runc run [mount(8)-like behaviour: --bind with no options]" { 95 requires root 96 97 pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind" 98 # If no flags were specified alongside bind, we keep all existing flags. 99 # Unspecified flags must be cleared (rw default). 100 run -0 grep -wq ro <<<"$mnt_flags" 101 run ! grep -wq rw <<<"$mnt_flags" 102 run -0 grep -wq noexec <<<"$mnt_flags" 103 run -0 grep -wq nodiratime <<<"$mnt_flags" 104 # On old systems, mount doesn't know about nosymfollow, which turns the 105 # flag into a data argument (which is ignored by MS_REMOUNT). 106 if sshfs_has_flag nosymfollow; then run -0 grep -wq nosymfollow <<<"$mnt_flags"; fi 107 108 # Now try with a user namespace. The results should be the same as above. 109 update_config ' .linux.namespaces += [{"type": "user"}] 110 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 111 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 112 113 pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind" 114 # If no flags were specified alongside bind, we keep all existing flags. 115 # Unspecified flags must be cleared (rw default). 116 run -0 grep -wq ro <<<"$mnt_flags" 117 run ! grep -wq rw <<<"$mnt_flags" 118 run -0 grep -wq noexec <<<"$mnt_flags" 119 run -0 grep -wq nodiratime <<<"$mnt_flags" 120 # On old systems, mount doesn't know about nosymfollow, which turns the 121 # flag into a data argument (which is ignored by MS_REMOUNT). 122 if sshfs_has_flag nosymfollow; then run -0 grep -wq nosymfollow <<<"$mnt_flags"; fi 123 } 124 125 # This behaviour does not match mount(8), but is preferable to the alternative. 126 # See <https://github.com/util-linux/util-linux/issues/2433>. 127 @test "runc run [mount(8)-unlike behaviour: --bind with clearing flag]" { 128 requires root 129 130 pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind,dev" 131 # Unspecified flags must be cleared as well. 132 run ! grep -wq ro <<<"$mnt_flags" 133 run -0 grep -wq rw <<<"$mnt_flags" 134 run ! grep -wq noexec <<<"$mnt_flags" 135 run ! grep -wq nosymfollow <<<"$mnt_flags" 136 # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" 137 # way will be ignored! 138 run -0 grep -wq nodiratime <<<"$mnt_flags" 139 140 # Now try with a user namespace. 141 update_config ' .linux.namespaces += [{"type": "user"}] 142 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 143 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 144 145 pass_sshfs_bind_flags "ro,noexec,nosymfollow,nodiratime" "bind,dev" 146 # Lockable flags must be kept, because we didn't request them explicitly. 147 run -0 grep -wq ro <<<"$mnt_flags" 148 run ! grep -wq rw <<<"$mnt_flags" 149 run -0 grep -wq noexec <<<"$mnt_flags" 150 run -0 grep -wq nodiratime <<<"$mnt_flags" 151 # nosymfollow is not lockable, so it must be cleared. 152 run ! grep -wq nosymfollow <<<"$mnt_flags" 153 } 154 155 @test "runc run [implied-rw bind mount of a ro fuse sshfs mount]" { 156 requires root 157 158 pass_sshfs_bind_flags "ro" "bind,nosuid,nodev,rprivate" 159 # Unspecified flags must be cleared (rw default). 160 run ! grep -wq ro <<<"$mnt_flags" 161 run -0 grep -wq rw <<<"$mnt_flags" 162 # The new flags must be applied. 163 run -0 grep -wq nosuid <<<"$mnt_flags" 164 run -0 grep -wq nodev <<<"$mnt_flags" 165 166 # Now try with a user namespace. The results should be the same as above. 167 update_config ' .linux.namespaces += [{"type": "user"}] 168 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 169 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 170 171 pass_sshfs_bind_flags "ro" "bind,nosuid,nodev,rprivate" 172 # "ro" must still be set (inherited). 173 run -0 grep -wq ro <<<"$mnt_flags" 174 # The new flags must be applied. 175 run -0 grep -wq nosuid <<<"$mnt_flags" 176 run -0 grep -wq nodev <<<"$mnt_flags" 177 } 178 179 @test "runc run [explicit-rw bind mount of a ro fuse sshfs mount]" { 180 requires root 181 182 # Try to overwrite MS_RDONLY. As we are running in a userns-less container, 183 # we can overwrite MNT_LOCKED flags. 184 pass_sshfs_bind_flags "ro" "bind,rw,nosuid,nodev,rprivate" 185 # "ro" must be cleared and replaced with "rw". 186 run ! grep -wq ro <<<"$mnt_flags" 187 run -0 grep -wq rw <<<"$mnt_flags" 188 # The new flags must be applied. 189 run -0 grep -wq nosuid <<<"$mnt_flags" 190 run -0 grep -wq nodev <<<"$mnt_flags" 191 192 # Now try with a user namespace. 193 update_config ' .linux.namespaces += [{"type": "user"}] 194 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 195 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 196 197 # This must fail because we explicitly requested a mount with a MNT_LOCKED 198 # mount option cleared (when the source mount has those mounts enabled), 199 # namely MS_RDONLY. 200 fail_sshfs_bind_flags "ro" "bind,rw,nosuid,nodev,rprivate" 201 } 202 203 @test "runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]" { 204 requires root 205 206 # When running without userns, overwriting host flags should work. 207 pass_sshfs_bind_flags "nosuid,nodev,noexec,noatime" "bind,dev,suid,exec,atime" 208 # Unspecified flags must be cleared (rw default). 209 run ! grep -wq ro <<<"$mnt_flags" 210 run -0 grep -wq rw <<<"$mnt_flags" 211 # Check that the flags were actually cleared by the mount. 212 run ! grep -wq nosuid <<<"$mnt_flags" 213 run ! grep -wq nodev <<<"$mnt_flags" 214 run ! grep -wq noexec <<<"$mnt_flags" 215 # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" 216 # way will be ignored! 217 run -0 grep -wq noatime <<<"$mnt_flags" 218 219 # Now try with a user namespace. 220 update_config ' .linux.namespaces += [{"type": "user"}] 221 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 222 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 223 224 # This must fail because we explicitly requested a mount with MNT_LOCKED 225 # mount options cleared (when the source mount has those mounts enabled). 226 fail_sshfs_bind_flags "nodev,nosuid,nosuid,noatime" "bind,dev,suid,exec,atime" 227 } 228 229 # Test to ensure we don't regress bind-mounting /etc/resolv.conf with 230 # containerd <https://github.com/containerd/containerd/pull/8309>. 231 @test "runc run [ro bind mount of a nodev,nosuid,noexec fuse sshfs mount]" { 232 requires root 233 234 # Setting flags that are not locked should work. 235 pass_sshfs_bind_flags "rw,nodev,nosuid,nodev,noexec,noatime" "bind,ro" 236 # The flagset should be the union of the two. 237 run -0 grep -wq ro <<<"$mnt_flags" 238 # Unspecified flags must be cleared. 239 run ! grep -wq nosuid <<<"$mnt_flags" 240 run ! grep -wq nodev <<<"$mnt_flags" 241 run ! grep -wq noexec <<<"$mnt_flags" 242 243 # Now try with a user namespace. 244 update_config ' .linux.namespaces += [{"type": "user"}] 245 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 246 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 247 248 # Setting flags that are not locked should work. 249 pass_sshfs_bind_flags "rw,nodev,nosuid,nodev,noexec,noatime" "bind,ro" 250 # The flagset should be the union of the two. 251 run -0 grep -wq ro <<<"$mnt_flags" 252 # (Unspecified MNT_LOCKED flags are inherited.) 253 run -0 grep -wq nosuid <<<"$mnt_flags" 254 run -0 grep -wq nodev <<<"$mnt_flags" 255 run -0 grep -wq noexec <<<"$mnt_flags" 256 } 257 258 @test "runc run [ro,symfollow bind mount of a rw,nodev,nosymfollow fuse sshfs mount]" { 259 requires root 260 261 pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,ro,symfollow" 262 # Must switch to ro. 263 run -0 grep -wq ro <<<"$mnt_flags" 264 run ! grep -wq rw <<<"$mnt_flags" 265 # Unspecified flags must be cleared. 266 run ! grep -wq nodev <<<"$mnt_flags" 267 # nosymfollow must also be cleared. 268 run ! grep -wq nosymfollow <<<"$mnt_flags" 269 270 # Now try with a user namespace. 271 update_config ' .linux.namespaces += [{"type": "user"}] 272 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 273 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 274 275 # Unsetting flags that are not lockable should work. 276 pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,ro,symfollow" 277 # The flagset should be the union of the two. 278 run -0 grep -wq ro <<<"$mnt_flags" 279 run -0 grep -wq nodev <<<"$mnt_flags" 280 # nosymfollow is not lockable, so it must be cleared. 281 run ! grep -wq nosymfollow <<<"$mnt_flags" 282 283 # Implied unsetting of non-lockable flags should also work. 284 pass_sshfs_bind_flags "rw,nodev,nosymfollow" "bind,rw" 285 # The flagset should be the union of the two. 286 run -0 grep -wq rw <<<"$mnt_flags" 287 run -0 grep -wq nodev <<<"$mnt_flags" 288 # nosymfollow is not lockable, so it must be cleared. 289 run ! grep -wq nosymfollow <<<"$mnt_flags" 290 } 291 292 @test "runc run [ro,noexec bind mount of a nosuid,noatime fuse sshfs mount]" { 293 requires root 294 295 # Setting flags that are not locked should work. 296 pass_sshfs_bind_flags "nodev,nosuid,noatime" "bind,ro,exec" 297 # The flagset must match the requested set. 298 run -0 grep -wq ro <<<"$mnt_flags" 299 run ! grep -wq noexec <<<"$mnt_flags" 300 # Unspecified flags must be cleared. 301 run ! grep -wq nosuid <<<"$mnt_flags" 302 run ! grep -wq nodev <<<"$mnt_flags" 303 # FIXME: As with mount(8), runc keeps the old atime setting by default. 304 run -0 grep -wq noatime <<<"$mnt_flags" 305 306 # Now try with a user namespace. 307 update_config ' .linux.namespaces += [{"type": "user"}] 308 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 309 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 310 311 # Setting flags that are not locked should work. 312 pass_sshfs_bind_flags "nodev,nosuid,noatime" "bind,ro,exec" 313 # The flagset should be the union of the two. 314 run -0 grep -wq ro <<<"$mnt_flags" 315 run ! grep -wq noexec <<<"$mnt_flags" 316 # (Unspecified MNT_LOCKED flags are inherited.) 317 run -0 grep -wq nosuid <<<"$mnt_flags" 318 run -0 grep -wq nodev <<<"$mnt_flags" 319 run -0 grep -wq noatime <<<"$mnt_flags" 320 } 321 322 @test "runc run [bind mount {no,rel,strict}atime semantics]" { 323 requires root 324 325 function is_strictatime() { 326 # There is no "strictatime" in /proc/self/mounts. 327 run ! grep -wq noatime <<<"${1:-$mnt_flags}" 328 run ! grep -wq relatime <<<"${1:-$mnt_flags}" 329 run ! grep -wq nodiratime <<<"${1:-$mnt_flags}" 330 } 331 332 # FIXME: As with mount(8), runc keeps the old atime setting by default. 333 pass_sshfs_bind_flags "noatime" "bind" 334 run -0 grep -wq noatime <<<"$mnt_flags" 335 run ! grep -wq relatime <<<"$mnt_flags" 336 337 # FIXME: As with mount(8), runc keeps the old atime setting by default. 338 pass_sshfs_bind_flags "noatime" "bind,norelatime" 339 run -0 grep -wq noatime <<<"$mnt_flags" 340 run ! grep -wq relatime <<<"$mnt_flags" 341 342 # FIXME FIXME: As with mount(8), trying to clear an atime flag the "naive" 343 # way will be ignored! 344 pass_sshfs_bind_flags "noatime" "bind,atime" 345 run -0 grep -wq noatime <<<"$mnt_flags" 346 run ! grep -wq relatime <<<"$mnt_flags" 347 348 # ... but explicitly setting a different flag works. 349 pass_sshfs_bind_flags "noatime" "bind,relatime" 350 run ! grep -wq noatime <<<"$mnt_flags" 351 run -0 grep -wq relatime <<<"$mnt_flags" 352 353 # Setting a flag that mount(8) would combine should result in only the 354 # requested flag being set. 355 pass_sshfs_bind_flags "noatime" "bind,nodiratime" 356 run ! grep -wq noatime <<<"$mnt_flags" 357 run -0 grep -wq nodiratime <<<"$mnt_flags" 358 # MS_DIRATIME implies MS_RELATIME by default. 359 run -0 grep -wq relatime <<<"$mnt_flags" 360 361 # Clearing flags that mount(8) would not clear works. 362 pass_sshfs_bind_flags "nodiratime" "bind,strictatime" 363 is_strictatime "$mnt_flags" 364 365 # nodiratime is a little weird -- it implies relatime unless you set 366 # another option (noatime or strictatime). But, runc also has norelatime -- 367 # so nodiratime,norelatime should _probably_ result in the same thing as 368 # nodiratime,strictatime. 369 pass_sshfs_bind_flags "noatime" "bind,nodiratime,strictatime" 370 run ! grep -wq noatime <<<"$mnt_flags" 371 run -0 grep -wq nodiratime <<<"$mnt_flags" 372 run ! grep -wq relatime <<<"$mnt_flags" 373 # FIXME FIXME: relatime should not be set in this case. 374 pass_sshfs_bind_flags "noatime" "bind,nodiratime,norelatime" 375 run ! grep -wq noatime <<<"$mnt_flags" 376 run -0 grep -wq nodiratime <<<"$mnt_flags" 377 run -0 grep -wq relatime <<<"$mnt_flags" 378 379 # Now try with a user namespace. 380 update_config ' .linux.namespaces += [{"type": "user"}] 381 | .linux.uidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] 382 | .linux.gidMappings += [{"hostID": 100000, "containerID": 0, "size": 65534}] ' 383 384 # Requesting a mount without specifying any preference for atime works, and 385 # inherits the original flags. 386 387 pass_sshfs_bind_flags "strictatime" "bind" 388 is_strictatime "$mnt_flags" 389 390 pass_sshfs_bind_flags "relatime" "bind" 391 run -0 grep -wq relatime <<<"$mnt_flags" 392 393 pass_sshfs_bind_flags "nodiratime" "bind" 394 run -0 grep -wq nodiratime <<<"$mnt_flags" 395 # MS_DIRATIME implies MS_RELATIME by default. 396 # Let's check either relatime is set or no other option that removes 397 # relatime semantics is set. 398 # The latter case is needed in debian. For more info, see issue: #4093 399 run -0 grep -wq relatime <<<"$mnt_flags" || 400 (run ! grep -wqE 'strictatime|norelatime|noatime' <<<"$mnt_flags") 401 402 pass_sshfs_bind_flags "noatime,nodiratime" "bind" 403 run -0 grep -wq noatime <<<"$mnt_flags" 404 run -0 grep -wq nodiratime <<<"$mnt_flags" 405 406 # An unrelated clear flag has no effect. 407 pass_sshfs_bind_flags "noatime,nodiratime" "bind,norelatime" 408 run -0 grep -wq noatime <<<"$mnt_flags" 409 run -0 grep -wq nodiratime <<<"$mnt_flags" 410 411 # Attempting to change most *atime flags will fail with user namespaces 412 # because *atime flags are all MNT_LOCKED. 413 fail_sshfs_bind_flags "nodiratime" "bind,strictatime" 414 fail_sshfs_bind_flags "relatime" "bind,strictatime" 415 fail_sshfs_bind_flags "noatime" "bind,strictatime" 416 fail_sshfs_bind_flags "nodiratime" "bind,noatime" 417 fail_sshfs_bind_flags "relatime" "bind,noatime" 418 fail_sshfs_bind_flags "relatime" "bind,nodiratime" 419 # Make sure strictatime sources are correctly handled by runc (the kernel 420 # ignores some other mount flags when passing MS_STRICTATIME). See 421 # remount() in rootfs_linux.go for details. 422 fail_sshfs_bind_flags "strictatime" "bind,relatime" 423 fail_sshfs_bind_flags "strictatime" "bind,noatime" 424 fail_sshfs_bind_flags "strictatime" "bind,nodiratime" 425 # Make sure that runc correctly handles the MS_NOATIME|MS_RELATIME kernel 426 # bug. See remount() in rootfs_linux.go for more details. 427 fail_sshfs_bind_flags "noatime" "bind,relatime" 428 429 # Attempting to bind-mount a mount with a request to clear the atime 430 # setting that would normally inherited must not work. 431 # FIXME FIXME: All of these cases should fail. 432 pass_sshfs_bind_flags "strictatime" "bind,nostrictatime" 433 is_strictatime "$mnt_flags" 434 pass_sshfs_bind_flags "nodiratime" "bind,diratime" 435 run -0 grep -wq nodiratime <<<"$mnt_flags" 436 pass_sshfs_bind_flags "nodiratime" "bind,norelatime" # MS_DIRATIME implies MS_RELATIME 437 run -0 grep -wq nodiratime <<<"$mnt_flags" 438 pass_sshfs_bind_flags "relatime" "bind,norelatime" 439 run -0 grep -wq relatime <<<"$mnt_flags" 440 pass_sshfs_bind_flags "noatime" "bind,atime" 441 run -0 grep -wq noatime <<<"$mnt_flags" 442 pass_sshfs_bind_flags "noatime,nodiratime" "bind,atime" 443 run -0 grep -wq noatime <<<"$mnt_flags" 444 run -0 grep -wq nodiratime <<<"$mnt_flags" 445 }