From 4f27755eba2ed0c794226eee5a1058e272ed6992 Mon Sep 17 00:00:00 2001 From: David Trudgian Date: Mon, 21 Nov 2022 09:46:10 +0000 Subject: [PATCH] fix: correct uid/gid non-root mapping Ensure e2e tests for oci actions use user profile. Set uid/gid mappings explicitly. We need to do this anyone, going forward, but here it works around: https://github.com/containers/crun/issues/1072 Signed-off-by: Edita Kizinevic --- cmd/internal/cli/build_linux.go | 11 ++- e2e/actions/oci.go | 2 +- internal/app/apptainer/overlay_create.go | 9 +- internal/pkg/build/stage.go | 2 +- .../pkg/{fakeroot => fakefake}/fakefake.go | 0 .../runtime/engine/apptainer/prepare_linux.go | 3 +- .../runtime/engine/apptainer/process_linux.go | 2 +- .../runtime/launcher/native/launcher_linux.go | 17 ++-- pkg/ocibundle/native/bundle_linux.go | 93 ++++++++++++++++++- 9 files changed, 116 insertions(+), 23 deletions(-) rename internal/pkg/{fakeroot => fakefake}/fakefake.go (100%) diff --git a/cmd/internal/cli/build_linux.go b/cmd/internal/cli/build_linux.go index 471a8d89f8..fe15c25cb1 100644 --- a/cmd/internal/cli/build_linux.go +++ b/cmd/internal/cli/build_linux.go @@ -22,7 +22,8 @@ import ( "github.com/apptainer/apptainer/internal/pkg/build" "github.com/apptainer/apptainer/internal/pkg/buildcfg" "github.com/apptainer/apptainer/internal/pkg/cache" - "github.com/apptainer/apptainer/internal/pkg/fakeroot" + fakefake "github.com/apptainer/apptainer/internal/pkg/fakefake" + fakerootutil "github.com/apptainer/apptainer/internal/pkg/fakeroot" "github.com/apptainer/apptainer/internal/pkg/remote/endpoint" fakerootConfig "github.com/apptainer/apptainer/internal/pkg/runtime/engine/fakeroot/config" "github.com/apptainer/apptainer/internal/pkg/util/env" @@ -83,13 +84,13 @@ func fakerootExec(isDeffile bool) { // https://github.com/containers/image/blob/master/internal/rootless/rootless.go os.Setenv("_CONTAINERS_ROOTLESS_UID", strconv.FormatUint(uint64(uid), 10)) - if uid != 0 && (!fakeroot.IsUIDMapped(uid) || buildArgs.ignoreSubuid) { - sylog.Infof("User not listed in %v, trying root-mapped namespace", fakeroot.SubUIDFile) + if uid != 0 && (!fakerootutil.IsUIDMapped(uid) || buildArgs.ignoreSubuid) { + sylog.Infof("User not listed in %v, trying root-mapped namespace", fakerootutil.SubUIDFile) os.Setenv("_APPTAINER_FAKEFAKEROOT", "1") if buildArgs.ignoreUserns { err = errors.New("could not start root-mapped namesapce because of --ignore-userns is set") } else { - err = fakeroot.UnshareRootMapped(args) + err = fakefake.UnshareRootMapped(args) } if err == nil { // All the work has been done by the child process @@ -149,7 +150,7 @@ func runBuild(cmd *cobra.Command, args []string) { if buildArgs.ignoreFakerootCmd { err = errors.New("fakeroot command is ignored because of --ignore-fakeroot-command") } else { - fakerootPath, err = fakeroot.FindFake() + fakerootPath, err = fakefake.FindFake() } if err != nil { sylog.Infof("fakeroot command not found") diff --git a/e2e/actions/oci.go b/e2e/actions/oci.go index 67d51e38f3..bd811021b4 100644 --- a/e2e/actions/oci.go +++ b/e2e/actions/oci.go @@ -84,7 +84,7 @@ func (c actionTests) actionOciRun(t *testing.T) { c.env.RunApptainer( t, e2e.AsSubtest(tt.name), - e2e.WithProfile(e2e.OCIRootProfile), + e2e.WithProfile(e2e.OCIUserProfile), e2e.WithCommand("run"), // While we don't support args we are entering a /bin/sh interactively. e2e.ConsoleRun(e2e.ConsoleSendLine("exit")), diff --git a/internal/app/apptainer/overlay_create.go b/internal/app/apptainer/overlay_create.go index 23a03f0dde..8789fc88eb 100644 --- a/internal/app/apptainer/overlay_create.go +++ b/internal/app/apptainer/overlay_create.go @@ -18,7 +18,8 @@ import ( "runtime" "strings" - "github.com/apptainer/apptainer/internal/pkg/fakeroot" + fakefake "github.com/apptainer/apptainer/internal/pkg/fakefake" + fakerootutil "github.com/apptainer/apptainer/internal/pkg/fakeroot" "github.com/apptainer/apptainer/internal/pkg/util/bin" "github.com/apptainer/apptainer/pkg/image" "github.com/apptainer/apptainer/pkg/sylog" @@ -222,7 +223,7 @@ func OverlayCreate(size int, imgPath string, overlaySparse bool, isFakeroot bool defer unix.Umask(oldumask) if uid != 0 { - if !fakeroot.IsUIDMapped(uint32(uid)) { + if !fakerootutil.IsUIDMapped(uint32(uid)) { // Using --fakeroot here for use with --fakeroot // overlay is only necessary when only root-mapped // user namespaces are in use: real root of course @@ -232,7 +233,7 @@ func OverlayCreate(size int, imgPath string, overlaySparse bool, isFakeroot bool // the fakeroot command (in suid flow with no user // namespaces), using the --fakeroot option here // prevents overlay from working, most unfortunately. - err = fakeroot.UnshareRootMapped([]string{"/bin/true"}) + err = fakefake.UnshareRootMapped([]string{"/bin/true"}) if err != nil { sylog.Debugf("UnshareRootMapped failed: %v", err) if isFakeroot { @@ -248,7 +249,7 @@ func OverlayCreate(size int, imgPath string, overlaySparse bool, isFakeroot bool if isFakeroot { sylog.Debugf("Trying root-mapped namespace") - err = fakeroot.UnshareRootMapped(os.Args) + err = fakefake.UnshareRootMapped(os.Args) if err == nil { // everything was done by the child os.Exit(0) diff --git a/internal/pkg/build/stage.go b/internal/pkg/build/stage.go index ed041b64b0..d536553a20 100644 --- a/internal/pkg/build/stage.go +++ b/internal/pkg/build/stage.go @@ -19,7 +19,7 @@ import ( "github.com/apptainer/apptainer/internal/pkg/build/files" "github.com/apptainer/apptainer/internal/pkg/buildcfg" - "github.com/apptainer/apptainer/internal/pkg/fakeroot" + "github.com/apptainer/apptainer/internal/pkg/fakefake" "github.com/apptainer/apptainer/pkg/build/types" "github.com/apptainer/apptainer/pkg/sylog" ) diff --git a/internal/pkg/fakeroot/fakefake.go b/internal/pkg/fakefake/fakefake.go similarity index 100% rename from internal/pkg/fakeroot/fakefake.go rename to internal/pkg/fakefake/fakefake.go diff --git a/internal/pkg/runtime/engine/apptainer/prepare_linux.go b/internal/pkg/runtime/engine/apptainer/prepare_linux.go index 6412f7c27a..3f9d310a2c 100644 --- a/internal/pkg/runtime/engine/apptainer/prepare_linux.go +++ b/internal/pkg/runtime/engine/apptainer/prepare_linux.go @@ -24,6 +24,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/apptainer/apptainer/internal/pkg/buildcfg" "github.com/apptainer/apptainer/internal/pkg/cgroups" + fakefake "github.com/apptainer/apptainer/internal/pkg/fakefake" fakerootutil "github.com/apptainer/apptainer/internal/pkg/fakeroot" "github.com/apptainer/apptainer/internal/pkg/image/driver" "github.com/apptainer/apptainer/internal/pkg/instance" @@ -97,7 +98,7 @@ func (e *EngineOperations) PrepareConfig(starterConfig *starter.Config) error { if fakerootPath := e.EngineConfig.GetFakerootPath(); fakerootPath != "" { // look for fakeroot again because the PATH used is // more restricted at this point than it was earlier - newPath, err := fakerootutil.FindFake() + newPath, err := fakefake.FindFake() if err != nil { return fmt.Errorf("error finding fakeroot in privileged PATH: %v", err) } diff --git a/internal/pkg/runtime/engine/apptainer/process_linux.go b/internal/pkg/runtime/engine/apptainer/process_linux.go index f0dce42fa9..b96456d1b8 100644 --- a/internal/pkg/runtime/engine/apptainer/process_linux.go +++ b/internal/pkg/runtime/engine/apptainer/process_linux.go @@ -32,7 +32,7 @@ import ( "unsafe" "github.com/apptainer/apptainer/internal/pkg/checkpoint/dmtcp" - "github.com/apptainer/apptainer/internal/pkg/fakeroot" + "github.com/apptainer/apptainer/internal/pkg/fakefake" "github.com/apptainer/apptainer/internal/pkg/instance" "github.com/apptainer/apptainer/internal/pkg/plugin" "github.com/apptainer/apptainer/internal/pkg/security" diff --git a/internal/pkg/runtime/launcher/native/launcher_linux.go b/internal/pkg/runtime/launcher/native/launcher_linux.go index 809017a306..427a5e23a4 100644 --- a/internal/pkg/runtime/launcher/native/launcher_linux.go +++ b/internal/pkg/runtime/launcher/native/launcher_linux.go @@ -26,7 +26,8 @@ import ( "github.com/apptainer/apptainer/internal/pkg/buildcfg" "github.com/apptainer/apptainer/internal/pkg/cgroups" "github.com/apptainer/apptainer/internal/pkg/checkpoint/dmtcp" - "github.com/apptainer/apptainer/internal/pkg/fakeroot" + fakefake "github.com/apptainer/apptainer/internal/pkg/fakefake" + fakerootutil "github.com/apptainer/apptainer/internal/pkg/fakeroot" "github.com/apptainer/apptainer/internal/pkg/image/driver" "github.com/apptainer/apptainer/internal/pkg/image/unpacker" "github.com/apptainer/apptainer/internal/pkg/instance" @@ -117,21 +118,21 @@ func (l *Launcher) Exec(ctx context.Context, image string, process string, args if l.cfg.IgnoreFakerootCmd { err = errors.New("fakeroot command is ignored because of --ignore-fakeroot-command") } else { - fakerootPath, err = fakeroot.FindFake() + fakerootPath, err = fakefake.FindFake() } if err != nil { sylog.Infof("fakeroot command not found, using only root-mapped namespace") } else { sylog.Infof("Using fakeroot command combined with root-mapped namespace") } - } else if (l.uid != 0) && (!fakeroot.IsUIDMapped(l.uid) || l.cfg.IgnoreSubuid) { - sylog.Infof("User not listed in %v, trying root-mapped namespace", fakeroot.SubUIDFile) + } else if (l.uid != 0) && (!fakerootutil.IsUIDMapped(l.uid) || l.cfg.IgnoreSubuid) { + sylog.Infof("User not listed in %v, trying root-mapped namespace", fakerootutil.SubUIDFile) l.cfg.Fakeroot = false var err error if l.cfg.IgnoreUserns { err = errors.New("could not start root-mapped namespace because of --ignore-userns is set") } else { - err = fakeroot.UnshareRootMapped(os.Args) + err = fakefake.UnshareRootMapped(os.Args) } if err == nil { // All good @@ -141,10 +142,10 @@ func (l *Launcher) Exec(ctx context.Context, image string, process string, args if l.cfg.IgnoreFakerootCmd { err = errors.New("fakeroot command is ignored because of --ignore-fakeroot-command") } else { - fakerootPath, err = fakeroot.FindFake() + fakerootPath, err = fakefake.FindFake() } if err != nil { - sylog.Fatalf("--fakeroot requires either being in %v, unprivileged user namespaces, or the fakeroot command", fakeroot.SubUIDFile) + sylog.Fatalf("--fakeroot requires either being in %v, unprivileged user namespaces, or the fakeroot command", fakerootutil.SubUIDFile) } notSandbox := false if strings.Contains(image, "://") { @@ -627,7 +628,7 @@ func (l *Launcher) setBinds(fakerootPath string) error { if fakerootPath != "" { l.engineConfig.SetFakerootPath(fakerootPath) // Add binds for fakeroot command - fakebindPaths, err := fakeroot.GetFakeBinds(fakerootPath) + fakebindPaths, err := fakefake.GetFakeBinds(fakerootPath) if err != nil { return fmt.Errorf("while getting fakeroot bindpoints: %w", err) } diff --git a/pkg/ocibundle/native/bundle_linux.go b/pkg/ocibundle/native/bundle_linux.go index 72d3a5bddc..52ec40358b 100644 --- a/pkg/ocibundle/native/bundle_linux.go +++ b/pkg/ocibundle/native/bundle_linux.go @@ -20,6 +20,7 @@ import ( apexlog "github.com/apex/log" "github.com/apptainer/apptainer/internal/pkg/build/oci" "github.com/apptainer/apptainer/internal/pkg/cache" + "github.com/apptainer/apptainer/internal/pkg/fakeroot" "github.com/apptainer/apptainer/internal/pkg/runtime/engine/config/oci/generate" "github.com/apptainer/apptainer/pkg/ocibundle" "github.com/apptainer/apptainer/pkg/ocibundle/tools" @@ -160,7 +161,9 @@ func (b *Bundle) Create(ctx context.Context, ociConfig *specs.Spec) error { b.setProcessArgs(g) // TODO - Handle custom env and user b.setProcessEnv(g) - b.setProcessUser(g) + if err := b.setProcessUser(g); err != nil { + return err + } return b.writeConfig(g) } @@ -170,14 +173,100 @@ func (b *Bundle) Path() string { return b.bundlePath } -func (b *Bundle) setProcessUser(g *generate.Generator) { +func (b *Bundle) setProcessUser(g *generate.Generator) error { // Set non-root uid/gid per Apptainer defaults uid := uint32(os.Getuid()) if uid != 0 { gid := uint32(os.Getgid()) g.Config.Process.User.UID = uid g.Config.Process.User.GID = gid + // Get user's configured subuid & subgid ranges + subuidRange, err := fakeroot.GetIDRange(fakeroot.SubUIDFile, uid) + if err != nil { + return err + } + // We must be able to map at least 0->65535 inside the container + if subuidRange.Size < 65536 { + return fmt.Errorf("subuid range size (%d) must be at least 65536", subuidRange.Size) + } + subgidRange, err := fakeroot.GetIDRange(fakeroot.SubGIDFile, uid) + if err != nil { + return err + } + if subgidRange.Size <= gid { + return fmt.Errorf("subuid range size (%d) must be at least 65536", subgidRange.Size) + } + + // Preserve own uid container->host, map everything else to subuid range. + if uid < 65536 { + g.Config.Linux.UIDMappings = []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: subuidRange.HostID, + Size: uid, + }, + { + ContainerID: uid, + HostID: uid, + Size: 1, + }, + { + ContainerID: uid + 1, + HostID: subuidRange.HostID + uid, + Size: subuidRange.Size - uid, + }, + } + } else { + g.Config.Linux.UIDMappings = []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: subuidRange.HostID, + Size: 65536, + }, + { + ContainerID: uid, + HostID: uid, + Size: 1, + }, + } + } + + // Preserve own gid container->host, map everything else to subgid range. + if gid < 65536 { + g.Config.Linux.GIDMappings = []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: subgidRange.HostID, + Size: gid, + }, + { + ContainerID: gid, + HostID: gid, + Size: 1, + }, + { + ContainerID: gid + 1, + HostID: subgidRange.HostID + gid, + Size: subgidRange.Size - gid, + }, + } + } else { + g.Config.Linux.GIDMappings = []specs.LinuxIDMapping{ + { + ContainerID: 0, + HostID: subgidRange.HostID, + Size: 65536, + }, + { + ContainerID: gid, + HostID: gid, + Size: 1, + }, + } + } + g.Config.Linux.Namespaces = append(g.Config.Linux.Namespaces, specs.LinuxNamespace{Type: "user"}) } + return nil } func (b *Bundle) setProcessEnv(g *generate.Generator) {