Repository: kubernetes/kubernetes (language: go)

Issue:
NFS PV mountOptions does not work when Volume SubPath option is set
### What happened?

Setting the Volume subpath option at deployment with an NFS PV with a mount option (e.g. nosuid) does not mount accordingly. If no SubPath is set and the NFS PV is set to the /nfs_share/directory the mount works with the nosuid.

### What did you expect to happen?

should be mounted with correct mountoptions

### How can we reproduce it (as minimally and precisely as possible)?

- create KIND cluster and install nfs client packages
- create a PV and PVC with below options: 


```
spec:
  accessModes:
    - ReadWriteOnce
    - ReadOnlyMany
    - ReadWriteMany
  capacity:
    storage: 10Gi
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: pvc01
    namespace: default
    resourceVersion: '53505'
    uid: 0b3736ac-bbb6-4539-95c1-854ddcb36114
  mountOptions:
    - nfsvers=4.2
    - sec=sys
    - nosuid
  nfs:
    path: /nfs
    server: 172.31.40.82
  persistentVolumeReclaimPolicy: Retain
  volumeMode: Filesystem  
```
-  Update the deployment with the volume and mount information: 

```
          volumeMounts:
            - mountPath: /data
              name: vol-oirnd
              subPath: share01

      volumes:
        - name: vol-oirnd
          persistentVolumeClaim:
            claimName: pvc01
          __newPvc:
            type: persistentvolumeclaim
            metadata:
              namespace: default
            spec:
              storageClassName: ''
              volumeName: ''
              resources:
                requests: {} 
```

### Anything else we need to know?

```
**Not using SubPath**

root@kind-2-control-plane:/# mount | grep nfs
192.168.100.217:/nfs on /var/lib/kubelet/pods/c29b33d8-3709-4082-892c-def5efbba86f/volumes/kubernetes.io~nfs/pv type nfs4 (rw,nosuid,relatime,vers=4.2,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.18.0.2,local_lock=none,addr=192.168.100.217)

**When not using SubPath**


192.168.100.217:/nfs/hello/world on /var/lib/kubelet/pods/c29b33d8-3709-4082-892c-def5efbba86f/volume-subpaths/pv/httpd/0 type nfs4 (rw,relatime,vers=4.2,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.18.0.2,local_lock=none,addr=192.168.100.217)
root@kind-2-control-plane:/# 

root@kind-2-control-plane:/# kubectl get nodes
NAME                   STATUS   ROLES           AGE   VERSION
kind-2-control-plane   Ready    control-plane   49m   v1.29.2
root@kind-2-control-plane:/# 
```

### Kubernetes version

<details>

```console
$ kubectl version
 v1.29.2
```

</details>


### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
Ubuntu

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>



Discussion / hints:
/sig storage
/triage accepted
Hi. I've researched this problem and here's how it works:
1) The nfs Plugin mounts nfs-share in volumes/kubernetes.io~nfs/\<pv-name>
2) subpather mounts volumes/kubernetes.io~nfs/\<pv-name>/\<subpath> to volume-subpaths/\<pv-name>/\<ctr-name>/\<mount-index>

In fact, subpather runs the mount -o bind command...
That's how it looks in the code:
https://github.com/kubernetes/kubernetes/blob/1608dc2b0928fc69a9923716a305612746dbec55/pkg/volume/util/subpath/subpath_linux.go#L228-L233

When using the --bind option to mount one directory to another, the original mounting options are not automatically transferred to the new mounting point. 
That is, when using mount --bind, the mount options, including the NFS-specific nosuid, will not be saved automatically on the new mount.
However, these options continue to work "in the background", as they are active for the original mounting point.
So it turns out that there is a problem only with displaying all the options, of course if we consider this a problem? 
It seems to me that this can be easily fixed by simply reading the parameters from the first mount and adding them to the second.
I can do a pr
/assign
In my cluster (created by `hack/local-up-cluster.sh`), I can see both the main mount + subpath mount retain `nosuid` in their /proc/mounts entries:

