188 lines
4.9 KiB
Go
188 lines
4.9 KiB
Go
package Podman
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
moby_user "github.com/moby/sys/user"
|
|
"kernel.org/pub/linux/libs/security/libcap/cap"
|
|
)
|
|
|
|
const subidUseFile = false
|
|
|
|
func NewIDMap(idMapName string) {
|
|
flag.Parse()
|
|
args := flag.Args()
|
|
var (
|
|
dir *os.File
|
|
err error
|
|
)
|
|
if len(args) < 4 {
|
|
log.Fatalf("%s [<pid>|fd:<pidfd>] <id> <lower id> <count> [ <id> <lower id> <count> ]", filepath.Base(os.Args[0]))
|
|
}
|
|
pid := args[0]
|
|
args = args[1:]
|
|
if len(args)%3 != 0 {
|
|
log.Fatalf("argument count is not a multiple of 3\n%s [<pid>|fd:<pidfd>] <id> <lower id> <count> [ <id> <lower id> <count> ]", filepath.Base(os.Args[0]))
|
|
}
|
|
if len(pid) > 3 && strings.HasPrefix(pid, "fd:") {
|
|
fd, err := strconv.Atoi(pid[3:])
|
|
if err != nil {
|
|
log.Fatalf("Invalid fd given: %s", pid)
|
|
}
|
|
dir = os.NewFile(uintptr(fd), pid[3:])
|
|
} else {
|
|
dir, err = os.Open(filepath.Join("/proc", pid))
|
|
if err != nil {
|
|
log.Fatalf("unable to find process %s", pid)
|
|
}
|
|
}
|
|
stat, err := dir.Stat()
|
|
if err != nil {
|
|
log.Fatalf("unable to find process %s", pid)
|
|
}
|
|
fstat := stat.Sys().(*syscall.Stat_t)
|
|
if os.Geteuid() != int(fstat.Uid) {
|
|
log.Fatal("You aren't allowed to do that :-P")
|
|
}
|
|
if os.Getegid() != int(fstat.Gid) {
|
|
log.Fatal("You aren't allowed to do that :-P")
|
|
}
|
|
var (
|
|
user Passwd
|
|
subid moby_user.SubID
|
|
passwds []Passwd = LoadAllPasswd()
|
|
)
|
|
subidstart := subidStart
|
|
for _, u := range passwds {
|
|
if u.UID == int(fstat.Uid) {
|
|
user = u
|
|
subid = moby_user.SubID{
|
|
Name: user.User,
|
|
SubID: subidstart,
|
|
Count: subidCount,
|
|
}
|
|
}
|
|
subidstart += subidCount
|
|
}
|
|
if user.User == "" {
|
|
log.Fatalf("Unable to find user")
|
|
}
|
|
if subidUseFile {
|
|
if idMapName == "uid_map" {
|
|
subid, err = getSubIDs("/etc/subuid", user.User, strconv.Itoa(user.UID))
|
|
} else {
|
|
subid, err = getSubIDs("/etc/subgid", user.User, strconv.Itoa(user.GID))
|
|
}
|
|
}
|
|
if err != nil {
|
|
log.Fatalf("Unable to get subid map for the user: %v", err)
|
|
}
|
|
idmaps, err := getIDMaps(int64(user.UID), subid, args)
|
|
if err != nil {
|
|
log.Fatalf("Unable to approve requested subid mappings: %v", err)
|
|
}
|
|
|
|
idMapFd, err := syscall.Openat(int(dir.Fd()), idMapName, os.O_WRONLY, 0o600)
|
|
if err != nil {
|
|
log.Fatalf("Unable to open process %s: %v", idMapName, err)
|
|
}
|
|
idMap := os.NewFile(uintptr(idMapFd), idMapName)
|
|
|
|
defer idMap.Close()
|
|
buf := &bytes.Buffer{}
|
|
for _, m := range idmaps {
|
|
fmt.Fprintf(buf, "%d %d %d\n", m.ID, m.ParentID, m.Count)
|
|
}
|
|
caps := cap.GetProc()
|
|
neededCap := cap.SETUID
|
|
if idMapName == "gid_map" {
|
|
neededCap = cap.SETGID
|
|
}
|
|
hasCap, _ := caps.GetFlag(cap.Effective, neededCap)
|
|
if !hasCap {
|
|
log.Printf("Wrong capability for setting %s: have %v need %v", idMapName, caps, neededCap)
|
|
}
|
|
|
|
// Only one write syscall is allowed
|
|
_, err = idMap.Write(buf.Bytes())
|
|
if err != nil {
|
|
log.Fatalf("Failed to write %s(%s) to process: %v", idMapName, buf.Bytes(), err)
|
|
}
|
|
}
|
|
|
|
func WriteSubids(file string) error {
|
|
buf := &bytes.Buffer{}
|
|
passwds := LoadAllPasswd()
|
|
subidstart := subidStart
|
|
for _, passwd := range passwds {
|
|
fmt.Fprintf(buf, "%s:%d:%d\n", passwd.User, subidstart, subidCount)
|
|
subidstart += subidCount
|
|
}
|
|
err := os.WriteFile(file, buf.Bytes(), 0o644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getIDMaps(id int64, subid moby_user.SubID, args []string) ([]moby_user.IDMap, error) {
|
|
var err error
|
|
idmap := moby_user.IDMap{}
|
|
idmaps := []moby_user.IDMap{}
|
|
for i, arg := range args {
|
|
switch i % 3 {
|
|
case 0:
|
|
idmap = moby_user.IDMap{}
|
|
idmap.ID, err = strconv.ParseInt(arg, 10, 64)
|
|
if err != nil {
|
|
return idmaps, fmt.Errorf("failed to parse uid as number: %v: %v", arg, err)
|
|
}
|
|
case 1:
|
|
idmap.ParentID, err = strconv.ParseInt(arg, 10, 64)
|
|
if err != nil {
|
|
return idmaps, fmt.Errorf("failed to parse parent uid as number: %v: %v", arg, err)
|
|
}
|
|
if idmap.ParentID != id && (idmap.ParentID < subid.SubID || idmap.ParentID >= subid.SubID+subid.Count) {
|
|
return idmaps, fmt.Errorf("parent ID: '%v' not in approved range: %v-%v", idmap.ParentID, subid.SubID, subid.SubID+subid.Count)
|
|
}
|
|
case 2:
|
|
idmap.Count, err = strconv.ParseInt(arg, 10, 64)
|
|
if err != nil {
|
|
return idmaps, fmt.Errorf("failed to parse count as number: %v: %v", arg, err)
|
|
}
|
|
end := idmap.ParentID + idmap.Count - 1
|
|
if end != id && (end < subid.SubID || end >= subid.SubID+subid.Count) {
|
|
return idmaps, fmt.Errorf("parent ID: '%v' not in approved range: %v-%v", end, subid.SubID, subid.SubID+subid.Count)
|
|
}
|
|
idmaps = append(idmaps, idmap)
|
|
}
|
|
}
|
|
return idmaps, nil
|
|
}
|
|
|
|
func getSubIDs(filename string, name string, id string) (moby_user.SubID, error) {
|
|
var subid moby_user.SubID
|
|
subids, err := moby_user.ParseSubIDFile(filename)
|
|
if err != nil {
|
|
log.Fatalf("Failed to read subuid file: %v", err)
|
|
}
|
|
for _, sub := range subids {
|
|
if sub.Name == name || sub.Name == id {
|
|
subid = sub
|
|
break
|
|
}
|
|
}
|
|
if subid.Name == "" {
|
|
return subid, fmt.Errorf("no entry in %q for %s", filename, name)
|
|
}
|
|
return subid, nil
|
|
}
|