mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
362 lines
12 KiB
Go
362 lines
12 KiB
Go
// Copyright 2019 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 vfs
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/limits"
|
|
)
|
|
|
|
// AccessTypes is a bitmask of Unix file permissions.
|
|
//
|
|
// +stateify savable
|
|
type AccessTypes uint16
|
|
|
|
// Bits in AccessTypes.
|
|
const (
|
|
MayExec AccessTypes = 1
|
|
MayWrite AccessTypes = 2
|
|
MayRead AccessTypes = 4
|
|
)
|
|
|
|
// OnlyRead returns true if access _only_ allows read.
|
|
func (a AccessTypes) OnlyRead() bool {
|
|
return a == MayRead
|
|
}
|
|
|
|
// MayRead returns true if access allows read.
|
|
func (a AccessTypes) MayRead() bool {
|
|
return a&MayRead != 0
|
|
}
|
|
|
|
// MayWrite returns true if access allows write.
|
|
func (a AccessTypes) MayWrite() bool {
|
|
return a&MayWrite != 0
|
|
}
|
|
|
|
// MayExec returns true if access allows exec.
|
|
func (a AccessTypes) MayExec() bool {
|
|
return a&MayExec != 0
|
|
}
|
|
|
|
func (a AccessTypes) checkPerms(perms AccessTypes) bool {
|
|
return uint16(a)&uint16(perms) == uint16(a)
|
|
}
|
|
|
|
// GenericCheckPermissions checks that creds has the given access rights on a
|
|
// file with the given permissions, UID, and GID, subject to the rules of
|
|
// fs/namei.c:generic_permission().
|
|
func GenericCheckPermissions(creds *auth.Credentials, ats AccessTypes, mode linux.FileMode, acl *PosixACL, kuid auth.KUID, kgid auth.KGID) error {
|
|
if acl != nil {
|
|
// Check against the ACL (if present)
|
|
if acl.checkPermissions(creds, ats, kuid, kgid) {
|
|
return nil
|
|
}
|
|
} else {
|
|
// Fallback: check permission bits.
|
|
perms := uint16(mode.Permissions())
|
|
if creds.EffectiveKUID == kuid {
|
|
perms >>= 6
|
|
} else if creds.InGroup(kgid) {
|
|
perms >>= 3
|
|
}
|
|
if uint16(ats)&perms == uint16(ats) {
|
|
// All permission bits match, access granted.
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// CAP_DAC_READ_SEARCH allows the caller to read and search arbitrary
|
|
// directories, and read arbitrary non-directory files.
|
|
if (mode.IsDir() && !ats.MayWrite()) || ats.OnlyRead() {
|
|
if creds.HasCapabilityOnFile(linux.CAP_DAC_READ_SEARCH, kuid, kgid) {
|
|
return nil
|
|
}
|
|
}
|
|
// CAP_DAC_OVERRIDE allows arbitrary access to directories, read/write
|
|
// access to non-directory files, and execute access to non-directory files
|
|
// for which at least one execute bit is set.
|
|
if mode.IsDir() || !ats.MayExec() || (mode.Permissions()&0111 != 0) {
|
|
if creds.HasCapabilityOnFile(linux.CAP_DAC_OVERRIDE, kuid, kgid) {
|
|
return nil
|
|
}
|
|
}
|
|
return linuxerr.EACCES
|
|
}
|
|
|
|
// MayLink determines whether creating a hard link to a file with the given
|
|
// mode, kuid, and kgid is permitted.
|
|
//
|
|
// This corresponds to Linux's fs/namei.c:may_linkat.
|
|
func MayLink(creds *auth.Credentials, mode linux.FileMode, acl *PosixACL, kuid auth.KUID, kgid auth.KGID) error {
|
|
// Source inode owner can hardlink all they like; otherwise, it must be a
|
|
// safe source.
|
|
if CanActAsOwner(creds, kuid) {
|
|
return nil
|
|
}
|
|
|
|
// Only regular files can be hard linked.
|
|
if mode.FileType() != linux.S_IFREG {
|
|
return linuxerr.EPERM
|
|
}
|
|
|
|
// Setuid files should not get pinned to the filesystem.
|
|
if mode&linux.S_ISUID != 0 {
|
|
return linuxerr.EPERM
|
|
}
|
|
|
|
// Executable setgid files should not get pinned to the filesystem, but we
|
|
// don't support S_IXGRP anyway.
|
|
|
|
// Hardlinking to unreadable or unwritable sources is dangerous.
|
|
if err := GenericCheckPermissions(creds, MayRead|MayWrite, mode, acl, kuid, kgid); err != nil {
|
|
return linuxerr.EPERM
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AccessTypesForOpenFlags returns the access types required to open a file
|
|
// with the given OpenOptions.Flags. Note that this is NOT the same thing as
|
|
// the set of accesses permitted for the opened file:
|
|
//
|
|
// - O_TRUNC causes MayWrite to be set in the returned AccessTypes (since it
|
|
// mutates the file), but does not permit writing to the open file description
|
|
// thereafter.
|
|
//
|
|
// - "Linux reserves the special, nonstandard access mode 3 (binary 11) in
|
|
// flags to mean: check for read and write permission on the file and return a
|
|
// file descriptor that can't be used for reading or writing." - open(2). Thus
|
|
// AccessTypesForOpenFlags returns MayRead|MayWrite in this case.
|
|
//
|
|
// Use May{Read,Write}FileWithOpenFlags() for these checks instead.
|
|
func AccessTypesForOpenFlags(opts *OpenOptions) AccessTypes {
|
|
ats := AccessTypes(0)
|
|
if opts.FileExec {
|
|
ats |= MayExec
|
|
}
|
|
|
|
switch opts.Flags & linux.O_ACCMODE {
|
|
case linux.O_RDONLY:
|
|
if opts.Flags&linux.O_TRUNC != 0 {
|
|
return ats | MayRead | MayWrite
|
|
}
|
|
return ats | MayRead
|
|
case linux.O_WRONLY:
|
|
return ats | MayWrite
|
|
default:
|
|
return ats | MayRead | MayWrite
|
|
}
|
|
}
|
|
|
|
// MayReadFileWithOpenFlags returns true if a file with the given open flags
|
|
// should be readable.
|
|
func MayReadFileWithOpenFlags(flags uint32) bool {
|
|
switch flags & linux.O_ACCMODE {
|
|
case linux.O_RDONLY, linux.O_RDWR:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MayWriteFileWithOpenFlags returns true if a file with the given open flags
|
|
// should be writable.
|
|
func MayWriteFileWithOpenFlags(flags uint32) bool {
|
|
switch flags & linux.O_ACCMODE {
|
|
case linux.O_WRONLY, linux.O_RDWR:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ShouldClearSGID determines if a change to the file's mode by the given
|
|
// creds, kuid, and kgid should trigger clearing of the file's setgid bit.
|
|
func ShouldClearSGID(creds *auth.Credentials, mode linux.FileMode, kuid auth.KUID, kgid auth.KGID) bool {
|
|
// "If the calling process is not privileged (Linux: does not have the CAP_FSETID capability),
|
|
// and the group of the file does not match the effective group ID of the process or one of its
|
|
// supplementary group IDs, the S_ISGID bit will be turned off, but this will not cause an error
|
|
// to be returned." - chmod(2)
|
|
return mode&linux.S_ISGID != 0 && !creds.HasCapabilityOnFile(linux.CAP_FSETID, kuid, kgid) && !creds.InGroup(kgid)
|
|
}
|
|
|
|
// CheckSetStat checks that creds has permission to change the metadata of a
|
|
// file with the given permissions, UID, and GID as specified by stat, subject
|
|
// to the rules of Linux's fs/attr.c:setattr_prepare(). Might mutate `opts`.
|
|
func CheckSetStat(ctx context.Context, creds *auth.Credentials, opts *SetStatOptions, mode linux.FileMode, acl *PosixACL, kuid auth.KUID, kgid auth.KGID) error {
|
|
stat := &opts.Stat
|
|
if stat.Mask&linux.STATX_SIZE != 0 {
|
|
limit, err := CheckLimit(ctx, 0, int64(stat.Size))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if limit < int64(stat.Size) {
|
|
return linuxerr.ErrExceedsFileSizeLimit
|
|
}
|
|
}
|
|
if stat.Mask&linux.STATX_MODE != 0 {
|
|
if !CanActAsOwner(creds, kuid) {
|
|
return linuxerr.EPERM
|
|
}
|
|
if ShouldClearSGID(creds, linux.FileMode(stat.Mode), kuid, kgid) {
|
|
stat.Mode &^= linux.S_ISGID
|
|
}
|
|
}
|
|
if stat.Mask&linux.STATX_UID != 0 {
|
|
if !((creds.EffectiveKUID == kuid && auth.KUID(stat.UID) == kuid) ||
|
|
creds.HasCapabilityOnFile(linux.CAP_CHOWN, kuid, kgid)) {
|
|
return linuxerr.EPERM
|
|
}
|
|
}
|
|
if stat.Mask&linux.STATX_GID != 0 {
|
|
if !((creds.EffectiveKUID == kuid && creds.InGroup(auth.KGID(stat.GID))) ||
|
|
creds.HasCapabilityOnFile(linux.CAP_CHOWN, kuid, kgid)) {
|
|
return linuxerr.EPERM
|
|
}
|
|
}
|
|
if opts.NeedWritePerm {
|
|
if err := GenericCheckPermissions(creds, MayWrite, mode, acl, kuid, kgid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if stat.Mask&(linux.STATX_ATIME|linux.STATX_MTIME|linux.STATX_CTIME) != 0 {
|
|
if !CanActAsOwner(creds, kuid) {
|
|
if (stat.Mask&linux.STATX_ATIME != 0 && stat.Atime.Nsec != linux.UTIME_NOW) ||
|
|
(stat.Mask&linux.STATX_MTIME != 0 && stat.Mtime.Nsec != linux.UTIME_NOW) ||
|
|
(stat.Mask&linux.STATX_CTIME != 0 && stat.Ctime.Nsec != linux.UTIME_NOW) {
|
|
return linuxerr.EPERM
|
|
}
|
|
if err := GenericCheckPermissions(creds, MayWrite, mode, acl, kuid, kgid); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckDeleteSticky checks whether the sticky bit is set on a directory with
|
|
// the given file mode, and if so, checks whether creds has permission to
|
|
// remove a file owned by childKUID from a directory with the given mode.
|
|
// CheckDeleteSticky is consistent with fs/linux.h:check_sticky().
|
|
func CheckDeleteSticky(creds *auth.Credentials, parentMode linux.FileMode, parentKUID auth.KUID, childKUID auth.KUID, childKGID auth.KGID) error {
|
|
if parentMode&linux.ModeSticky == 0 {
|
|
return nil
|
|
}
|
|
if creds.EffectiveKUID == childKUID ||
|
|
creds.EffectiveKUID == parentKUID ||
|
|
creds.HasCapabilityOnFile(linux.CAP_FOWNER, childKUID, childKGID) {
|
|
return nil
|
|
}
|
|
return linuxerr.EPERM
|
|
}
|
|
|
|
// CanActAsOwner returns true if creds can act as the owner of a file with the
|
|
// given owning UID, consistent with Linux's
|
|
// fs/inode.c:inode_owner_or_capable().
|
|
func CanActAsOwner(creds *auth.Credentials, kuid auth.KUID) bool {
|
|
if creds.EffectiveKUID == kuid {
|
|
return true
|
|
}
|
|
return creds.HasSelfCapability(linux.CAP_FOWNER) && creds.UserNamespace.MapFromKUID(kuid).Ok()
|
|
}
|
|
|
|
// CheckLimit enforces file size rlimits. It returns error if the write
|
|
// operation must not proceed. Otherwise it returns the max length allowed to
|
|
// without violating the limit.
|
|
func CheckLimit(ctx context.Context, offset, size int64) (int64, error) {
|
|
fileSizeLimit := limits.FromContextOrDie(ctx).Get(limits.FileSize).Cur
|
|
if fileSizeLimit > math.MaxInt64 {
|
|
return size, nil
|
|
}
|
|
if offset >= int64(fileSizeLimit) {
|
|
return 0, linuxerr.ErrExceedsFileSizeLimit
|
|
}
|
|
remaining := int64(fileSizeLimit) - offset
|
|
if remaining < size {
|
|
return remaining, nil
|
|
}
|
|
return size, nil
|
|
}
|
|
|
|
// CheckXattrPermissions checks permissions for extended attribute access.
|
|
// This is analogous to fs/xattr.c:xattr_permission(). Some key differences:
|
|
// - Does not check for read-only filesystem property.
|
|
// - Does not check inode immutability or append only mode. In both cases EPERM
|
|
// must be returned by filesystem implementations.
|
|
// - Does not do inode permission checks. Filesystem implementations should
|
|
// handle inode permission checks as they may differ across implementations.
|
|
// - Writes in security namespace are not supported, except for writes to
|
|
// "security.capability", which are required to support file capabilities.
|
|
func CheckXattrPermissions(creds *auth.Credentials, ats AccessTypes, mode linux.FileMode, kuid auth.KUID, name string) error {
|
|
switch {
|
|
case strings.HasPrefix(name, linux.XATTR_TRUSTED_PREFIX):
|
|
// The trusted.* namespace can only be accessed by privileged
|
|
// users.
|
|
if creds.HasRootCapability(linux.CAP_SYS_ADMIN) {
|
|
return nil
|
|
}
|
|
if ats.MayWrite() {
|
|
return linuxerr.EPERM
|
|
}
|
|
return linuxerr.ENODATA
|
|
case strings.HasPrefix(name, linux.XATTR_USER_PREFIX):
|
|
// In the user.* namespace, only regular files and directories can have
|
|
// extended attributes. For sticky directories, only the owner and
|
|
// privileged users can write attributes.
|
|
filetype := mode.FileType()
|
|
if filetype != linux.ModeRegular && filetype != linux.ModeDirectory {
|
|
if ats.MayWrite() {
|
|
return linuxerr.EPERM
|
|
}
|
|
return linuxerr.ENODATA
|
|
}
|
|
if filetype == linux.ModeDirectory && mode&linux.ModeSticky != 0 && ats.MayWrite() && !CanActAsOwner(creds, kuid) {
|
|
return linuxerr.EPERM
|
|
}
|
|
case strings.HasPrefix(name, linux.XATTR_SECURITY_PREFIX):
|
|
if ats.MayRead() {
|
|
return nil
|
|
}
|
|
if name == linux.XATTR_SECURITY_CAPABILITY {
|
|
return nil
|
|
}
|
|
return linuxerr.EOPNOTSUPP
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearSUIDAndSGID clears the setuid and/or setgid bits after a chown or write.
|
|
// Depending on the mode, neither bit, only the setuid bit, or both are cleared.
|
|
func ClearSUIDAndSGID(mode uint32) uint32 {
|
|
// Directories don't have their bits changed.
|
|
if mode&linux.ModeDirectory == linux.ModeDirectory {
|
|
return mode
|
|
}
|
|
|
|
// Changing owners always disables the setuid bit. It disables
|
|
// the setgid bit when the file is executable.
|
|
mode &= ^uint32(linux.ModeSetUID)
|
|
if sgid := uint32(linux.ModeSetGID | linux.ModeGroupExec); mode&sgid == sgid {
|
|
mode &= ^uint32(linux.ModeSetGID)
|
|
}
|
|
return mode
|
|
}
|