From 65ed96585d7057a32ac0afaec9834d89aa6af994 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Tue, 25 Jun 2024 14:41:30 +0200 Subject: [PATCH] podman top: join the container userns When we execute ps(1) in the container and the container uses a userns with a different id mapping the user id field will be wrong. To fix this we must join the userns in such case. Fixes #22293 Signed-off-by: Paul Holzinger --- libpod/container_top_linux.c | 25 +++++++++++++++++++++++++ libpod/container_top_linux.go | 25 +++++++++++++++++++------ test/e2e/top_test.go | 12 ++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/libpod/container_top_linux.c b/libpod/container_top_linux.c index a7192c54c2..636a82699f 100644 --- a/libpod/container_top_linux.c +++ b/libpod/container_top_linux.c @@ -3,6 +3,7 @@ #define _GNU_SOURCE #include +#include #include #include #include @@ -11,6 +12,7 @@ /* keep special_exit_code in sync with container_top_linux.go */ int special_exit_code = 255; +int join_userns = 0; char **argv = NULL; void @@ -33,6 +35,12 @@ set_argv (int pos, char *arg) argv[pos] = arg; } +void +set_userns () +{ + join_userns = 1; +} + /* We use cgo code here so we can fork then exec separately, this is done so we can mount proc after the fork because the pid namespace is @@ -64,6 +72,23 @@ fork_exec_ps () fprintf (stderr, "mount proc: %m"); exit (special_exit_code); } + if (join_userns) + { + // join the userns to make sure uid mapping match + // we are already part of the pidns so so pid 1 is the main container process + r = open ("/proc/1/ns/user", O_CLOEXEC | O_RDONLY); + if (r < 0) + { + fprintf (stderr, "open /proc/1/ns/user: %m"); + exit (special_exit_code); + } + if ((status = setns (r, CLONE_NEWUSER)) < 0) + { + fprintf (stderr, "setns NEWUSER: %m"); + exit (special_exit_code); + } + } + /* use execve to unset all env vars, we do not want to leak anything into the container */ execve (argv[0], argv, NULL); fprintf (stderr, "execve: %m"); diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 7dff973186..0b85a5a705 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -31,6 +31,7 @@ import ( void fork_exec_ps(); void create_argv(int len); void set_argv(int pos, char *arg); +void set_userns(); */ import "C" @@ -56,13 +57,13 @@ func podmanTopMain() { os.Exit(0) } -// podmanTopInner os.Args = {command name} {pid} {psPath} [args...] +// podmanTopInner os.Args = {command name} {pid} {userns(1/0)} {psPath} [args...] // We are rexxec'd in a new mountns, then we need to set some security settings in order // to safely execute ps in the container pid namespace. Most notably make sure podman and // ps are read only to prevent a process from overwriting it. func podmanTopInner() error { - if len(os.Args) < 3 { - return fmt.Errorf("internal error, need at least two arguments") + if len(os.Args) < 4 { + return fmt.Errorf("internal error, need at least three arguments") } // We have to lock the thread as we a) switch namespace below and b) use PR_SET_PDEATHSIG @@ -84,7 +85,7 @@ func podmanTopInner() error { return fmt.Errorf("make / mount private: %w", err) } - psPath := os.Args[2] + psPath := os.Args[3] // try to mount everything read only if err := unix.MountSetattr(0, "/", unix.AT_RECURSIVE, &unix.MountAttr{ @@ -122,8 +123,13 @@ func podmanTopInner() error { } pidFD.Close() + userns := os.Args[2] + if userns == "1" { + C.set_userns() + } + args := []string{psPath} - args = append(args, os.Args[3:]...) + args = append(args, os.Args[4:]...) C.create_argv(C.int(len(args))) for i, arg := range args { @@ -317,7 +323,14 @@ func (c *Container) execPS(psArgs []string) ([]string, bool, error) { wPipe.Close() return nil, true, err } - args := append([]string{podmanTopCommand, strconv.Itoa(c.state.PID), psPath}, psArgs...) + + // see podmanTopInner() + userns := "0" + if len(c.config.IDMappings.UIDMap) > 0 { + userns = "1" + } + + args := append([]string{podmanTopCommand, strconv.Itoa(c.state.PID), userns, psPath}, psArgs...) cmd := reexec.Command(args...) cmd.SysProcAttr = &syscall.SysProcAttr{ diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index d67c607735..262b62111a 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -119,6 +119,18 @@ var _ = Describe("Podman top", func() { exec := podmanTest.Podman([]string{"top", session.OutputToString(), "aux"}) exec.WaitWithDefaultTimeout() Expect(exec).Should(ExitWithError(125, "OCI runtime attempted to invoke a command that was not found")) + + session = podmanTest.Podman([]string{"run", "-d", "--uidmap=0:1000:1000", "--user", "9", fedoraMinimal, "sleep", "inf"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + + result = podmanTest.Podman([]string{"top", session.OutputToString(), "-ef", "hn"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(ExitCleanly()) + output := result.OutputToString() + Expect(output).To(ContainSubstring("sleep inf")) + // check for https://github.com/containers/podman/issues/22293 + Expect(output).To(HavePrefix("9 "), "user id of process") }) It("podman top with comma-separated options", func() {