sandbox: write uid/gid map as init
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Fortify (push) Successful in 2m30s
Test / Fpkg (push) Successful in 3m21s
Test / Data race detector (push) Successful in 3m39s
Test / Flake checks (push) Successful in 48s

This avoids PR_SET_DUMPABLE in the parent process.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-03-14 17:41:08 +09:00
parent 94895bbacb
commit f41fd94628
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
3 changed files with 41 additions and 10 deletions

View File

@ -58,10 +58,6 @@ type (
InitParams
// Custom [exec.Cmd] initialisation function.
CommandContext func(ctx context.Context) (cmd *exec.Cmd)
// mapped uid in user namespace
Uid int
// mapped gid in user namespace
Gid int
// param encoder for shim and init
setup *gob.Encoder
@ -86,6 +82,10 @@ type (
// Initial process argv.
Args []string
// Mapped Uid in user namespace.
Uid int
// Mapped Gid in user namespace.
Gid int
// Hostname value in UTS namespace.
Hostname string
// Sequential container setup ops.
@ -140,8 +140,6 @@ func (p *Container) Start() error {
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNS,
UidMappings: []syscall.SysProcIDMap{{p.Uid, syscall.Getuid(), 1}},
GidMappings: []syscall.SysProcIDMap{{p.Gid, syscall.Getgid(), 1}},
// remain privileged for setup
AmbientCaps: []uintptr{CAP_SYS_ADMIN},
@ -200,6 +198,8 @@ func (p *Container) Serve() error {
return setup.Encode(
&initParams{
p.InitParams,
syscall.Getuid(),
syscall.Getgid(),
len(p.ExtraFiles),
fmsg.Load(),
},

View File

@ -9,6 +9,7 @@ import (
"os/exec"
"path"
"slices"
"syscall"
"testing"
"time"
@ -65,6 +66,8 @@ func TestContainer(t *testing.T) {
container := sandbox.New(ctx, os.Args[0], "-test.v",
"-test.run=TestHelperCheckContainer", "--", "check", tc.host)
container.Uid = 1000
container.Gid = 100
container.Hostname = tc.host
container.CommandContext = func(ctx context.Context) *exec.Cmd {
return exec.CommandContext(ctx, os.Args[0], "-test.v",
@ -154,6 +157,14 @@ func TestHelperCheckContainer(t *testing.T) {
return
}
t.Run("user", func(t *testing.T) {
if uid := syscall.Getuid(); uid != 1000 {
t.Errorf("Getuid: %d, want 1000", uid)
}
if gid := syscall.Getgid(); gid != 100 {
t.Errorf("Getgid: %d, want 100", gid)
}
})
t.Run("hostname", func(t *testing.T) {
if name, err := os.Hostname(); err != nil {
t.Fatalf("cannot get hostname: %v", err)

View File

@ -33,6 +33,7 @@ const (
type initParams struct {
InitParams
HostUid, HostGid int
// extra files count
Count int
// verbosity pass through
@ -43,10 +44,6 @@ func Init(exit func(code int)) {
runtime.LockOSThread()
fmsg.Prepare("init")
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
}
if os.Getpid() != 1 {
log.Fatal("this process must run as pid 1")
}
@ -80,6 +77,29 @@ func Init(exit func(code int)) {
offsetSetup = int(setupFile.Fd() + 1)
}
// write uid/gid map here so parent does not need to set dumpable
if err := internal.SetDumpable(internal.SUID_DUMP_USER); err != nil {
log.Fatalf("cannot set SUID_DUMP_USER: %s", err)
}
if err := os.WriteFile("/proc/self/uid_map",
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
0); err != nil {
log.Fatalf("%v", err)
}
if err := os.WriteFile("/proc/self/setgroups",
[]byte("deny\n"),
0); err != nil && !os.IsNotExist(err) {
log.Fatalf("%v", err)
}
if err := os.WriteFile("/proc/self/gid_map",
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
0); err != nil {
log.Fatalf("%v", err)
}
if err := internal.SetDumpable(internal.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
}
if params.Hostname != "" {
if err := syscall.Sethostname([]byte(params.Hostname)); err != nil {
log.Fatalf("cannot set hostname: %v", err)