Port SCM Rights to VFS2.

Fixes #1477.

PiperOrigin-RevId: 308317511
This commit is contained in:
Dean Deng
2020-04-24 13:46:58 -07:00
committed by gVisor bot
parent f87964e829
commit f13f26d17d
7 changed files with 260 additions and 8 deletions
+4 -2
View File
@@ -23,6 +23,8 @@ import (
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
)
// LINT.IfChange
type scmRights struct {
fds []int
}
@@ -32,8 +34,6 @@ func newSCMRights(fds []int) control.SCMRights {
}
// Files implements control.SCMRights.Files.
//
// TODO(gvisor.dev/issue/2017): Port to VFS2.
func (c *scmRights) Files(ctx context.Context, max int) (control.RightsFiles, bool) {
n := max
var trunc bool
@@ -93,3 +93,5 @@ func fdsToFiles(ctx context.Context, fds []int) []*fs.File {
}
return files
}
// LINT.ThenChange(../../fsimpl/host/control.go)
+3
View File
@@ -5,6 +5,7 @@ licenses(["notice"])
go_library(
name = "host",
srcs = [
"control.go",
"host.go",
"ioctl_unsafe.go",
"tty.go",
@@ -23,6 +24,8 @@ go_library(
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/memmap",
"//pkg/sentry/socket/control",
"//pkg/sentry/socket/unix/transport",
"//pkg/sentry/unimpl",
"//pkg/sentry/vfs",
"//pkg/sync",
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package host
import (
"syscall"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/socket/control"
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
type scmRights struct {
fds []int
}
func newSCMRights(fds []int) control.SCMRightsVFS2 {
return &scmRights{fds}
}
// Files implements control.SCMRights.Files.
func (c *scmRights) Files(ctx context.Context, max int) (control.RightsFilesVFS2, bool) {
n := max
var trunc bool
if l := len(c.fds); n > l {
n = l
} else if n < l {
trunc = true
}
rf := control.RightsFilesVFS2(fdsToFiles(ctx, c.fds[:n]))
// Only consume converted FDs (fdsToFiles may convert fewer than n FDs).
c.fds = c.fds[len(rf):]
return rf, trunc
}
// Clone implements transport.RightsControlMessage.Clone.
func (c *scmRights) Clone() transport.RightsControlMessage {
// Host rights never need to be cloned.
return nil
}
// Release implements transport.RightsControlMessage.Release.
func (c *scmRights) Release() {
for _, fd := range c.fds {
syscall.Close(fd)
}
c.fds = nil
}
// If an error is encountered, only files created before the error will be
// returned. This is what Linux does.
func fdsToFiles(ctx context.Context, fds []int) []*vfs.FileDescription {
files := make([]*vfs.FileDescription, 0, len(fds))
for _, fd := range fds {
// Get flags. We do it here because they may be modified
// by subsequent functions.
fileFlags, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_GETFL, 0)
if errno != 0 {
ctx.Warningf("Error retrieving host FD flags: %v", error(errno))
break
}
// Create the file backed by hostFD.
file, err := ImportFD(ctx, kernel.KernelFromContext(ctx).HostMount(), fd, false /* isTTY */)
if err != nil {
ctx.Warningf("Error creating file from host FD: %v", err)
break
}
if err := file.SetStatusFlags(ctx, auth.CredentialsFromContext(ctx), uint32(fileFlags&linux.O_NONBLOCK)); err != nil {
ctx.Warningf("Error setting flags on host FD file: %v", err)
break
}
files = append(files, file)
}
return files
}
+5 -1
View File
@@ -4,7 +4,10 @@ package(licenses = ["notice"])
go_library(
name = "control",
srcs = ["control.go"],
srcs = [
"control.go",
"control_vfs2.go",
],
imports = [
"gvisor.dev/gvisor/pkg/sentry/fs",
],
@@ -18,6 +21,7 @@ go_library(
"//pkg/sentry/kernel/auth",
"//pkg/sentry/socket",
"//pkg/sentry/socket/unix/transport",
"//pkg/sentry/vfs",
"//pkg/syserror",
"//pkg/tcpip",
"//pkg/usermem",
+20 -4
View File
@@ -41,6 +41,8 @@ type SCMCredentials interface {
Credentials(t *kernel.Task) (kernel.ThreadID, auth.UID, auth.GID)
}
// LINT.IfChange
// SCMRights represents a SCM_RIGHTS socket control message.
type SCMRights interface {
transport.RightsControlMessage
@@ -142,6 +144,8 @@ func PackRights(t *kernel.Task, rights SCMRights, cloexec bool, buf []byte, flag
return putCmsg(buf, flags, linux.SCM_RIGHTS, align, fds)
}
// LINT.ThenChange(./control_vfs2.go)
// scmCredentials represents an SCM_CREDENTIALS socket control message.
//
// +stateify savable
@@ -537,11 +541,19 @@ func Parse(t *kernel.Task, socketOrEndpoint interface{}, buf []byte) (socket.Con
}
if len(fds) > 0 {
rights, err := NewSCMRights(t, fds)
if err != nil {
return socket.ControlMessages{}, err
if kernel.VFS2Enabled {
rights, err := NewSCMRightsVFS2(t, fds)
if err != nil {
return socket.ControlMessages{}, err
}
cmsgs.Unix.Rights = rights
} else {
rights, err := NewSCMRights(t, fds)
if err != nil {
return socket.ControlMessages{}, err
}
cmsgs.Unix.Rights = rights
}
cmsgs.Unix.Rights = rights
}
return cmsgs, nil
@@ -566,6 +578,8 @@ func MakeCreds(t *kernel.Task) SCMCredentials {
return &scmCredentials{t, tcred.EffectiveKUID, tcred.EffectiveKGID}
}
// LINT.IfChange
// New creates default control messages if needed.
func New(t *kernel.Task, socketOrEndpoint interface{}, rights SCMRights) transport.ControlMessages {
return transport.ControlMessages{
@@ -573,3 +587,5 @@ func New(t *kernel.Task, socketOrEndpoint interface{}, rights SCMRights) transpo
Rights: rights,
}
}
// LINT.ThenChange(./control_vfs2.go)
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package control
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
// SCMRightsVFS2 represents a SCM_RIGHTS socket control message.
type SCMRightsVFS2 interface {
transport.RightsControlMessage
// Files returns up to max RightsFiles.
//
// Returned files are consumed and ownership is transferred to the caller.
// Subsequent calls to Files will return the next files.
Files(ctx context.Context, max int) (rf RightsFilesVFS2, truncated bool)
}
// RightsFiles represents a SCM_RIGHTS socket control message. A reference is
// maintained for each vfs.FileDescription and is release either when an FD is created or
// when the Release method is called.
type RightsFilesVFS2 []*vfs.FileDescription
// NewSCMRightsVFS2 creates a new SCM_RIGHTS socket control message
// representation using local sentry FDs.
func NewSCMRightsVFS2(t *kernel.Task, fds []int32) (SCMRightsVFS2, error) {
files := make(RightsFilesVFS2, 0, len(fds))
for _, fd := range fds {
file := t.GetFileVFS2(fd)
if file == nil {
files.Release()
return nil, syserror.EBADF
}
files = append(files, file)
}
return &files, nil
}
// Files implements SCMRights.Files.
func (fs *RightsFilesVFS2) Files(ctx context.Context, max int) (RightsFilesVFS2, bool) {
n := max
var trunc bool
if l := len(*fs); n > l {
n = l
} else if n < l {
trunc = true
}
rf := (*fs)[:n]
*fs = (*fs)[n:]
return rf, trunc
}
// Clone implements transport.RightsControlMessage.Clone.
func (fs *RightsFilesVFS2) Clone() transport.RightsControlMessage {
nfs := append(RightsFilesVFS2(nil), *fs...)
for _, nf := range nfs {
nf.IncRef()
}
return &nfs
}
// Release implements transport.RightsControlMessage.Release.
func (fs *RightsFilesVFS2) Release() {
for _, f := range *fs {
f.DecRef()
}
*fs = nil
}
// rightsFDsVFS2 gets up to the specified maximum number of FDs.
func rightsFDsVFS2(t *kernel.Task, rights SCMRightsVFS2, cloexec bool, max int) ([]int32, bool) {
files, trunc := rights.Files(t, max)
fds := make([]int32, 0, len(files))
for i := 0; i < max && len(files) > 0; i++ {
fd, err := t.NewFDFromVFS2(0, files[0], kernel.FDFlags{
CloseOnExec: cloexec,
})
files[0].DecRef()
files = files[1:]
if err != nil {
t.Warningf("Error inserting FD: %v", err)
// This is what Linux does.
break
}
fds = append(fds, int32(fd))
}
return fds, trunc
}
// PackRightsVFS2 packs as many FDs as will fit into the unused capacity of buf.
func PackRightsVFS2(t *kernel.Task, rights SCMRightsVFS2, cloexec bool, buf []byte, flags int) ([]byte, int) {
maxFDs := (cap(buf) - len(buf) - linux.SizeOfControlMessageHeader) / 4
// Linux does not return any FDs if none fit.
if maxFDs <= 0 {
flags |= linux.MSG_CTRUNC
return buf, flags
}
fds, trunc := rightsFDsVFS2(t, rights, cloexec, maxFDs)
if trunc {
flags |= linux.MSG_CTRUNC
}
align := t.Arch().Width()
return putCmsg(buf, flags, linux.SCM_RIGHTS, align, fds)
}
// NewVFS2 creates default control messages if needed.
func NewVFS2(t *kernel.Task, socketOrEndpoint interface{}, rights SCMRightsVFS2) transport.ControlMessages {
return transport.ControlMessages{
Credentials: makeCreds(t, socketOrEndpoint),
Rights: rights,
}
}
+1 -1
View File
@@ -804,7 +804,7 @@ func recvSingleMsg(t *kernel.Task, s socket.SocketVFS2, msgPtr usermem.Addr, fla
}
if cms.Unix.Rights != nil {
controlData, mflags = control.PackRights(t, cms.Unix.Rights.(control.SCMRights), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags)
controlData, mflags = control.PackRightsVFS2(t, cms.Unix.Rights.(control.SCMRightsVFS2), flags&linux.MSG_CMSG_CLOEXEC != 0, controlData, mflags)
}
// Copy the address to the caller.