// Copyright 2018 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 auth

import (
	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/errors/linuxerr"
	"gvisor.dev/gvisor/pkg/sentry/seccheck"
	pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
)

// Credentials contains information required to authorize privileged operations
// in a user namespace.
//
// +stateify savable
type Credentials struct {
	// Real/effective/saved user/group IDs in the root user namespace. None of
	// these should ever be NoID.
	RealKUID      KUID
	EffectiveKUID KUID
	SavedKUID     KUID
	RealKGID      KGID
	EffectiveKGID KGID
	SavedKGID     KGID

	// Filesystem user/group IDs are not implemented. "... setfsuid() is
	// nowadays unneeded and should be avoided in new applications (likewise
	// for setfsgid(2))." - setfsuid(2)

	// Supplementary groups used by set/getgroups.
	//
	// ExtraKGIDs slices are immutable, allowing multiple Credentials with the
	// same ExtraKGIDs to share the same slice.
	ExtraKGIDs []KGID

	// The capability sets applicable to this set of credentials.
	PermittedCaps   CapabilitySet
	InheritableCaps CapabilitySet
	EffectiveCaps   CapabilitySet
	BoundingCaps    CapabilitySet
	AmbientCaps     CapabilitySet

	// KeepCaps is the flag for PR_SET_KEEPCAPS which allow capabilities to be
	// maintained after a switch from root user to non-root user via setuid().
	KeepCaps bool

	// The user namespace associated with the owner of the credentials.
	UserNamespace *UserNamespace
}

// NewAnonymousCredentials returns a set of credentials with no capabilities in
// any user namespace.
func NewAnonymousCredentials() *Credentials {
	// Create a new root user namespace. Since the new namespace's owner is
	// KUID 0 and the returned credentials have non-zero KUID/KGID, the
	// returned credentials do not have any capabilities in the new namespace.
	// Since the new namespace is not part of any existing user namespace
	// hierarchy, the returned credentials do not have any capabilities in any
	// other namespace.
	return &Credentials{
		RealKUID:      NobodyKUID,
		EffectiveKUID: NobodyKUID,
		SavedKUID:     NobodyKUID,
		RealKGID:      NobodyKGID,
		EffectiveKGID: NobodyKGID,
		SavedKGID:     NobodyKGID,
		UserNamespace: NewRootUserNamespace(),
	}
}

// NewRootCredentials returns a set of credentials with KUID and KGID 0 (i.e.
// global root) in user namespace ns.
func NewRootCredentials(ns *UserNamespace) *Credentials {
	// I can't find documentation for this anywhere, but it's correct for the
	// inheritable capability set to be initially empty (the capabilities test
	// checks for this property).
	return &Credentials{
		RealKUID:      RootKUID,
		EffectiveKUID: RootKUID,
		SavedKUID:     RootKUID,
		RealKGID:      RootKGID,
		EffectiveKGID: RootKGID,
		SavedKGID:     RootKGID,
		PermittedCaps: AllCapabilities,
		EffectiveCaps: AllCapabilities,
		BoundingCaps:  AllCapabilities,
		UserNamespace: ns,
	}
}

// NewUserCredentials returns a set of credentials based on the given UID, GIDs,
// and capabilities in a given namespace. If all arguments are their zero
// values, this returns the same credentials as NewRootCredentials.
func NewUserCredentials(kuid KUID, kgid KGID, extraKGIDs []KGID, capabilities *TaskCapabilities, ns *UserNamespace) *Credentials {
	creds := NewRootCredentials(ns)

	// Set the UID.
	uid := kuid
	creds.RealKUID = uid
	creds.EffectiveKUID = uid
	creds.SavedKUID = uid

	// Set GID.
	gid := kgid
	creds.RealKGID = gid
	creds.EffectiveKGID = gid
	creds.SavedKGID = gid

	// Set additional GIDs.
	creds.ExtraKGIDs = append(creds.ExtraKGIDs, extraKGIDs...)

	// Set capabilities.
	if capabilities != nil {
		creds.PermittedCaps = capabilities.PermittedCaps
		creds.EffectiveCaps = capabilities.EffectiveCaps
		creds.BoundingCaps = capabilities.BoundingCaps
		creds.InheritableCaps = capabilities.InheritableCaps
		creds.AmbientCaps = capabilities.AmbientCaps
	} else {
		// If no capabilities are specified, grant capabilities consistent with
		// setresuid + setresgid from NewRootCredentials to the given uid and
		// gid.
		if kuid == RootKUID {
			creds.PermittedCaps = AllCapabilities
			creds.EffectiveCaps = AllCapabilities
		} else {
			creds.PermittedCaps = 0
			creds.EffectiveCaps = 0
		}
		creds.BoundingCaps = AllCapabilities
	}

	return creds
}

// Fork generates an identical copy of a set of credentials.
func (c *Credentials) Fork() *Credentials {
	nc := new(Credentials)
	*nc = *c // Copy-by-value; this is legal for all fields.
	return nc
}

