repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/runner/mounts.go
Rawpackage runner
import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
"bugabinga.net/luci/internal/ciconfig"
"bugabinga.net/luci/internal/podman"
)
func durableMounts(ctx context.Context, dataDir, repo, job string, declarations []ciconfig.Mount) ([]podman.Mount, func(), error) {
ordered := append([]ciconfig.Mount(nil), declarations...)
sort.Slice(ordered, func(i, j int) bool { return ordered[i].Type+"/"+ordered[i].Name < ordered[j].Type+"/"+ordered[j].Name })
var files []*os.File
cleanup := func() {
for i := len(files) - 1; i >= 0; i-- {
_ = syscall.Flock(int(files[i].Fd()), syscall.LOCK_UN)
_ = files[i].Close()
}
}
mounts := make([]podman.Mount, 0, len(ordered))
for _, declaration := range ordered {
source, err := durableMountPath(dataDir, declaration.Type, repo, job, declaration.Name)
if err != nil {
cleanup()
return nil, func() {}, err
}
if declaration.Type == "cache" {
if err := privateMkdirAll(dataDir, source); err != nil {
cleanup()
return nil, func() {}, err
}
}
if err := trustedPath(dataDir, source); err != nil {
cleanup()
return nil, func() {}, fmt.Errorf("%s mount %q unavailable: %w", declaration.Type, declaration.Name, err)
}
if declaration.Type == "auth" {
info, err := os.Lstat(source)
if err != nil || !info.IsDir() || info.Mode().Perm()&0o077 != 0 {
cleanup()
return nil, func() {}, fmt.Errorf("auth mount %q is not owner-only", declaration.Name)
}
}
lockPath := filepath.Join(dataDir, "mount-locks", declaration.Type, repo, job, declaration.Name)
if err := privateMkdirAll(dataDir, filepath.Dir(lockPath)); err != nil {
cleanup()
return nil, func() {}, err
}
file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR|syscall.O_NOFOLLOW, 0o600)
if err != nil {
cleanup()
return nil, func() {}, err
}
info, err := file.Stat()
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0o077 != 0 {
_ = file.Close()
cleanup()
return nil, func() {}, fmt.Errorf("unsafe mount lock %q", declaration.Name)
}
if err := lockContext(ctx, file); err != nil {
_ = file.Close()
cleanup()
return nil, func() {}, err
}
files = append(files, file)
mounts = append(mounts, podman.Mount{Source: source, Target: declaration.Target})
}
return mounts, cleanup, nil
}
func durableMountPath(dataDir, kind, repo, job, name string) (string, error) {
for label, value := range map[string]string{"type": kind, "repo": repo, "job": job, "mount": name} {
if value == "" || value != filepath.Base(value) || value == "." || value == ".." || strings.Contains(value, string(filepath.Separator)) {
return "", fmt.Errorf("unsafe %s segment %q", label, value)
}
}
return filepath.Join(dataDir, "mounts", kind, repo, job, name), nil
}
func privateMkdirAll(root, target string) error {
relative, err := filepath.Rel(root, target)
if err != nil || relative == "." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return fmt.Errorf("outside data directory")
}
info, err := os.Lstat(root)
if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("unsafe data directory")
}
fd, err := os.OpenRoot(root)
if err != nil {
return err
}
defer fd.Close()
current := ""
for _, part := range strings.Split(relative, string(filepath.Separator)) {
if current == "" {
current = part
} else {
current = filepath.Join(current, part)
}
info, err := fd.Lstat(current)
if os.IsNotExist(err) {
if err := fd.Mkdir(current, 0o700); err != nil && !os.IsExist(err) {
return err
}
info, err = fd.Lstat(current)
}
if err != nil {
return err
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 || info.Mode().Perm()&0o077 != 0 {
return fmt.Errorf("not a private directory")
}
}
return nil
}
func trustedPath(root, path string) error {
relative, err := filepath.Rel(root, path)
if err != nil || relative == "." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return fmt.Errorf("outside data directory")
}
current := root
for _, part := range strings.Split(relative, string(filepath.Separator)) {
current = filepath.Join(current, part)
info, err := os.Lstat(current)
if err != nil {
return err
}
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("not a private directory")
}
if info.Mode().Perm()&0o022 != 0 {
return fmt.Errorf("directory is writable by group or other")
}
}
return nil
}
func lockContext(ctx context.Context, file *os.File) error {
for {
err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err == nil {
return nil
}
if err != syscall.EWOULDBLOCK && err != syscall.EAGAIN {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(10 * time.Millisecond):
}
}
}