```
192.168.122.1:/home/jsafrane/tmp on /var/lib/kubelet/pods/559a34f5-226a-48ae-8dbe-a93458404b68/volumes/kubernetes.io~nfs/nfs-pv type nfs4 (rw,nosuid,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.122.75,local_lock=none,addr=192.168.122.1)
192.168.122.1:/home/jsafrane/tmp/subdir on /var/lib/kubelet/pods/559a34f5-226a-48ae-8dbe-a93458404b68/volume-subpaths/nfs-pv/busybox/0 type nfs4 (rw,nosuid,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.122.75,local_lock=none,addr=192.168.122.1)
```

Kubernetes 1.31 beta (but I don't think it matters), Fedora 39, Kernel 6.8.10-200.fc39.x86_64
It could be caused either by the kernel or KIND.

`mount` man page says:

> Note that the filesystem mount options maintained by the kernel will remain the same as those on the original mount point. The userspace mount options (e.g., _netdev) will not be copied by mount and it’s necessary to explicitly specify the options on the mount command line.
...
> It’s also possible to change nosuid, nodev, noexec, noatime, nodiratime, relatime and nosymfollow VFS entry flags via ...


I interpret it as `nosuid` should be copied by default, but can be explicitly changed in a bind mount.
@jsafrane Hi
It appears that you are correct. However, I conducted additional testing and can confirm that I have:
1. cluster "KIND" using an image created from the master branch.
```
Kubernetes version: v1.32.0-alpha.0.18
Containerd version: v1.7.15
OS in a KIND:
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm

OS on my host:
NAME="Fedora Linux"
VERSION="40 (Workstation Edition)"
ID=fedora
VERSION_ID=40
VERSION_CODENAME=""
PLATFORM_ID="platform:f40"
PRETTY_NAME="Fedora Linux 40 (Workstation Edition)"

KERNEL: 6.9.11-200.fc40.x86_64
```

RESULT:
```
172.18.0.3:/var/nfs/pvc1 on /var/lib/kubelet/pods/1ff6e863-124d-498e-83db-e79fe668426b/volumes/kubernetes.io~nfs/nfs-pv-1 type nfs (rw,nosuid,relatime,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=172.18.0.3,mountvers=3,mountport=39415,mountproto=udp,local_lock=none,addr=172.18.0.3)
172.18.0.3:/var/nfs/pvc1/share01 on /var/lib/kubelet/pods/1ff6e863-124d-498e-83db-e79fe668426b/volume-subpaths/nfs-pv-1/busybox/0 type nfs (rw,relatime,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=172.18.0.3,mountvers=3,mountport=39415,mountproto=udp,local_lock=none,addr=172.18.0.3)
```


2. Working cluster on bare metal

```
Kubernetes version: v1.26.15 (I understand that this version is old, but now I don't have another one at hand, later I will be able to repeat this on a more recent one.)
Containerd version: v1.7.13
OS:
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy

KERNEL: 5.15.0-75-generic
```

RESULT:
```
172.18.18.80:/var/nfs/pvc1 on /var/lib/kubelet/pods/3d389a14-f2b9-44ba-af6c-0e5154285527/volumes/kubernetes.io~nfs/nfs-pv-1 type nfs4 (rw,nosuid,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.18.18.81,local_lock=none,addr=172.18.18.80)
172.18.18.80:/var/nfs/pvc1/share01 on /var/lib/kubelet/pods/3d389a14-f2b9-44ba-af6c-0e5154285527/volume-subpaths/nfs-pv-1/busybox/0 type nfs4 (rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.18.18.81,local_lock=none,addr=172.18.18.80)

```
I continued my research and found that the "Mounter" with the bind option always performs a remount
https://github.com/kubernetes/kubernetes/blob/60c4c2b2521fb454ce69dee737e3eb91a25e0535/staging/src/k8s.io/mount-utils/mount_linux.go#L145-L149
This is why mount parameters that were not passed directly are missing.

But there is a fix that added some mount options, if kubelet is running in a user namespace, that's why I think you didn't encounter this problem in your testing.
https://github.com/kubernetes/kubernetes/blob/60c4c2b2521fb454ce69dee737e3eb91a25e0535/staging/src/k8s.io/mount-utils/mount_linux.go#L150-L162

Nice! So... should we jest remove `if libcontaineruserns.RunningInUserNS()` check?
I think so. In the near future, I will be able to check this and make a new PR or change the old one.

Fix patch:
diff --git a/staging/src/k8s.io/mount-utils/go.mod b/staging/src/k8s.io/mount-utils/go.mod
index c8796fee1b63b..fae337c5ca01c 100644
--- a/staging/src/k8s.io/mount-utils/go.mod
+++ b/staging/src/k8s.io/mount-utils/go.mod
@@ -8,7 +8,6 @@ godebug default=go1.23
 
 require (
 	github.com/moby/sys/mountinfo v0.7.2
-	github.com/moby/sys/userns v0.1.0
 	github.com/stretchr/testify v1.9.0
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
 	golang.org/x/sys v0.26.0
diff --git a/staging/src/k8s.io/mount-utils/go.sum b/staging/src/k8s.io/mount-utils/go.sum
index 9e77478c242ec..466650c5853c0 100644
--- a/staging/src/k8s.io/mount-utils/go.sum
+++ b/staging/src/k8s.io/mount-utils/go.sum
@@ -13,8 +13,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
 github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
-github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
-github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/staging/src/k8s.io/mount-utils/mount_linux.go b/staging/src/k8s.io/mount-utils/mount_linux.go
index 37912c1f58a2d..5901114f049ce 100644
--- a/staging/src/k8s.io/mount-utils/mount_linux.go
+++ b/staging/src/k8s.io/mount-utils/mount_linux.go
@@ -35,7 +35,6 @@ import (
 	"github.com/moby/sys/mountinfo"
 	"golang.org/x/sys/unix"
 
-	inuserns "github.com/moby/sys/userns"
 	"k8s.io/klog/v2"
 	utilexec "k8s.io/utils/exec"
 )
@@ -114,7 +113,7 @@ func (mounter *Mounter) hasSystemd() bool {
 
 // Map unix.Statfs mount flags ro, nodev, noexec, nosuid, noatime, relatime,
 // nodiratime to mount option flag strings.
-func getUserNSBindMountOptions(path string, statfs func(path string, buf *unix.Statfs_t) (err error)) ([]string, error) {
+func getBindMountOptions(path string, statfs func(path string, buf *unix.Statfs_t) (err error)) ([]string, error) {
 	var s unix.Statfs_t
 	var mountOpts []string
 	if err := statfs(path, &s); err != nil {
@@ -137,32 +136,23 @@ func getUserNSBindMountOptions(path string, statfs func(path string, buf *unix.S
 	return mountOpts, nil
 }
 
-// Do a bind mount including the needed remount for applying the bind opts.
-// If the remount fails and we are running in a user namespace
-// figure out if the source filesystem has the ro, nodev, noexec, nosuid,
-// noatime, relatime or nodiratime flag set and try another remount with the found flags.
+// Performs a bind mount with the specified options, and then remounts
+// the mount point with the same `nodev`, `nosuid`, `noexec`, `nosuid`, `noatime`,
+// `relatime`, `nodiratime` options as the original mount point.
 func (mounter *Mounter) bindMountSensitive(mounterPath string, mountCmd string, source string, target string, fstype string, bindOpts []string, bindRemountOpts []string, bindRemountOptsSensitive []string, mountFlags []string, systemdMountRequired bool) error {
-	err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired)
+	err := mounter.doMount(mounterPath, mountCmd, source, target, fstype, bindOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired)
 	if err != nil {
 		return err
 	}
-	err = mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired)
-	if inuserns.RunningInUserNS() {
-		if err == nil {
-			return nil
-		}
-		// Check if the source has ro, nodev, noexec, nosuid, noatime, relatime,
-		// nodiratime flag...
-		fixMountOpts, err := getUserNSBindMountOptions(source, unix.Statfs)
-		if err != nil {
-			return &os.PathError{Op: "statfs", Path: source, Err: err}
-		}
-		// ... and retry the mount with flags found above.
-		bindRemountOpts = append(bindRemountOpts, fixMountOpts...)
-		return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired)
-	} else {
-		return err
+	// Check if the source has ro, nodev, noexec, nosuid, noatime, relatime,
+	// nodiratime flag...
+	fixMountOpts, err := getBindMountOptions(source, unix.Statfs)
+	if err != nil {
+		return &os.PathError{Op: "statfs", Path: source, Err: err}
 	}
+	// ... and retry the mount with flags found above.
+	bindRemountOpts = append(bindRemountOpts, fixMountOpts...)
+	return mounter.doMount(mounterPath, mountCmd, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive, mountFlags, systemdMountRequired)
 }
 
 // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must


Test patch:
diff --git a/staging/src/k8s.io/mount-utils/mount_linux_test.go b/staging/src/k8s.io/mount-utils/mount_linux_test.go
index 3c0842b20b35c..e8c765b5c52e4 100644
--- a/staging/src/k8s.io/mount-utils/mount_linux_test.go
+++ b/staging/src/k8s.io/mount-utils/mount_linux_test.go
@@ -821,7 +821,7 @@ func mkStatfsFlags[T1 constraints.Integer, T2 constraints.Integer](orig T1, add
 	return orig | T1(add)
 }
 
-func TestGetUserNSBindMountOptions(t *testing.T) {
+func TestGetBindMountOptions(t *testing.T) {
 	var testCases = map[string]struct {
 		flags        int32 // smallest size used by any platform we care about
 		mountoptions string
@@ -843,9 +843,9 @@ func TestGetUserNSBindMountOptions(t *testing.T) {
 		return nil
 	}
 
-	testGetUserNSBindMountOptionsSingleCase := func(t *testing.T) {
+	testGetBindMountOptionsSingleCase := func(t *testing.T) {
 		path := strings.Split(t.Name(), "/")[1]
-		options, _ := getUserNSBindMountOptions(path, statfsMock)
+		options, _ := getBindMountOptions(path, statfsMock)
 		sort.Strings(options)
 		optionString := strings.Join(options, ",")
 		mountOptions := testCases[path].mountoptions
@@ -855,7 +855,7 @@ func TestGetUserNSBindMountOptions(t *testing.T) {
 	}
 
 	for k := range testCases {
-		t.Run(k, testGetUserNSBindMountOptionsSingleCase)
+		t.Run(k, testGetBindMountOptionsSingleCase)
 	}
 }
 
diff --git a/test/e2e/storage/drivers/in_tree.go b/test/e2e/storage/drivers/in_tree.go
index 58ec455c67b30..7d34574e1ea3c 100644
--- a/test/e2e/storage/drivers/in_tree.go
+++ b/test/e2e/storage/drivers/in_tree.go
@@ -43,6 +43,7 @@ import (
 	"time"
 
 	"github.com/onsi/ginkgo/v2"
+
 	v1 "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
 	storagev1 "k8s.io/api/storage/v1"
@@ -1138,19 +1139,54 @@ type localVolume struct {
 var (
 	// capabilities
 	defaultLocalVolumeCapabilities = map[storageframework.Capability]bool{
-		storageframework.CapPersistence:       true,
-		storageframework.CapFsGroup:           true,
-		storageframework.CapBlock:             false,
-		storageframework.CapExec:              true,
+		storageframework.CapPersistence: true,
+		storageframework.CapFsGroup:     true,
+		storageframework.CapBlock:       false,
+		// To test CapExec, we need a volume with a filesystem.
+		// During end-to-end (e2e) testing, we utilize the `/tmp` directory for volume creation.
+		// However, best practices recommend mounting `/tmp` with the `noexec`, `nodev`, and `nosuid` parameters.
+		// This security measure prevents the execution of scripts and binaries within the `/tmp` directory.
+		// This practice, while promoting security, creates a dependency on the infrastructure configuration during e2e tests.
+		// This can result in "Permission Denied" errors when attempting to execute files from `/tmp`.
+		// To address this, we intentionally skip exec tests for certain types of LocalVolumes, such as `dir` or `dir-link`.
+		// This allows us to conduct comprehensive testing without relying on potentially restrictive security configurations.
+		storageframework.CapExec:              false,
 		storageframework.CapMultiPODs:         true,
 		storageframework.CapSingleNodeVolume:  true,
 		storageframework.CapMultiplePVsSameID: true,
 	}
 	localVolumeCapabitilies = map[utils.LocalVolumeType]map[storageframework.Capability]bool{
+		utils.LocalVolumeTmpfs: {
+			storageframework.CapPersistence:       true,
+			storageframework.CapFsGroup:           true,
+			storageframework.CapBlock:             false,
+			storageframework.CapExec:              true,
+			storageframework.CapMultiPODs:         true,
+			storageframework.CapSingleNodeVolume:  true,
+			storageframework.CapMultiplePVsSameID: true,
+		},
 		utils.LocalVolumeBlock: {
 			storageframework.CapPersistence:       true,
 			storageframework.CapFsGroup:           true,
 			storageframework.CapBlock:             true,
+			storageframework.CapExec:              false,
+			storageframework.CapMultiPODs:         true,
+			storageframework.CapSingleNodeVolume:  true,
+			storageframework.CapMultiplePVsSameID: true,
+		},
+		utils.LocalVolumeBlockFS: {
+			storageframework.CapPersistence:       true,
+			storageframework.CapFsGroup:           true,
+			storageframework.CapBlock:             false,
+			storageframework.CapExec:              true,
+			storageframework.CapMultiPODs:         true,
+			storageframework.CapSingleNodeVolume:  true,
+			storageframework.CapMultiplePVsSameID: true,
+		},
+		utils.LocalVolumeGCELocalSSD: {
+			storageframework.CapPersistence:       true,
+			storageframework.CapFsGroup:           true,
+			storageframework.CapBlock:             false,
 			storageframework.CapExec:              true,
 			storageframework.CapMultiPODs:         true,
 			storageframework.CapSingleNodeVolume:  true,
diff --git a/test/e2e/storage/utils/local.go b/test/e2e/storage/utils/local.go
index df96eac124e3b..1dccee34625da 100644
--- a/test/e2e/storage/utils/local.go
+++ b/test/e2e/storage/utils/local.go
@@ -27,6 +27,7 @@ import (
 	"strings"
 
 	"github.com/onsi/ginkgo/v2"
+
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/util/uuid"
 	"k8s.io/kubernetes/test/e2e/framework"
@@ -215,7 +216,7 @@ func (l *ltrMgr) cleanupLocalVolumeDirectory(ctx context.Context, ltr *LocalTest
 func (l *ltrMgr) setupLocalVolumeDirectoryLink(ctx context.Context, node *v1.Node, parameters map[string]string) *LocalTestResource {
 	hostDir := l.getTestDir()
 	hostDirBackend := hostDir + "-backend"
-	cmd := fmt.Sprintf("mkdir %s && ln -s %s %s", hostDirBackend, hostDirBackend, hostDir)
+	cmd := fmt.Sprintf("mkdir -p %s && ln -s %s %s", hostDirBackend, hostDirBackend, hostDir)
 	err := l.hostExec.IssueCommand(ctx, cmd, node)
 	framework.ExpectNoError(err)
 	return &LocalTestResource{
@@ -235,7 +236,7 @@ func (l *ltrMgr) cleanupLocalVolumeDirectoryLink(ctx context.Context, ltr *Local
 
 func (l *ltrMgr) setupLocalVolumeDirectoryBindMounted(ctx context.Context, node *v1.Node, parameters map[string]string) *LocalTestResource {
 	hostDir := l.getTestDir()
-	cmd := fmt.Sprintf("mkdir %s && mount --bind %s %s", hostDir, hostDir, hostDir)
+	cmd := fmt.Sprintf("mkdir -p %s && mount --bind %s %s", hostDir, hostDir, hostDir)
 	err := l.hostExec.IssueCommand(ctx, cmd, node)
 	framework.ExpectNoError(err)
 	return &LocalTestResource{
@@ -255,7 +256,7 @@ func (l *ltrMgr) cleanupLocalVolumeDirectoryBindMounted(ctx context.Context, ltr
 func (l *ltrMgr) setupLocalVolumeDirectoryLinkBindMounted(ctx context.Context, node *v1.Node, parameters map[string]string) *LocalTestResource {
 	hostDir := l.getTestDir()
 	hostDirBackend := hostDir + "-backend"
-	cmd := fmt.Sprintf("mkdir %s && mount --bind %s %s && ln -s %s %s", hostDirBackend, hostDirBackend, hostDirBackend, hostDirBackend, hostDir)
+	cmd := fmt.Sprintf("mkdir -p %s && mount --bind %s %s && ln -s %s %s", hostDirBackend, hostDirBackend, hostDirBackend, hostDirBackend, hostDir)
 	err := l.hostExec.IssueCommand(ctx, cmd, node)
 	framework.ExpectNoError(err)
 	return &LocalTestResource{