diff --git a/pkg/abi/linux/fuse.go b/pkg/abi/linux/fuse.go index 891bbbff5..0d7db57f8 100644 --- a/pkg/abi/linux/fuse.go +++ b/pkg/abi/linux/fuse.go @@ -755,6 +755,32 @@ func (r *FUSESymlinkIn) SizeBytes() int { return r.Name.SizeBytes() + r.Target.SizeBytes() } +// FUSELinkIn is the request sent by the kernel to create a hard link. +// +// +marshal dynamic +type FUSELinkIn struct { + // OldNodeID is the ID of the inode that is being linked to. + OldNodeID primitive.Uint64 + // Name of the new hard link to create. + Name CString +} + +// MarshalBytes implements marshal.Marshallable.MarshalBytes. +func (r *FUSELinkIn) MarshalBytes(buf []byte) []byte { + buf = r.OldNodeID.MarshalBytes(buf) + return r.Name.MarshalBytes(buf) +} + +// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes. +func (r *FUSELinkIn) UnmarshalBytes(buf []byte) []byte { + panic("Unimplemented, FUSELinkIn is never unmarshalled") +} + +// SizeBytes implements marshal.Marshallable.SizeBytes. +func (r *FUSELinkIn) SizeBytes() int { + return r.OldNodeID.SizeBytes() + r.Name.SizeBytes() +} + // FUSEEmptyIn is used by operations without request body. // // +marshal dynamic diff --git a/pkg/sentry/fsimpl/fuse/fusefs.go b/pkg/sentry/fsimpl/fuse/fusefs.go index b3de44c95..97d25562a 100644 --- a/pkg/sentry/fsimpl/fuse/fusefs.go +++ b/pkg/sentry/fsimpl/fuse/fusefs.go @@ -291,7 +291,7 @@ func (fs *filesystem) MountOptions() string { func (fs *filesystem) newRoot(ctx context.Context, creds *auth.Credentials, mode linux.FileMode) *kernfs.Dentry { i := &inode{fs: fs, nodeID: 1} i.attrMu.Lock() - i.init(creds, linux.UNNAMED_MAJOR, fs.devMinor, 1, linux.ModeDirectory|0755) + i.init(creds, linux.UNNAMED_MAJOR, fs.devMinor, 1, linux.ModeDirectory|0755, 2) i.attrMu.Unlock() i.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) i.InitRefs() @@ -305,7 +305,7 @@ func (fs *filesystem) newInode(ctx context.Context, nodeID uint64, attr linux.FU i := &inode{fs: fs, nodeID: nodeID} creds := auth.Credentials{EffectiveKGID: auth.KGID(attr.UID), EffectiveKUID: auth.KUID(attr.UID)} i.attrMu.Lock() - i.init(&creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.FileMode(attr.Mode)) + i.init(&creds, linux.UNNAMED_MAJOR, fs.devMinor, nodeID, linux.FileMode(attr.Mode), attr.Nlink) i.size.Store(attr.Size) i.attrMu.Unlock() i.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) diff --git a/pkg/sentry/fsimpl/fuse/inode.go b/pkg/sentry/fsimpl/fuse/inode.go index 002755c3f..880e761b6 100644 --- a/pkg/sentry/fsimpl/fuse/inode.go +++ b/pkg/sentry/fsimpl/fuse/inode.go @@ -45,7 +45,6 @@ type fileHandle struct { type inode struct { inodeRefs kernfs.InodeAlwaysValid - kernfs.InodeDirectoryNoNewChildren kernfs.InodeNotSymlink kernfs.InodeWatches kernfs.OrderedChildren @@ -110,6 +109,18 @@ func (i *inode) Mode() linux.FileMode { return i.filemode() } +func (i *inode) UID() auth.KUID { + i.attrMu.Lock() + defer i.attrMu.Unlock() + return auth.KUID(i.uid.Load()) +} + +func (i *inode) GID() auth.KGID { + i.attrMu.Lock() + defer i.attrMu.Unlock() + return auth.KGID(i.gid.Load()) +} + // +checklocks:i.attrMu func (i *inode) filemode() linux.FileMode { return linux.FileMode(i.mode.Load()) @@ -132,15 +143,11 @@ func (i *inode) touchAtime() { } // +checklocks:i.attrMu -func (i *inode) init(creds *auth.Credentials, devMajor, devMinor uint32, nodeid uint64, mode linux.FileMode) { +func (i *inode) init(creds *auth.Credentials, devMajor, devMinor uint32, nodeid uint64, mode linux.FileMode, nlink uint32) { if mode.FileType() == 0 { panic(fmt.Sprintf("No file type specified in 'mode' for InodeAttrs.Init(): mode=0%o", mode)) } - nlink := uint32(1) - if mode.FileType() == linux.ModeDirectory { - nlink = 2 - } i.nodeID = nodeid i.ino.Store(nodeid) i.mode.Store(uint32(mode)) @@ -399,6 +406,16 @@ func (i *inode) NewSymlink(ctx context.Context, name, target string) (kernfs.Ino return i.newEntry(ctx, name, linux.S_IFLNK, linux.FUSE_SYMLINK, &in) } +// NewLink implements kernfs.Inode.NewLink. +func (i *inode) NewLink(ctx context.Context, name string, target kernfs.Inode) (kernfs.Inode, error) { + targetInode := target.(*inode) + in := linux.FUSELinkIn{ + OldNodeID: primitive.Uint64(targetInode.nodeID), + Name: linux.CString(name), + } + return i.newEntry(ctx, name, targetInode.Mode().FileType(), linux.FUSE_LINK, &in) +} + // Unlink implements kernfs.Inode.Unlink. func (i *inode) Unlink(ctx context.Context, name string, child kernfs.Inode) error { kernelTask := kernel.TaskFromContext(ctx) diff --git a/pkg/sentry/fsimpl/fuse/request_response.go b/pkg/sentry/fsimpl/fuse/request_response.go index 98bf60486..fc65a3edc 100644 --- a/pkg/sentry/fsimpl/fuse/request_response.go +++ b/pkg/sentry/fsimpl/fuse/request_response.go @@ -108,9 +108,8 @@ func (conn *connection) NewRequest(creds *auth.Credentials, pid uint32, ino uint defer conn.fd.mu.Unlock() conn.fd.nextOpID += linux.FUSEOpID(reqIDStep) - hdrLen := (*linux.FUSEHeaderIn)(nil).SizeBytes() hdr := linux.FUSEHeaderIn{ - Len: uint32(hdrLen + payload.SizeBytes()), + Len: linux.SizeOfFUSEHeaderIn + uint32(payload.SizeBytes()), Opcode: opcode, Unique: conn.fd.nextOpID, NodeID: ino, @@ -121,8 +120,8 @@ func (conn *connection) NewRequest(creds *auth.Credentials, pid uint32, ino uint buf := make([]byte, hdr.Len) - hdr.MarshalUnsafe(buf[:hdrLen]) - payload.MarshalUnsafe(buf[hdrLen:]) + hdr.MarshalUnsafe(buf[:linux.SizeOfFUSEHeaderIn]) + payload.MarshalUnsafe(buf[linux.SizeOfFUSEHeaderIn:]) return &Request{ id: hdr.Unique, diff --git a/pkg/sentry/fsimpl/host/host.go b/pkg/sentry/fsimpl/host/host.go index 38d8993fa..e1b5788d2 100644 --- a/pkg/sentry/fsimpl/host/host.go +++ b/pkg/sentry/fsimpl/host/host.go @@ -346,6 +346,16 @@ func (i *inode) Mode() linux.FileMode { return linux.FileMode(s.Mode) } +// Mode implements kernfs.Inode.UID +func (i *inode) UID() auth.KUID { + return auth.KUID(i.virtualOwner.uid.Load()) +} + +// Mode implements kernfs.Inode.GID +func (i *inode) GID() auth.KGID { + return auth.KGID(i.virtualOwner.gid.Load()) +} + // Stat implements kernfs.Inode.Stat. func (i *inode) Stat(ctx context.Context, vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) { if opts.Mask&linux.STATX__RESERVED != 0 { diff --git a/pkg/sentry/fsimpl/kernfs/filesystem.go b/pkg/sentry/fsimpl/kernfs/filesystem.go index f6d5a8a72..d69118846 100644 --- a/pkg/sentry/fsimpl/kernfs/filesystem.go +++ b/pkg/sentry/fsimpl/kernfs/filesystem.go @@ -364,6 +364,13 @@ func (fs *Filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs. parent.dirMu.Lock() defer parent.dirMu.Unlock() + inode := vd.Dentry().Impl().(*Dentry).Inode() + if inode.Mode().IsDir() { + return linuxerr.EPERM + } + if err := vfs.MayLink(rp.Credentials(), inode.Mode(), inode.UID(), inode.GID()); err != nil { + return err + } pc := rp.Component() if err := checkCreateLocked(ctx, rp.Credentials(), pc, parent); err != nil { return err @@ -379,17 +386,12 @@ func (fs *Filesystem) LinkAt(ctx context.Context, rp *vfs.ResolvingPath, vd vfs. } defer rp.Mount().EndWrite() - d := vd.Dentry().Impl().(*Dentry) - if d.isDir() { - return linuxerr.EPERM - } - - childI, err := parent.inode.NewLink(ctx, pc, d.inode) + childI, err := parent.inode.NewLink(ctx, pc, inode) if err != nil { return err } parent.inode.Watches().Notify(ctx, pc, linux.IN_CREATE, 0, vfs.InodeEvent, false /* unlinked */) - d.inode.Watches().Notify(ctx, "", linux.IN_ATTRIB, 0, vfs.InodeEvent, false /* unlinked */) + inode.Watches().Notify(ctx, "", linux.IN_ATTRIB, 0, vfs.InodeEvent, false /* unlinked */) var child Dentry child.Init(fs, childI) parent.insertChildLocked(pc, &child) diff --git a/pkg/sentry/fsimpl/kernfs/inode_impl_util.go b/pkg/sentry/fsimpl/kernfs/inode_impl_util.go index befa3a375..6caa7fc67 100644 --- a/pkg/sentry/fsimpl/kernfs/inode_impl_util.go +++ b/pkg/sentry/fsimpl/kernfs/inode_impl_util.go @@ -228,6 +228,16 @@ func (a *InodeAttrs) Ino() uint64 { return a.ino.Load() } +// UID implements Inode.UID. +func (a *InodeAttrs) UID() auth.KUID { + return auth.KUID(a.uid.Load()) +} + +// GID implements Inode.GID. +func (a *InodeAttrs) GID() auth.KGID { + return auth.KGID(a.gid.Load()) +} + // Mode implements Inode.Mode. func (a *InodeAttrs) Mode() linux.FileMode { return linux.FileMode(a.mode.Load()) diff --git a/pkg/sentry/fsimpl/kernfs/kernfs.go b/pkg/sentry/fsimpl/kernfs/kernfs.go index 18c056c63..f7345be9d 100644 --- a/pkg/sentry/fsimpl/kernfs/kernfs.go +++ b/pkg/sentry/fsimpl/kernfs/kernfs.go @@ -722,6 +722,14 @@ type inodeMetadata interface { // separated from Stat for performance. Mode() linux.FileMode + // UID returns the (struct stat)::st_uid value for this inode. This is + // separated from Stat for performance. + UID() auth.KUID + + // GID returns the (struct stat)::st_gid value for this inode. This is + // separated from Stat for performance. + GID() auth.KGID + // Stat returns the metadata for this inode. This corresponds to // vfs.FilesystemImpl.StatAt. Stat(ctx context.Context, fs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) diff --git a/pkg/sentry/fsimpl/pipefs/pipefs.go b/pkg/sentry/fsimpl/pipefs/pipefs.go index 501e475b8..f5ebb7e77 100644 --- a/pkg/sentry/fsimpl/pipefs/pipefs.go +++ b/pkg/sentry/fsimpl/pipefs/pipefs.go @@ -127,6 +127,16 @@ func (i *inode) Mode() linux.FileMode { return pipeMode } +// UID implements kernfs.Inode.UID. +func (i *inode) UID() auth.KUID { + return auth.KUID(i.uid) +} + +// GID implements kernfs.Inode.GID. +func (i *inode) GID() auth.KGID { + return auth.KGID(i.gid) +} + // Stat implements kernfs.Inode.Stat. func (i *inode) Stat(_ context.Context, vfsfs *vfs.Filesystem, opts vfs.StatOptions) (linux.Statx, error) { ts := linux.NsecToStatxTimestamp(i.ctime.Nanoseconds()) diff --git a/test/runner/fuse/fuse.go b/test/runner/fuse/fuse.go index 6297b84d2..dbd106fa3 100644 --- a/test/runner/fuse/fuse.go +++ b/test/runner/fuse/fuse.go @@ -34,7 +34,7 @@ func main() { log.Warningf("could not create loopback root: %v", err) os.Exit(1) } - opts := &fuse.MountOptions{DirectMount: true, Debug: true, Options: []string{"default_permissions"}} + opts := &fuse.MountOptions{DirectMount: true, Debug: true, AllowOther: true, Options: []string{"default_permissions"}} rawFS := fs.NewNodeFS(loopbackRoot, &fs.Options{NullPermissions: true, Logger: golog.Default()}) server, err := fuse.NewServer(rawFS, "/tmp", opts) if err != nil { diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 0cdaa1636..62b82d674 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -293,6 +293,7 @@ syscall_test( ) syscall_test( + add_fusefs = True, add_overlay = True, test = "//test/syscalls/linux:link_test", use_tmpfs = True, # gofer needs CAP_DAC_READ_SEARCH to use AT_EMPTY_PATH with linkat(2)