github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    19  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/hostarch"
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/marshal"
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel"
    24  	"github.com/nicocha30/gvisor-ligolo/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  	// "A privileged process (under Linux: one with the CAP_SYS_RESOURCE
   112  	// capability in the initial user namespace) may make arbitrary changes
   113  	// to either limit value."
   114  	privileged := t.HasCapabilityIn(linux.CAP_SYS_RESOURCE, t.Kernel().RootUserNamespace())
   115  
   116  	oldLim, err := t.ThreadGroup().Limits().Set(resource, *newLim, privileged)
   117  	if err != nil {
   118  		return limits.Limit{}, err
   119  	}
   120  
   121  	if resource == limits.CPU {
   122  		t.NotifyRlimitCPUUpdated()
   123  	}
   124  	return oldLim, nil
   125  }
   126  
   127  // Getrlimit implements linux syscall getrlimit(2).
   128  func Getrlimit(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   129  	resource, ok := limits.FromLinuxResource[int(args[0].Int())]
   130  	if !ok {
   131  		// Return err; unknown limit.
   132  		return 0, nil, linuxerr.EINVAL
   133  	}
   134  	addr := args[1].Pointer()
   135  	rlim, err := newRlimit(t)
   136  	if err != nil {
   137  		return 0, nil, err
   138  	}
   139  	lim, err := prlimit64(t, resource, nil)
   140  	if err != nil {
   141  		return 0, nil, err
   142  	}
   143  	rlim.fromLimit(lim)
   144  	_, err = rlim.CopyOut(t, addr)
   145  	return 0, nil, err
   146  }
   147  
   148  // Setrlimit implements linux syscall setrlimit(2).
   149  func Setrlimit(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   150  	resource, ok := limits.FromLinuxResource[int(args[0].Int())]
   151  	if !ok {
   152  		// Return err; unknown limit.
   153  		return 0, nil, linuxerr.EINVAL
   154  	}
   155  	addr := args[1].Pointer()
   156  	rlim, err := newRlimit(t)
   157  	if err != nil {
   158  		return 0, nil, err
   159  	}
   160  	if _, err := rlim.CopyIn(t, addr); err != nil {
   161  		return 0, nil, linuxerr.EFAULT
   162  	}
   163  	_, err = prlimit64(t, resource, rlim.toLimit())
   164  	return 0, nil, err
   165  }
   166  
   167  // Prlimit64 implements linux syscall prlimit64(2).
   168  func Prlimit64(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
   169  	tid := kernel.ThreadID(args[0].Int())
   170  	resource, ok := limits.FromLinuxResource[int(args[1].Int())]
   171  	if !ok {
   172  		// Return err; unknown limit.
   173  		return 0, nil, linuxerr.EINVAL
   174  	}
   175  	newRlimAddr := args[2].Pointer()
   176  	oldRlimAddr := args[3].Pointer()
   177  
   178  	var newLim *limits.Limit
   179  	if newRlimAddr != 0 {
   180  		var nrl rlimit64
   181  		if err := nrl.copyIn(t, newRlimAddr); err != nil {
   182  			return 0, nil, linuxerr.EFAULT
   183  		}
   184  		newLim = nrl.toLimit()
   185  	}
   186  
   187  	if tid < 0 {
   188  		return 0, nil, linuxerr.EINVAL
   189  	}
   190  	ot := t
   191  	if tid > 0 {
   192  		if ot = t.PIDNamespace().TaskWithID(tid); ot == nil {
   193  			return 0, nil, linuxerr.ESRCH
   194  		}
   195  	}
   196  
   197  	// "To set or get the resources of a process other than itself, the caller
   198  	// must have the CAP_SYS_RESOURCE capability, or the real, effective, and
   199  	// saved set user IDs of the target process must match the real user ID of
   200  	// the caller and the real, effective, and saved set group IDs of the
   201  	// target process must match the real group ID of the caller."
   202  	if ot != t && !t.HasCapabilityIn(linux.CAP_SYS_RESOURCE, t.PIDNamespace().UserNamespace()) {
   203  		cred, tcred := t.Credentials(), ot.Credentials()
   204  		if cred.RealKUID != tcred.RealKUID ||
   205  			cred.RealKUID != tcred.EffectiveKUID ||
   206  			cred.RealKUID != tcred.SavedKUID ||
   207  			cred.RealKGID != tcred.RealKGID ||
   208  			cred.RealKGID != tcred.EffectiveKGID ||
   209  			cred.RealKGID != tcred.SavedKGID {
   210  			return 0, nil, linuxerr.EPERM
   211  		}
   212  	}
   213  
   214  	oldLim, err := prlimit64(ot, resource, newLim)
   215  	if err != nil {
   216  		return 0, nil, err
   217  	}
   218  
   219  	if oldRlimAddr != 0 {
   220  		if err := makeRlimit64(oldLim).copyOut(t, oldRlimAddr); err != nil {
   221  			return 0, nil, linuxerr.EFAULT
   222  		}
   223  	}
   224  
   225  	return 0, nil, nil
   226  }