286 lines
9.0 KiB
Go
286 lines
9.0 KiB
Go
// 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/sentry/context"
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
)
|
|
|
|
// MapFromKUID translates kuid, a UID in the root namespace, to a UID in ns.
|
|
func (ns *UserNamespace) MapFromKUID(kuid KUID) UID {
|
|
if ns.parent == nil {
|
|
return UID(kuid)
|
|
}
|
|
return UID(ns.mapID(&ns.uidMapFromParent, uint32(ns.parent.MapFromKUID(kuid))))
|
|
}
|
|
|
|
// MapFromKGID translates kgid, a GID in the root namespace, to a GID in ns.
|
|
func (ns *UserNamespace) MapFromKGID(kgid KGID) GID {
|
|
if ns.parent == nil {
|
|
return GID(kgid)
|
|
}
|
|
return GID(ns.mapID(&ns.gidMapFromParent, uint32(ns.parent.MapFromKGID(kgid))))
|
|
}
|
|
|
|
// MapToKUID translates uid, a UID in ns, to a UID in the root namespace.
|
|
func (ns *UserNamespace) MapToKUID(uid UID) KUID {
|
|
if ns.parent == nil {
|
|
return KUID(uid)
|
|
}
|
|
return ns.parent.MapToKUID(UID(ns.mapID(&ns.uidMapToParent, uint32(uid))))
|
|
}
|
|
|
|
// MapToKGID translates gid, a GID in ns, to a GID in the root namespace.
|
|
func (ns *UserNamespace) MapToKGID(gid GID) KGID {
|
|
if ns.parent == nil {
|
|
return KGID(gid)
|
|
}
|
|
return ns.parent.MapToKGID(GID(ns.mapID(&ns.gidMapToParent, uint32(gid))))
|
|
}
|
|
|
|
func (ns *UserNamespace) mapID(m *idMapSet, id uint32) uint32 {
|
|
if id == NoID {
|
|
return NoID
|
|
}
|
|
ns.mu.Lock()
|
|
defer ns.mu.Unlock()
|
|
if it := m.FindSegment(id); it.Ok() {
|
|
return it.Value() + (id - it.Start())
|
|
}
|
|
return NoID
|
|
}
|
|
|
|
// allIDsMapped returns true if all IDs in the range [start, end) are mapped in
|
|
// m.
|
|
//
|
|
// Preconditions: end >= start.
|
|
func (ns *UserNamespace) allIDsMapped(m *idMapSet, start, end uint32) bool {
|
|
ns.mu.Lock()
|
|
defer ns.mu.Unlock()
|
|
return m.SpanRange(idMapRange{start, end}) == end-start
|
|
}
|
|
|
|
// An IDMapEntry represents a mapping from a range of contiguous IDs in a user
|
|
// namespace to an equally-sized range of contiguous IDs in the namespace's
|
|
// parent.
|
|
//
|
|
// +stateify savable
|
|
type IDMapEntry struct {
|
|
// FirstID is the first ID in the range in the namespace.
|
|
FirstID uint32
|
|
|
|
// FirstParentID is the first ID in the range in the parent namespace.
|
|
FirstParentID uint32
|
|
|
|
// Length is the number of IDs in the range.
|
|
Length uint32
|
|
}
|
|
|
|
// SetUIDMap instructs ns to translate UIDs as specified by entries.
|
|
//
|
|
// Note: SetUIDMap does not place an upper bound on the number of entries, but
|
|
// Linux does. This restriction is implemented in SetUIDMap's caller, the
|
|
// implementation of /proc/[pid]/uid_map.
|
|
func (ns *UserNamespace) SetUIDMap(ctx context.Context, entries []IDMapEntry) error {
|
|
c := CredentialsFromContext(ctx)
|
|
|
|
ns.mu.Lock()
|
|
defer ns.mu.Unlock()
|
|
// "After the creation of a new user namespace, the uid_map file of *one*
|
|
// of the processes in the namespace may be written to *once* to define the
|
|
// mapping of user IDs in the new user namespace. An attempt to write more
|
|
// than once to a uid_map file in a user namespace fails with the error
|
|
// EPERM. Similar rules apply for gid_map files." - user_namespaces(7)
|
|
if !ns.uidMapFromParent.IsEmpty() {
|
|
return syserror.EPERM
|
|
}
|
|
// "At least one line must be written to the file."
|
|
if len(entries) == 0 {
|
|
return syserror.EINVAL
|
|
}
|
|
// """
|
|
// In order for a process to write to the /proc/[pid]/uid_map
|
|
// (/proc/[pid]/gid_map) file, all of the following requirements must be
|
|
// met:
|
|
//
|
|
// 1. The writing process must have the CAP_SETUID (CAP_SETGID) capability
|
|
// in the user namespace of the process pid.
|
|
// """
|
|
if !c.HasCapabilityIn(linux.CAP_SETUID, ns) {
|
|
return syserror.EPERM
|
|
}
|
|
// "2. The writing process must either be in the user namespace of the process
|
|
// pid or be in the parent user namespace of the process pid."
|
|
if c.UserNamespace != ns && c.UserNamespace != ns.parent {
|
|
return syserror.EPERM
|
|
}
|
|
// """
|
|
// 3. (see trySetUIDMap)
|
|
//
|
|
// 4. One of the following two cases applies:
|
|
//
|
|
// * Either the writing process has the CAP_SETUID (CAP_SETGID) capability
|
|
// in the parent user namespace.
|
|
// """
|
|
if !c.HasCapabilityIn(linux.CAP_SETUID, ns.parent) {
|
|
// """
|
|
// * Or otherwise all of the following restrictions apply:
|
|
//
|
|
// + The data written to uid_map (gid_map) must consist of a single line
|
|
// that maps the writing process' effective user ID (group ID) in the
|
|
// parent user namespace to a user ID (group ID) in the user namespace.
|
|
// """
|
|
if len(entries) != 1 || ns.parent.MapToKUID(UID(entries[0].FirstParentID)) != c.EffectiveKUID || entries[0].Length != 1 {
|
|
return syserror.EPERM
|
|
}
|
|
// """
|
|
// + The writing process must have the same effective user ID as the
|
|
// process that created the user namespace.
|
|
// """
|
|
if c.EffectiveKUID != ns.owner {
|
|
return syserror.EPERM
|
|
}
|
|
}
|
|
// trySetUIDMap leaves data in maps if it fails.
|
|
if err := ns.trySetUIDMap(entries); err != nil {
|
|
ns.uidMapFromParent.RemoveAll()
|
|
ns.uidMapToParent.RemoveAll()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ns *UserNamespace) trySetUIDMap(entries []IDMapEntry) error {
|
|
for _, e := range entries {
|
|
// Determine upper bounds and check for overflow. This implicitly
|
|
// checks for NoID.
|
|
lastID := e.FirstID + e.Length
|
|
if lastID <= e.FirstID {
|
|
return syserror.EINVAL
|
|
}
|
|
lastParentID := e.FirstParentID + e.Length
|
|
if lastParentID <= e.FirstParentID {
|
|
return syserror.EINVAL
|
|
}
|
|
// "3. The mapped user IDs (group IDs) must in turn have a mapping in
|
|
// the parent user namespace."
|
|
// Only the root namespace has a nil parent, and root is assigned
|
|
// mappings when it's created, so SetUIDMap would have returned EPERM
|
|
// without reaching this point if ns is root.
|
|
if !ns.parent.allIDsMapped(&ns.parent.uidMapToParent, e.FirstParentID, lastParentID) {
|
|
return syserror.EPERM
|
|
}
|
|
// If either of these Adds fail, we have an overlapping range.
|
|
if !ns.uidMapFromParent.Add(idMapRange{e.FirstParentID, lastParentID}, e.FirstID) {
|
|
return syserror.EINVAL
|
|
}
|
|
if !ns.uidMapToParent.Add(idMapRange{e.FirstID, lastID}, e.FirstParentID) {
|
|
return syserror.EINVAL
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetGIDMap instructs ns to translate GIDs as specified by entries.
|
|
func (ns *UserNamespace) SetGIDMap(ctx context.Context, entries []IDMapEntry) error {
|
|
c := CredentialsFromContext(ctx)
|
|
|
|
ns.mu.Lock()
|
|
defer ns.mu.Unlock()
|
|
if !ns.gidMapFromParent.IsEmpty() {
|
|
return syserror.EPERM
|
|
}
|
|
if len(entries) == 0 {
|
|
return syserror.EINVAL
|
|
}
|
|
if !c.HasCapabilityIn(linux.CAP_SETGID, ns) {
|
|
return syserror.EPERM
|
|
}
|
|
if c.UserNamespace != ns && c.UserNamespace != ns.parent {
|
|
return syserror.EPERM
|
|
}
|
|
if !c.HasCapabilityIn(linux.CAP_SETGID, ns.parent) {
|
|
if len(entries) != 1 || ns.parent.MapToKGID(GID(entries[0].FirstParentID)) != c.EffectiveKGID || entries[0].Length != 1 {
|
|
return syserror.EPERM
|
|
}
|
|
// It's correct for this to still be UID.
|
|
if c.EffectiveKUID != ns.owner {
|
|
return syserror.EPERM
|
|
}
|
|
// "In the case of gid_map, use of the setgroups(2) system call must
|
|
// first be denied by writing "deny" to the /proc/[pid]/setgroups file
|
|
// (see below) before writing to gid_map." (This file isn't implemented
|
|
// in the version of Linux we're emulating; see comment in
|
|
// UserNamespace.)
|
|
}
|
|
if err := ns.trySetGIDMap(entries); err != nil {
|
|
ns.gidMapFromParent.RemoveAll()
|
|
ns.gidMapToParent.RemoveAll()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ns *UserNamespace) trySetGIDMap(entries []IDMapEntry) error {
|
|
for _, e := range entries {
|
|
lastID := e.FirstID + e.Length
|
|
if lastID <= e.FirstID {
|
|
return syserror.EINVAL
|
|
}
|
|
lastParentID := e.FirstParentID + e.Length
|
|
if lastParentID <= e.FirstParentID {
|
|
return syserror.EINVAL
|
|
}
|
|
if !ns.parent.allIDsMapped(&ns.parent.gidMapToParent, e.FirstParentID, lastParentID) {
|
|
return syserror.EPERM
|
|
}
|
|
if !ns.gidMapFromParent.Add(idMapRange{e.FirstParentID, lastParentID}, e.FirstID) {
|
|
return syserror.EINVAL
|
|
}
|
|
if !ns.gidMapToParent.Add(idMapRange{e.FirstID, lastID}, e.FirstParentID) {
|
|
return syserror.EINVAL
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UIDMap returns the user ID mappings configured for ns. If no mappings
|
|
// have been configured, UIDMap returns nil.
|
|
func (ns *UserNamespace) UIDMap() []IDMapEntry {
|
|
return ns.getIDMap(&ns.uidMapToParent)
|
|
}
|
|
|
|
// GIDMap returns the group ID mappings configured for ns. If no mappings
|
|
// have been configured, GIDMap returns nil.
|
|
func (ns *UserNamespace) GIDMap() []IDMapEntry {
|
|
return ns.getIDMap(&ns.gidMapToParent)
|
|
}
|
|
|
|
func (ns *UserNamespace) getIDMap(m *idMapSet) []IDMapEntry {
|
|
ns.mu.Lock()
|
|
defer ns.mu.Unlock()
|
|
var entries []IDMapEntry
|
|
for it := m.FirstSegment(); it.Ok(); it = it.NextSegment() {
|
|
entries = append(entries, IDMapEntry{
|
|
FirstID: it.Start(),
|
|
FirstParentID: it.Value(),
|
|
Length: it.Range().Length(),
|
|
})
|
|
}
|
|
return entries
|
|
}
|