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
|
// Copyright 2025 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
)
func TestIDMappedMount(t *testing.T) {
// sys_open_tree requires CAP_SYS_ADMIN
if os.Geteuid() != 0 {
t.Skip("id-mapped mount requires CAP_SYS_ADMIN")
}
tc := newTestCase(t, &testOptions{idMappedMount: true})
tc.writeOrig("file", "hello", 0644)
fi, err := os.Lstat(filepath.Join(tc.origDir, "file"))
if err != nil {
t.Fatalf("stat for path %s failed: %v", filepath.Join(tc.origDir, "file"), err)
}
st := fuse.ToStatT(fi)
if tc.server.KernelSettings().Flags64()&fuse.CAP_ALLOW_IDMAP == 0 {
t.Skip("Kernel does not support id-mapped mount")
}
const offset = 10000
fd, err := usernsFD(offset)
if err != nil {
t.Fatalf("failed to get user namespace FD: %v", err)
}
defer fd.Close()
idDir := t.TempDir()
if err = idMapMount(tc.mntDir, idDir, int(fd.Fd())); err != nil {
t.Fatalf("id-mapped mount failed: %v", err)
}
defer unix.Unmount(idDir, 0)
mfi, err := os.Lstat(filepath.Join(idDir, "file"))
if err != nil {
t.Fatalf("stat for path %s failed: %v", filepath.Join(idDir, "file"), err)
}
mst := fuse.ToStatT(mfi)
if st.Uid+offset != mst.Uid {
t.Errorf("Uid %v + offset %v != mapped Uid %v", st.Uid, offset, mst.Uid)
}
if st.Gid+offset != mst.Gid {
t.Errorf("Gid %v + offset %v != mapped Gid %v", st.Gid, offset, mst.Gid)
}
}
func idMapMount(source, target string, fd int) (err error) {
const ignored = 0
dFd, err := unix.OpenTree(ignored, source, uint(unix.OPEN_TREE_CLONE|unix.OPEN_TREE_CLOEXEC|unix.AT_EMPTY_PATH))
if err != nil {
return fmt.Errorf("open tree failed %s: %w", source, err)
}
defer unix.Close(dFd)
if err = unix.MountSetattr(dFd, "", unix.AT_EMPTY_PATH, &unix.MountAttr{Attr_set: unix.MOUNT_ATTR_IDMAP, Userns_fd: uint64(fd)}); err != nil {
return fmt.Errorf("set attr for %s failed: %w", source, err)
}
if err = unix.MoveMount(dFd, "", ignored, target, unix.MOVE_MOUNT_F_EMPTY_PATH); err != nil {
return fmt.Errorf("move mount to %s failed: %w", target, err)
}
return nil
}
func usernsFD(offset int) (*os.File, error) {
var err error
args := []string{"sleep", "1h"}
if args[0], err = exec.LookPath("sleep"); err != nil {
return nil, fmt.Errorf("failed to find sleep binary: %w", err)
}
p, err := os.StartProcess(args[0], args, &os.ProcAttr{
Sys: &syscall.SysProcAttr{
Cloneflags: unix.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: offset,
Size: offset,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: offset,
Size: offset,
},
},
Pdeathsig: syscall.SIGKILL,
},
})
if err != nil {
return nil, fmt.Errorf("failed to start process: %w", err)
}
defer func() {
p.Kill()
p.Wait()
}()
return os.Open(fmt.Sprintf("/proc/%d/ns/user", p.Pid))
}
|