gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/syscalls/linux/sys_rlimit.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package linux
    16  
    17  import (
    18  	"gvisor.dev/gvisor/pkg/abi/linux"
    19  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    20  	"gvisor.dev/gvisor/pkg/hostarch"
    21  	"gvisor.dev/gvisor/pkg/marshal"
    22  	"gvisor.dev/gvisor/pkg/sentry/arch"
    23  	"gvisor.dev/gvisor/pkg/sentry/kernel"
    24  	"gvisor.dev/gvisor/pkg/sentry/limits"
    25  )
    26  
    27  // rlimit describes an implementation of 'struct rlimit', which may vary from
    28  // system-to-system.
    29  type rlimit interface {
    30  	marshal.Marshallable
    31  
    32  	// toLimit converts an rlimit to a limits.Limit.
    33  	toLimit() *limits.Limit
    34  
    35  	// fromLimit converts a limits.Limit to an rlimit.
    36  	fromLimit(lim limits.Limit)
    37  }
    38  
    39  // newRlimit returns the appropriate rlimit type for 'struct rlimit' on this system.
    40  func newRlimit(t *kernel.Task) (rlimit, error) {
    41  	switch t.Arch().Width() {
    42  	case 8:
    43  		// On 64-bit system, struct rlimit and struct rlimit64 are identical.
    44  		return &rlimit64{}, nil
    45  	default:
    46  		return nil, linuxerr.ENOSYS
    47  	}
    48  }
    49  
    50  // +marshal
    51  type rlimit64 struct {
    52  	Cur uint64
    53  	Max uint64
    54  }
    55  
    56  func (r *rlimit64) toLimit() *limits.Limit {
    57  	return &limits.Limit{
    58  		Cur: limits.FromLinux(r.Cur),
    59  		Max: limits.FromLinux(r.Max),
    60  	}
    61  }
    62  
    63  func (r *rlimit64) fromLimit(lim limits.Limit) {
    64  	*r = rlimit64{
    65  		Cur: limits.ToLinux(lim.Cur),
    66  		Max: limits.ToLinux(lim.Max),
    67  	}
    68  }
    69  
    70  func (r *rlimit64) copyIn(t *kernel.Task, addr hostarch.Addr) error {
    71  	_, err := r.CopyIn(t, addr)
    72  	return err
    73  }
    74  
    75  func (r *rlimit64) copyOut(t *kernel.Task, addr hostarch.Addr) error {
    76  	_, err := r.CopyOut(t, addr)
    77  	return err
    78  }
    79  
    80  func makeRlimit64(lim limits.Limit) *rlimit64 {
    81  	return &rlimit64{Cur: lim.Cur, Max: lim.Max}
    82  }
    83  
    84  // setableLimits is the set of supported setable limits.
    85  var setableLimits = map[limits.LimitType]struct{}{
    86  	limits.NumberOfFiles: {},
    87  	limits.AS:            {},
    88  	limits.CPU:           {},
    89  	limits.Data:          {},
    90  	limits.FileSize:      {},
    91  	limits.MemoryLocked:  {},
    92  	limits.Stack:         {},
    93  	// RSS can be set, but it's not enforced because Linux doesn't enforce it
    94  	// either: "This limit has effect only in Linux 2.4.x, x < 30"
    95  	limits.Rss: {},
    96  	// These are not enforced, but we include them here to avoid returning
    97  	// EPERM, since some apps expect them to succeed.
    98  	limits.Core:         {},
    99  	limits.ProcessCount: {},
   100  }
   101  
   102  func prlimit64(t *kernel.Task, resource limits.LimitType, newLim *limits.Limit) (limits.Limit, error) {
   103  	if newLim == nil {
   104  		return t.ThreadGroup().Limits().Get(resource), nil
   105  	}
   106  
   107  	if _, ok := setableLimits[resource]; !ok {
   108  		return limits.Limit{}, linuxerr.EPERM
   109  	}
   110  
   111  	switch resource {
   112  	case limits.NumberOfFiles:
   113  		if newLim.Max > uint64(t.Kernel().MaxFDLimit.Load()) {
   114  			return limits.Limit{}, linuxerr.EPERM
   115  		}
   116  	}
   117  
   118  	// "A privileged process (under Linux: one with the CAP_SYS_RESOURCE
   119  	// capability in the initial user namespace) may make arbitrary changes
   120  	// to either limit value."
   121  	privileged := t.HasCapabilityIn(linux.CAP_SYS_RESOURCE, t.Kernel().RootUserNamespace())
   122  
   123  	oldLim, err := t.ThreadGroup().Limits().Set(resource, *newLim, privileged)
   124  	if err != nil {
   125  		return limits.Limit{}, err
   126  	}
   127  
   128  	if resource == limits.CPU {
   129  		t.NotifyRlimitCPUUpdated()
   130  	}
   131  	return oldLim, nil
   132  }
   133  
   134  // Getrlimit implements linux syscall getrlimit(2).
   135  func Getrlimit(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   136  	resource, ok := limits.FromLinuxResource[int(args[0].Int())]
   137  	if !ok {
   138  		// Return err; unknown limit.
   139  		return 0, nil, linuxerr.EINVAL
   140  	}
   141  	addr := args[1].Pointer()
   142  	rlim, err := newRlimit(t)
   143  	if err != nil {
   144  		return 0, nil, err
   145  	}
   146  	lim, err := prlimit64(t, resource, nil)
   147  	if err != nil {
   148  		return 0, nil, err
   149  	}
   150  	rlim.fromLimit(lim)
   151  	_, err = rlim.CopyOut(t, addr)
   152  	return 0, nil, err
   153  }
   154  
   155  // Setrlimit implements linux syscall setrlimit(2).
   156  func Setrlimit(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   157  	resource, ok := limits.FromLinuxResource[int(args[0].Int())]
   158  	if !ok {
   159  		// Return err; unknown limit.
   160  		return 0, nil, linuxerr.EINVAL
   161  	}
   162  	addr := args[1].Pointer()
   163  	rlim, err := newRlimit(t)
   164  	if err != nil {
   165  		return 0, nil, err
   166  	}
   167  	if _, err := rlim.CopyIn(t, addr); err != nil {
   168  		return 0, nil, linuxerr.EFAULT
   169  	}
   170  	_, err = prlimit64(t, resource, rlim.toLimit())
   171  	return 0, nil, err
   172  }
   173  
   174  // Prlimit64 implements linux syscall prlimit64(2).
   175  func Prlimit64(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   176  	tid := kernel.ThreadID(args[0].Int())
   177  	resource, ok := limits.FromLinuxResource[int(args[1].Int())]
   178  	if !ok {
   179  		// Return err; unknown limit.
   180  		return 0, nil, linuxerr.EINVAL
   181  	}
   182  	newRlimAddr := args[2].Pointer()
   183  	oldRlimAddr := args[3].Pointer()
   184  
   185  	var newLim *limits.Limit
   186  	if newRlimAddr != 0 {
   187  		var nrl rlimit64
   188  		if err := nrl.copyIn(t, newRlimAddr); err != nil {
   189  			return 0, nil, linuxerr.EFAULT
   190  		}
   191  		newLim = nrl.toLimit()
   192  	}
   193  
   194  	if tid < 0 {
   195  		return 0, nil, linuxerr.EINVAL
   196  	}
   197  	ot := t
   198  	if tid > 0 {
   199  		if ot = t.PIDNamespace().TaskWithID(tid); ot == nil {
   200  			return 0, nil, linuxerr.ESRCH
   201  		}
   202  	}
   203  
   204  	// "To set or get the resources of a process other than itself, the caller
   205  	// must have the CAP_SYS_RESOURCE capability, or the real, effective, and
   206  	// saved set user IDs of the target process must match the real user ID of
   207  	// the caller and the real, effective, and saved set group IDs of the
   208  	// target process must match the real group ID of the caller."
   209  	if ot != t && !t.HasCapabilityIn(linux.CAP_SYS_RESOURCE, t.PIDNamespace().UserNamespace()) {
   210  		cred, tcred := t.Credentials(), ot.Credentials()
   211  		if cred.RealKUID != tcred.RealKUID ||
   212  			cred.RealKUID != tcred.EffectiveKUID ||
   213  			cred.RealKUID != tcred.SavedKUID ||
   214  			cred.RealKGID != tcred.RealKGID ||
   215  			cred.RealKGID != tcred.EffectiveKGID ||
   216  			cred.RealKGID != tcred.SavedKGID {
   217  			return 0, nil, linuxerr.EPERM
   218  		}
   219  	}
   220  
   221  	oldLim, err := prlimit64(ot, resource, newLim)
   222  	if err != nil {
   223  		return 0, nil, err
   224  	}
   225  
   226  	if oldRlimAddr != 0 {
   227  		if err := makeRlimit64(oldLim).copyOut(t, oldRlimAddr); err != nil {
   228  			return 0, nil, linuxerr.EFAULT
   229  		}
   230  	}
   231  
   232  	return 0, nil, nil
   233  }