// ForkIntoUserNamespace returns a copy of c after its caller has entered user
// namespace ns.
func (c *Credentials) ForkIntoUserNamespace(ns *UserNamespace) *Credentials {
	nc := c.Fork()
	nc.UserNamespace = ns
	// "The child process created by clone(2) with the CLONE_NEWUSER flag
	// starts out with a complete set of capabilities in the new user
	// namespace. Likewise, a process that creates a new user namespace using
	// unshare(2) or joins an existing user namespace using setns(2) gains a
	// full set of capabilities in that namespace."
	nc.PermittedCaps = AllCapabilities
	nc.InheritableCaps = 0
	nc.EffectiveCaps = AllCapabilities
	nc.BoundingCaps = AllCapabilities
	nc.AmbientCaps = 0
	// "A call to clone(2), unshare(2), or setns(2) using the CLONE_NEWUSER
	// flag sets the "securebits" flags (see capabilities(7)) to their default
	// values (all flags disabled) in the child (for clone(2)) or caller (for
	// unshare(2), or setns(2)." - user_namespaces(7)
	nc.KeepCaps = false
	return nc
}

// InGroup returns true if c is in group kgid. Compare Linux's
// kernel/groups.c:in_group_p().
func (c *Credentials) InGroup(kgid KGID) bool {
	if c.EffectiveKGID == kgid {
		return true
	}
	for _, extraKGID := range c.ExtraKGIDs {
		if extraKGID == kgid {
			return true
		}
	}
	return false
}

// HasCapabilityIn returns true if c has capability cp in ns.
func (c *Credentials) HasCapabilityIn(cp linux.Capability, ns *UserNamespace) bool {
	for {
		// "1. A process has a capability inside a user namespace if it is a member
		// of that namespace and it has the capability in its effective capability
		// set." - user_namespaces(7)
		if c.UserNamespace == ns {
			return CapabilitySetOf(cp)&c.EffectiveCaps != 0
		}
		// "3. ... A process that resides in the parent of the user namespace and
		// whose effective user ID matches the owner of the namespace has all
		// capabilities in the namespace."
		if c.UserNamespace == ns.parent && c.EffectiveKUID == ns.owner {
			return true
		}
		// "2. If a process has a capability in a user namespace, then it has that
		// capability in all child (and further removed descendant) namespaces as
		// well."
		if ns.parent == nil {
			return false
		}
		ns = ns.parent
	}
}

// HasSelfCapability returns true if c has capability cp in its user namespace.
func (c *Credentials) HasSelfCapability(cp linux.Capability) bool {
	return c.HasCapabilityIn(cp, c.UserNamespace)
}

// HasRootCapability returns true if c has capability cp in c's user namespace's root.
func (c *Credentials) HasRootCapability(cp linux.Capability) bool {
	return c.HasCapabilityIn(cp, c.UserNamespace.Root())
}

// HasCapabilityOnFile returns true if creds has the given capability with
// respect to a file with the given owning UID and GID, consistent with Linux's
// kernel/capability.c:capable_wrt_inode_uidgid().
func (c *Credentials) HasCapabilityOnFile(cp linux.Capability, kuid KUID, kgid KGID) bool {
	return c.HasSelfCapability(cp) && c.UserNamespace.MapFromKUID(kuid).Ok() && c.UserNamespace.MapFromKGID(kgid).Ok()
}

// UseUID checks that c can use uid in its user namespace, then translates it
// to the root user namespace.
//
// The checks UseUID does are common, but you should verify that it's doing
// exactly what you want.
func (c *Credentials) UseUID(uid UID) (KUID, error) {
	// uid must be mapped.
	kuid := c.UserNamespace.MapToKUID(uid)
	if !kuid.Ok() {
		return NoID, linuxerr.EINVAL
	}
	// If c has CAP_SETUID, then it can use any UID in its user namespace.
	if c.HasSelfCapability(linux.CAP_SETUID) {
		return kuid, nil
	}
	// Otherwise, c must already have the UID as its real, effective, or saved
	// set-user-ID.
	if kuid == c.RealKUID || kuid == c.EffectiveKUID || kuid == c.SavedKUID {
		return kuid, nil
	}
	return NoID, linuxerr.EPERM
}

// UseGID checks that c can use gid in its user namespace, then translates it
// to the root user namespace.
func (c *Credentials) UseGID(gid GID) (KGID, error) {
	kgid := c.UserNamespace.MapToKGID(gid)
	if !kgid.Ok() {
		return NoID, linuxerr.EINVAL
	}
	if c.HasSelfCapability(linux.CAP_SETGID) {
		return kgid, nil
	}
	if kgid == c.RealKGID || kgid == c.EffectiveKGID || kgid == c.SavedKGID {
		return kgid, nil
	}
	return NoID, linuxerr.EPERM
}

// LoadSeccheckData sets credential data based on mask.
func (c *Credentials) LoadSeccheckData(mask seccheck.FieldMask, info *pb.ContextData) {
	if mask.Contains(seccheck.FieldCtxtCredentials) {
		info.Credentials = &pb.Credentials{
			RealUid:      uint32(c.RealKUID),
			EffectiveUid: uint32(c.EffectiveKUID),
			SavedUid:     uint32(c.SavedKUID),
			RealGid:      uint32(c.RealKGID),
			EffectiveGid: uint32(c.EffectiveKGID),
			SavedGid:     uint32(c.SavedKGID),
		}
	}
}
