Compare commits
3 Commits
ec5f7bb75a
...
master
Author | SHA1 | Date | |
---|---|---|---|
f97d36d6ed | |||
51e4774bb2 | |||
8da229bf39 |
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
||||
module gitea.narnian.us/lordwelch/Podman
|
||||
|
||||
go 1.25.2
|
||||
|
||||
require kernel.org/pub/linux/libs/security/libcap/cap v1.2.76
|
||||
|
||||
require kernel.org/pub/linux/libs/security/libcap/psx v1.2.76 // indirect
|
146
main.go
Normal file
146
main.go
Normal file
@ -0,0 +1,146 @@
|
||||
package Podman
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func Run(args ...string) error {
|
||||
podman := exec.Command("/user/podman", args...)
|
||||
podman.Env = append(os.Environ(), "TMPDIR=/tmp")
|
||||
podman.Stdin = os.Stdin
|
||||
podman.Stdout = os.Stdout
|
||||
podman.Stderr = os.Stderr
|
||||
|
||||
exit := make(chan os.Signal, 1)
|
||||
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
podman.Process.Signal(<-exit)
|
||||
}()
|
||||
if err := podman.Run(); err != nil {
|
||||
return fmt.Errorf("%v: %v", podman.Args, err)
|
||||
}
|
||||
exit <- os.Interrupt
|
||||
return nil
|
||||
}
|
||||
|
||||
var numericRe = regexp.MustCompile(`^[0-9]+$`)
|
||||
|
||||
func Signal(name string, sig os.Signal) error {
|
||||
fis, err := os.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
if !fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
if !numericRe.MatchString(fi.Name()) {
|
||||
continue
|
||||
}
|
||||
b, err := os.ReadFile(filepath.Join("/proc", fi.Name(), "cmdline"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue // process vanished
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(b), name) {
|
||||
continue
|
||||
}
|
||||
pid, _ := strconv.Atoi(fi.Name()) // already verified to be numeric
|
||||
p, _ := os.FindProcess(pid)
|
||||
return p.Signal(sig)
|
||||
}
|
||||
return fmt.Errorf("Process not found: %s", name)
|
||||
}
|
||||
|
||||
func IsMounted(mountpoint string) (bool, error) {
|
||||
b, err := os.ReadFile("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil // platform does not have /proc/self/mountinfo, fall back to not verifying
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(b)), "\n") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 5 {
|
||||
continue
|
||||
}
|
||||
if filepath.Clean(parts[4]) == filepath.Clean(mountpoint) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func MakeWritable(dir string) error {
|
||||
mounted, err := IsMounted(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mounted {
|
||||
// Nothing to do, directory is already mounted.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read all regular files in this directory.
|
||||
regularFiles := make(map[string]string)
|
||||
fis, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
b, err := os.ReadFile(filepath.Join(dir, fi.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regularFiles[fi.Name()] = string(b)
|
||||
}
|
||||
|
||||
if err := MountTmpfs(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write all regular files from memory back to new tmpfs.
|
||||
for name, contents := range regularFiles {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MountTmpfs(dest string) error {
|
||||
if mounted, err := IsMounted(dest); err == nil && mounted {
|
||||
return nil
|
||||
}
|
||||
if err := syscall.Mount("tmpfs", dest, "tmpfs", syscall.MS_NOSUID|syscall.MS_RELATIME, "size=5G"); err != nil {
|
||||
return fmt.Errorf("mounting tmpfs to %s: %v", dest, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Bind(source, dest string) error {
|
||||
if mounted, err := IsMounted(dest); err == nil && mounted {
|
||||
return nil
|
||||
}
|
||||
os.MkdirAll(source, 0o777)
|
||||
os.MkdirAll(dest, 0o777)
|
||||
|
||||
if err := syscall.Mount(source, dest, "", syscall.MS_BIND, ""); err != nil {
|
||||
return fmt.Errorf("error mounting %s to %s: %v", source, dest, err)
|
||||
}
|
||||
return nil
|
||||
}
|
142
privDrop.go
Normal file
142
privDrop.go
Normal file
@ -0,0 +1,142 @@
|
||||
package Podman
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"kernel.org/pub/linux/libs/security/libcap/cap"
|
||||
)
|
||||
|
||||
func setFromCaps(caps ...string) (*cap.Set, error) {
|
||||
var err error
|
||||
newSet := cap.NewSet()
|
||||
valueList := make([]cap.Value, 0, len(caps))
|
||||
for _, capName := range caps {
|
||||
value, err := cap.FromName(strings.ToLower(capName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueList = append(valueList, value)
|
||||
}
|
||||
|
||||
err = newSet.SetFlag(cap.Effective, true, valueList...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = newSet.Fill(cap.Permitted, cap.Effective)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSet, err
|
||||
}
|
||||
|
||||
func ChownRecursively(root string, owner int, group int) {
|
||||
err := filepath.Walk(root,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if int(info.Sys().(*syscall.Stat_t).Uid) != owner || int(info.Sys().(*syscall.Stat_t).Gid) != group {
|
||||
err = os.Chown(path, owner, group)
|
||||
if err == nil {
|
||||
log.Printf("File ownership of %s changed.\n", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Chown failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Passwd struct {
|
||||
User string
|
||||
Password string
|
||||
UID int
|
||||
GID int
|
||||
Gecos string
|
||||
Home string
|
||||
Shell string
|
||||
Gids []string
|
||||
}
|
||||
|
||||
// MustDropPrivileges executes the program in a child process, dropping root
|
||||
// privileges, but retaining the given capabilities.
|
||||
func MustDropPrivileges(caps ...string) {
|
||||
// Read and display the capabilities of the running process
|
||||
passwd := LoadPasswd(os.Getenv("HOME"))
|
||||
err := os.Chown(passwd.Home, passwd.UID, passwd.GID)
|
||||
if err != nil {
|
||||
log.Print("Failed to chown home directory", os.Getenv("HOME"))
|
||||
}
|
||||
c := cap.GetProc()
|
||||
if os.Getenv("PRIVILEGES_DROPPED") == "1" {
|
||||
return
|
||||
}
|
||||
capSet, err := setFromCaps(caps...)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to parse capabilities: %s", err)
|
||||
}
|
||||
diff, _ := capSet.Cf(c)
|
||||
if passwd.GID == os.Getgid() && passwd.UID == os.Getuid() && diff == 0 {
|
||||
return
|
||||
}
|
||||
os.Setenv("PRIVILEGES_DROPPED", "1")
|
||||
|
||||
if passwd.GID != 0 {
|
||||
err = cap.SetGroups(passwd.GID)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to change gid to %d: %s", passwd.GID, err)
|
||||
}
|
||||
}
|
||||
if passwd.GID != 0 {
|
||||
err = cap.SetUID(passwd.UID)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to change uid to %d: %s", passwd.UID, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = capSet.SetProc()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to drop privileges: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func LoadPasswd(home string) Passwd {
|
||||
var (
|
||||
passwd Passwd
|
||||
)
|
||||
passwd.User = filepath.Base(os.Getenv("HOME"))
|
||||
b, err := os.ReadFile("/etc/passwd")
|
||||
if err != nil {
|
||||
return passwd
|
||||
}
|
||||
for _, line := range strings.SplitN(string(b), "\n", -1) {
|
||||
fields := strings.SplitN(line, ":", 7)
|
||||
if len(fields) != 7 {
|
||||
continue
|
||||
}
|
||||
if passwd.User != string(fields[0]) {
|
||||
continue
|
||||
}
|
||||
passwd.User = fields[0]
|
||||
passwd.Password = fields[1]
|
||||
passwd.UID, err = strconv.Atoi(fields[2])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
passwd.GID, err = strconv.Atoi(fields[3])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
passwd.Gecos = fields[4]
|
||||
passwd.Home = fields[5]
|
||||
passwd.Shell = fields[6]
|
||||
}
|
||||
return passwd
|
||||
}
|
Reference in New Issue
Block a user