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 <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger 2024-06-25 14:41:30 +02:00
parent 5e27243935
commit 65ed96585d
No known key found for this signature in database
GPG Key ID: EB145DD938A3CAF2
3 changed files with 56 additions and 6 deletions

View File

@ -3,6 +3,7 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <errno.h> #include <errno.h>
#include <sched.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/mount.h> #include <sys/mount.h>
@ -11,6 +12,7 @@
/* keep special_exit_code in sync with container_top_linux.go */ /* keep special_exit_code in sync with container_top_linux.go */
int special_exit_code = 255; int special_exit_code = 255;
int join_userns = 0;
char **argv = NULL; char **argv = NULL;
void void
@ -33,6 +35,12 @@ set_argv (int pos, char *arg)
argv[pos] = arg; argv[pos] = arg;
} }
void
set_userns ()
{
join_userns = 1;
}
/* /*
We use cgo code here so we can fork then exec separately, 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 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"); fprintf (stderr, "mount proc: %m");
exit (special_exit_code); 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 */ /* use execve to unset all env vars, we do not want to leak anything into the container */
execve (argv[0], argv, NULL); execve (argv[0], argv, NULL);
fprintf (stderr, "execve: %m"); fprintf (stderr, "execve: %m");

View File

@ -31,6 +31,7 @@ import (
void fork_exec_ps(); void fork_exec_ps();
void create_argv(int len); void create_argv(int len);
void set_argv(int pos, char *arg); void set_argv(int pos, char *arg);
void set_userns();
*/ */
import "C" import "C"
@ -56,13 +57,13 @@ func podmanTopMain() {
os.Exit(0) 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 // 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 // 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. // ps are read only to prevent a process from overwriting it.
func podmanTopInner() error { func podmanTopInner() error {
if len(os.Args) < 3 { if len(os.Args) < 4 {
return fmt.Errorf("internal error, need at least two arguments") 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 // 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) return fmt.Errorf("make / mount private: %w", err)
} }
psPath := os.Args[2] psPath := os.Args[3]
// try to mount everything read only // try to mount everything read only
if err := unix.MountSetattr(0, "/", unix.AT_RECURSIVE, &unix.MountAttr{ if err := unix.MountSetattr(0, "/", unix.AT_RECURSIVE, &unix.MountAttr{
@ -122,8 +123,13 @@ func podmanTopInner() error {
} }
pidFD.Close() pidFD.Close()
userns := os.Args[2]
if userns == "1" {
C.set_userns()
}
args := []string{psPath} args := []string{psPath}
args = append(args, os.Args[3:]...) args = append(args, os.Args[4:]...)
C.create_argv(C.int(len(args))) C.create_argv(C.int(len(args)))
for i, arg := range args { for i, arg := range args {
@ -317,7 +323,14 @@ func (c *Container) execPS(psArgs []string) ([]string, bool, error) {
wPipe.Close() wPipe.Close()
return nil, true, err 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 := reexec.Command(args...)
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{

View File

@ -119,6 +119,18 @@ var _ = Describe("Podman top", func() {
exec := podmanTest.Podman([]string{"top", session.OutputToString(), "aux"}) exec := podmanTest.Podman([]string{"top", session.OutputToString(), "aux"})
exec.WaitWithDefaultTimeout() exec.WaitWithDefaultTimeout()
Expect(exec).Should(ExitWithError(125, "OCI runtime attempted to invoke a command that was not found")) 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() { It("podman top with comma-separated options", func() {