Containers and namespaces from the host

Map container processes and spot escapes.

Advanced16 min · lesson 6 of 15

A container is not a small virtual machine. It is one or more ordinary Linux processes that the kernel has placed in their own namespaces (private views of the process table, mounts, network and so on) and a cgroup (a resource-accounting group). That is the defender's advantage: from the host, a container is just processes, visible in ps and /proc, and everything that isolates them is readable through interfaces you already use. This lesson shows how to find a container's processes from the host whatever started them, map a container PID to a host PID, read and enter the namespaces that fence it in, and tell an ordinary container from one that can reach the host, the other road from a foothold to root. The lab uses podman (install it with sudo apt install podman on Ubuntu; it is RHEL's native engine) with a small pinned image; nothing is escaped, only inspected. linux-perf/k-ns teaches the mechanism; here the angle is detection.

A container is only processes

Pull a small image and start a container that runs sleep 900 with no network (--network none). Both commands print an ID: the image's, then the new container's.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo podman pull -q docker.io/library/alpine:3.23.4
2ffb2ff4aab36d06b7f3266bbb10e8232769cd2360613131d37abd19430cf6f1
$ sudo podman run -d --name ct-web --network none docker.io/library/alpine:3.23.4 sleep 900
509cefe146fc1ecc16676166e38ebec2a8c9cb06a52cb224bc291baf0cbec61d

Now find it from the host. The engine records the host-side PID of the container's first process, and ps shows it like any other.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo podman inspect -f '{{.State.Pid}}' ct-web ps -o pid,ppid,user,comm -p $(sudo podman inspect -f '{{.State.Pid}}' ct-web)
309726 PID PPID USER COMMAND 309726 309724 root sleep
$ P=$(sudo podman inspect -f '{{.State.Pid}}' ct-web); ps -o pid,ppid,user,comm -p $(ps -o ppid= -p $P | tr -d ' ')
PID PPID USER COMMAND 309724 1 root conmon

The sleep runs as host PID 309726, owned by root, and its parent is conmon, a small monitor that podman attaches to each container to hold its output and report its exit. conmon's parent is PID 1, so the container outlives the command that launched it. The runtime that built the namespaces, crun, has already exited. On the host a container is a normal process tree; the tell is not the process but where it sits, which the rest of this lesson reads.

Inside the container that same process thinks it is PID 1. Two views make the mapping explicit.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo podman top ct-web hpid pid user args
HPID PID USER COMMAND 309726 1 root sleep 900
$ sudo grep NSpid /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/status
NSpid: 309726 1

podman top ... hpid pid prints the host PID (309726) beside the in-container PID (1). NSpid: 309726 1 in /proc/PID/status says the same: the process's PID in each nested PID namespace, outermost first. An alert that names an in-container PID means nothing on the host until you translate it this way; and for any suspicious host PID, a second number on its NSpid line says it runs inside a PID namespace.

What isolates it: namespaces and the cgroup

The container's process is in its own namespaces. Compare each one against the host's PID 1 to see which views it has been given privately.

deploy@web01 · Ubuntu 26.04 LTS
$ P=$(sudo podman inspect -f '{{.State.Pid}}' ct-web) for ns in mnt pid net ipc uts cgroup user; do printf '%-7s host=%-22s container=%s\n' $ns $(sudo readlink /proc/1/ns/$ns) $(sudo readlink /proc/$P/ns/$ns); done
mnt host=mnt:[4026531832] container=mnt:[4026532304] pid host=pid:[4026531836] container=pid:[4026532509] net host=net:[4026531833] container=net:[4026532511] ipc host=ipc:[4026531839] container=ipc:[4026532508] uts host=uts:[4026531838] container=uts:[4026532437] cgroup host=cgroup:[4026531835] container=cgroup:[4026532573] user host=user:[4026531837] container=user:[4026531837]

Six namespaces differ from the host (mnt, pid, net, ipc, uts, cgroup): the container has its own mounts, process table, network stack, IPC objects, hostname and cgroup view. user does not differ. The container shares the host's user namespace, so its root is the host's real UID 0, held back only by the dropped capabilities and the access label shown below, not by a UID mapping: one missing capability or one bad mount away from root on the host. A rootless container (podman run by an ordinary user) would show a private user namespace mapping container-root to an unprivileged host UID.

The cgroup line says who manages it, and lsns counts the processes sharing each namespace.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo cat /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/cgroup
0::/machine.slice/libpod-509cefe146fc1ecc16676166e38ebec2a8c9cb06a52cb224bc291baf0cbec61d.scope/container
$ sudo lsns -p $(sudo podman inspect -f '{{.State.Pid}}' ct-web) -o NS,TYPE,NPROCS,PID,COMMAND
NS TYPE NPROCS PID COMMAND 4026531834 time 126 1 /usr/lib/systemd/systemd --switched-root --system --deserialize=51 4026531837 user 127 1 /usr/lib/systemd/systemd --switched-root --system --deserialize=51 4026532304 mnt 1 309726 sleep 900 4026532437 uts 1 309726 sleep 900 4026532508 ipc 1 309726 sleep 900 4026532509 pid 1 309726 sleep 900 4026532511 net 1 309726 sleep 900 4026532573 cgroup 1 309726 sleep 900

The cgroup path /machine.slice/libpod-<id>.scope/container marks this as a podman-managed container (systemd puts machines and containers under machine.slice), and the long id ties the process back to podman inspect. lsns shows each of the six namespaces holding a single process, while the time and user namespaces are the host's own (NPROCS counts the host processes in each at that moment, about 120 here; the counts move as processes start and exit). The cgroup path is the stronger container marker of the two, as the hunt at the end of this lesson shows.

Because these are just kernel objects, you can enter them from the host with nsenter, which is how you inspect a container without the engine or a shell inside it.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo nsenter -t $(sudo podman inspect -f '{{.State.Pid}}' ct-web) -n ip -brief addr
lo UNKNOWN 127.0.0.1/8 ::1/128
$ sudo nsenter -t $(sudo podman inspect -f '{{.State.Pid}}' ct-web) -m sh -c 'cat /etc/os-release | head -2; ls /'
NAME="Alpine Linux" ID=alpine bin dev …

Entering the network namespace (-n) shows only a loopback interface, because this container was run with --network none. Entering the mount namespace (-m) drops you into the container's root filesystem: NAME="Alpine Linux" and a root that is the image's, not the host's Ubuntu. nsenter against the host PID is a defender's tool: it lets you list files, sockets or processes as the container sees them during an investigation, using only host privileges.

What it may do: capabilities and the access label

A rootful container's root is restrained by two things. The first is a reduced capability set: the kernel splits root's power into separate capabilities (linux-det/peloc), and podman keeps only a small default subset. The second is a mandatory-access-control label: an AppArmor profile on Ubuntu, an SELinux type on RHEL.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo grep -E 'Cap(Eff|Bnd)' /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/status sudo capsh --decode=$(sudo awk '/CapEff/{print $2}' /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/status)
CapEff: 00000000800405fb CapBnd: 00000000800405fb 0x00000000800405fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_sys_chroot,cap_setfcap
$ sudo cat /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/attr/current
containers-default-0.66.0 (enforce)

CapEff: 00000000800405fb decodes to eleven capabilities, not the full forty-plus. cap_sys_admin, cap_sys_module, cap_sys_ptrace and the other broad ones are absent, which is why a default container cannot load a kernel module, re-mount the host filesystem or trace host processes even though it runs as UID 0. The AppArmor label containers-default-0.66.0 (enforce) is podman's default profile, confining the process further. On RHEL the confinement is SELinux, and the label shows in ps -eZ.

deploy@rocky10 · Rocky Linux 10.2
$ sudo ps -eZ | grep -w $(sudo podman inspect -f '{{.State.Pid}}' ct-web)
system_u:system_r:container_t:s0:c788,c802 124078 ? 00:00:00 sleep
$ sudo grep -E 'CapEff' /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/status sudo capsh --decode=$(sudo awk '/CapEff/{print $2}' /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-web)/status)
CapEff: 00000000800405fb 0x00000000800405fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_sys_chroot,cap_setfcap

The type container_t with a unique category pair (s0:c788,c802) is how SELinux isolates one container from another and from the host: two containers get different categories, so even as root neither can touch the other's files. The capability set is the same reduced eleven. So on both platforms the defensive baseline for a container process is: a known-small capability set and a confining label. A container process with the full capability set, or an unconfined label, has neither guardrail, and that is the next thing to recognise.

A privileged container, and the escape signals

A privileged container is one started with --privileged, often with extra host access bolted on. It is sometimes legitimate (a monitoring agent, a storage driver), but it is also what an attacker asks for, because it removes the guardrails above. Start one the way it is usually found, with the host's process namespace (--pid=host) and the host's root filesystem mounted at /host (-v /:/host), and read it from the host without entering it.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo podman run -d --name ct-priv --privileged --pid=host -v /:/host --network none docker.io/library/alpine:3.23.4 sleep 900
5142e2d64bce43ada243bb1827e7d1cbe64ab3cbd734b7b401ac772e3d47f4ae
$ sudo podman inspect -f 'privileged={{.HostConfig.Privileged}} pidmode={{.HostConfig.PidMode}} mounts={{range .Mounts}}{{.Source}}->{{.Destination}} {{end}}' ct-priv
privileged=true pidmode=host mounts=/->/host
$ sudo grep -E 'CapEff' /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-priv)/status sudo cat /proc/$(sudo podman inspect -f '{{.State.Pid}}' ct-priv)/attr/current
CapEff: 000001ffffffffff crun (unconfined)

podman inspect reports privileged=true, pidmode=host, and the host root mounted at /host. The process carries CapEff: 000001ffffffffff, the full capability set, and its label is crun (unconfined): Ubuntu ships a crun AppArmor profile, but it is declared unconfined, so no AppArmor restriction applies.

deploy@web01 · Ubuntu 26.04 LTS
$ grep -E "^profile" /etc/apparmor.d/crun
profile crun /usr/bin/crun flags=(unconfined) {

Those three facts, full capabilities, no confining label, host resources mapped in, are the signature of a container that can trivially become the host. The namespace view confirms it.

deploy@web01 · Ubuntu 26.04 LTS
$ P=$(sudo podman inspect -f '{{.State.Pid}}' ct-priv) for ns in pid mnt; do printf '%-4s host=%-22s container=%s\n' $ns $(sudo readlink /proc/1/ns/$ns) $(sudo readlink /proc/$P/ns/$ns); done
pid host=pid:[4026531836] container=pid:[4026531836] mnt host=mnt:[4026531832] container=mnt:[4026532574]
$ sudo findmnt -N $(sudo podman inspect -f '{{.State.Pid}}' ct-priv) -no TARGET,SOURCE,FSTYPE,OPTIONS /host
/host /dev/vda1 ext4 rw,relatime,discard,errors=remount-ro,commit=30

--pid=host means the container shares the host's PID namespace (pid matches PID 1's), so its processes can see and signal every process on the host. findmnt against its PID shows the host's real root filesystem (/dev/vda1) mounted inside it at /host, read-write (rw): from there, root in the container reads and writes every file on the host. (A :ro suffix on -v would mount it read-only, but a privileged container's root holds CAP_SYS_ADMIN and can remount it, so treat any host root mount in a privileged container as writable.) You do not demonstrate the escape; you recognise the shape.

The escape signals, scoped to containers
These signals count only for a process inside a container: its cgroup is a container scope (machine.slice/libpod-*, docker-*, cri-containerd-*, kubepods) or its PID namespace differs from PID 1's. On the host every root process has a full CapEff, and on Ubuntu most are AppArmor unconfined, so an unscoped rule fires on the whole process table. Inside that scope, three findings mark a container that can reach the host: a full capability set or an unconfined label; the host's PID or network namespace (--pid=host, --net=host: its /proc/PID/ns/* matches PID 1's); and a host path mounted in, above all /, /proc, /dev or an engine socket (/var/run/docker.sock, /run/podman/podman.sock, /run/containerd/containerd.sock). Baseline the containers that legitimately need these, and alert on any other container that has them.

Finding every container, whatever started it

The engine's own list is the obvious starting point, and it is incomplete.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo podman ps --format "{{.Names}} {{.Image}} {{.Command}}"
ct-web docker.io/library/alpine:3.23.4 sleep 900 ct-priv docker.io/library/alpine:3.23.4 sleep 900

sudo podman ps lists the containers of root's podman and nothing else. Rootless podman keeps each user's containers in that user's own storage, so root's list never shows them (sudo -iu name podman ps does, per account). Docker, containerd and CRI-O (the Kubernetes runtimes, queried with docker ps and crictl ps) keep their own lists. And a process can enter private namespaces with unshare without any engine at all. Start one of those as a stand-in for a sandbox no engine knows about.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo unshare --pid --fork --mount-proc sleep 907 >/dev/null 2>&1 &

Now ask the kernel instead. lsns -t pid lists every PID namespace on the host with its lowest-numbered process, whoever created it.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo lsns -t pid -o NS,NPROCS,PID,USER,COMMAND
NS NPROCS PID USER COMMAND 4026531836 130 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=51 4026532509 1 309726 root sleep 900 4026532639 1 310482 root sleep 907
$ for p in $(sudo lsns -t pid -n -o PID); do echo "$p $(sudo cat /proc/$p/cgroup)"; done
1 0::/init.scope 309726 0::/machine.slice/libpod-509cefe146fc1ecc16676166e38ebec2a8c9cb06a52cb224bc291baf0cbec61d.scope/container 310482 0::/user.slice/user-502.slice/session-4.scope

Three PID namespaces: the host's, ct-web's, and the unshare sandbox podman did not list. Their cgroups tell them apart: ct-web sits in a machine.slice/libpod-* scope, the sandbox in a login session's scope (here user 502's, the account the lab VM's tool runs commands through; on your host, your own session). A private namespace whose cgroup is neither a container scope nor a service you can explain is the finding to chase. ct-priv is missing because --pid=host keeps it in the host's PID namespace, so also read every process's cgroup for container scopes.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo grep -hoE '(libpod|docker|cri-containerd|crio)-[0-9a-f]{12}' /proc/[0-9]*/cgroup 2>/dev/null | sort | uniq -c
1 libpod-509cefe146fc 1 libpod-5142e2d64bce

Both podman containers show up, one scope each, including the privileged one the PID-namespace view missed. The same pattern names Docker (docker-), containerd (cri-containerd-) and CRI-O (crio-) containers. A private mount namespace on its own proves nothing, though.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo lsns -t mnt -o NS,NPROCS,PID,COMMAND
NS NPROCS PID COMMAND 4026531832 116 1 /usr/lib/systemd/systemd --switched-root --system --deserialize=51 4026532303 1 472 ├─/usr/lib/systemd/systemd-udevd 4026532436 1 821 ├─/usr/lib/systemd/systemd-networkd 4026532490 1 905 ├─/usr/lib/systemd/systemd-logind 4026532504 1 121617 ├─/usr/lib/polkit-1/polkitd --no-debug --log-level=notice 4026532506 1 121631 ├─/usr/sbin/ModemManager 4026532510 1 300800 ├─/usr/libexec/fwupd/fwupd 4026532569 1 132654 ├─/usr/lib/systemd/systemd-resolved 4026532570 3 251187 └─/bin/sh /usr/lib/systemd/scripts/chronyd-starter.sh -n -F 1 … 4026532304 1 309726 sleep 900 4026532574 1 310239 sleep 900 4026532638 2 310481 unshare --pid --fork --mount-proc sleep 907

systemd gives services their own mount namespace when a unit uses sandboxing settings such as PrivateTmp= or ProtectSystem=, so resolved, udevd, networkd, ModemManager, polkitd, logind, fwupd and chrony all have one on this server, none of them in a container. PrivateUsers= and PrivateNetwork= do the same for user and network namespaces. So never alert on a private namespace alone: pair it with the cgroup, where a system.slice unit whose sandboxing explains it is expected and a login session or an unknown scope is not, and audit unshare and setns calls from accounts with no reason to make them. The Rocky lab shows both sides.

deploy@rocky10 · Rocky Linux 10.2
$ sudo podman ps --format "{{.Names}} {{.Image}} {{.Command}}"
ct-web docker.io/library/alpine:3.23.4 sleep 900
$ sudo lsns -t user -o NS,NPROCS,PID,USER,COMMAND
NS NPROCS PID USER COMMAND 4026531837 150 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize=51 no_timer_check 4026532361 1 761 root └─/usr/sbin/irqbalance 4026532499 2 1187 lima /proc/self/exe --state-dir=/run/user/502/containerd-rootless --net=slirp4netns --mtu=65520 --slirp4netns-sandbox=auto --slirp4netns-seccomp=auto --disable-host-loopback --port-driver=builtin --copy-up=/etc --copy-up=/run --copy-up=/var/lib --propagation=rslave --detach-netns /usr/local/bin/containerd-rootless.sh

irqbalance has a user namespace of its own because its unit sets PrivateUsers=true: expected. Root's podman lists only ct-web, yet lsns -t user also finds a user namespace owned by lima running a rootless containerd. That one belongs to the lab VM's tool, not to RHEL, but an intruder's rootless container looks exactly the same: a non-root owner, a private user namespace, and no entry in root's engine.

Ubuntu restricts unprivileged user namespaces

User namespaces let an unprivileged user become root inside a namespace of their own, which is what makes rootless containers possible. The same feature has been the way into many kernel exploits, because it hands an ordinary user code paths that expect privilege. Ubuntu 26.04 ships a mitigation on by default; linux-hard/sysctlkern showed the switch, and here is exactly what it does.

deploy@web01 · Ubuntu 26.04 LTS
$ sysctl kernel.apparmor_restrict_unprivileged_userns
kernel.apparmor_restrict_unprivileged_userns = 1
$ unshare -U id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup),65534(nogroup),65534(nogroup)
$ unshare -U -r id
unshare: write failed /proc/self/uid_map: Operation not permitted

kernel.apparmor_restrict_unprivileged_userns is 1. An unprivileged user can still create a user namespace (unshare -U succeeds, leaving them mapped to nobody; every ID with no mapping shows as the overflow ID 65534, so each of deploy's supplementary groups appears as nogroup), but the privileged step, mapping their UID to root inside it with -r, is refused. The restriction is enforced through AppArmor, so the denial is recorded.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo ausearch -if /var/log/audit/audit.log -m AVC 2>/dev/null | grep 'profile="unprivileged_userns"' | tail -1
type=AVC msg=audit(1790507316.129:19317): apparmor="DENIED" operation="capable" class="cap" profile="unprivileged_userns" pid=310657 comm="unshare" capability=21 capname="sys_admin"

The audit record names the mechanism: AppArmor DENIED the CAP_SYS_ADMIN the mapping needs, under a profile called unprivileged_userns that the kernel attaches to any unconfined program trying this. (auditd is installed on this lab host; linux-det/auditpipe covers reading these records, and without auditd the same line lands in journalctl -k. capability=21 is CAP_SYS_ADMIN on every architecture: capability numbers, unlike system-call numbers, do not vary.) Programs that genuinely need user namespaces are allowed through a profile that grants the userns permission, which is how podman still works.

deploy@web01 · Ubuntu 26.04 LTS
$ grep -vE "^[[:space:]]*(#|$)" /etc/apparmor.d/podman
abi <abi/5.0>, include <tunables/global> profile podman /usr/bin/podman flags=(unconfined) { userns, @{exec_path} mr, include if exists <local/podman> }

The podman profile is flags=(unconfined) and lists userns,, so podman may create user namespaces while a bare unshare from an ordinary account may not. RHEL takes a different path. The sysctl does not exist there, user.max_user_namespaces is sized from memory rather than set to zero, and unconfined_t does not restrict user namespaces, so the same unshare -U -r succeeds on the Rocky lab.

deploy@rocky10 · Rocky Linux 10.2
$ sysctl kernel.apparmor_restrict_unprivileged_userns
sysctl: cannot stat /proc/sys/kernel/apparmor_restrict_unprivileged_userns: No such file or directory
$ sysctl user.max_user_namespaces
user.max_user_namespaces = 14365
$ unshare -U -r id
uid=0(root) gid=0(root) groups=0(root),65534(nobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

On RHEL, then, every local account can reach the kernel code that user namespaces expose. Where no rootless containers or other user-namespace users run, setting user.max_user_namespaces = 0 in a sysctl.d file closes it (linux-hard/sysctlkern); where they do, audit unshare and setns by accounts that have no reason to call them. Know which platform you are on before you conclude that an unprivileged user namespace is or is not a concern.

A container from the host: shared, isolated, and dangerous
Shared with host
user namespace (rootful)
container root = host UID 0
held back by
dropped caps + LSM label
time namespace
the host clock
Isolated
mnt, pid, net, ipc, uts
private views
cgroup
machine.slice/libpod-*
enter with
nsenter -t HOSTPID
Escape signals
full CapEff / unconfined
no guardrails
--pid=host / --net=host
sees host processes
host path mounted in
/ , /proc, docker.sock
Everything here is readable from the host with ps, /proc, podman inspect and nsenter.

Try this

On the Ubuntu lab, with podman installed, start sudo podman run -d --name t --network none docker.io/library/alpine:3.23.4 sleep 600. Find its host PID with sudo podman inspect -f '{{.State.Pid}}' t and confirm the mapping with sudo grep NSpid /proc/<pid>/status (host PID, then 1). Compare namespaces with for ns in pid net user; do echo $ns $(sudo readlink /proc/1/ns/$ns) $(sudo readlink /proc/<pid>/ns/$ns); done: pid and net differ, user matches. Run the cgroup-scope count from this lesson and find one more libpod- scope. Decode CapEff with sudo capsh --decode=<value> and see the eleven capabilities, then remove it with sudo podman rm -f t and confirm the scope count drops again.

Takeaway

Find containers from the kernel side (container cgroup scopes and private PID or user namespaces) before you ask any engine, then treat each as processes: map its host PID and read its namespaces, capabilities and LSM label from /proc. Alert on the escape signals only inside container scopes, and baseline the containers that are allowed to have them.

Quick check
01An alert fires on "PID 1 running a crypto miner" inside a container on one of your hosts. You run ps on the host and see no miner at PID 1 (it is systemd). What has gone wrong, and what do you do?
Incorrect — A default container has its own PID namespace, so its PID 1 is not the host's; killing host PID 1 would take the machine down and is never the move.
Correct — In-container PIDs (like 1) are per-namespace; you map them to a host PID with podman top ... hpid pid or /proc/PID/status NSpid, then act on the host PID.
Incorrect — Nothing here shows an escape or a deletion; the mismatch is simply that a container PID and a host PID are different numbers in different namespaces.
Incorrect — PIDs are not randomised; the container's PID is stable within its namespace and maps to a fixed host PID you can look up.
02Reading a rootful container's /proc/PID/ns/*, you find every namespace differs from the host's PID 1 except user, which is identical. Why does that matter for containment?
Incorrect — Rootful containers routinely share the host user namespace; it is a normal configuration, not a fault.
Incorrect — That describes the time namespace; the user namespace governs UID mapping, and sharing it means container UID 0 is host UID 0.
Incorrect — The other six namespaces still isolate mounts, PIDs, network and the rest; the concern is specifically that UID 0 is not remapped, not that isolation is absent.
Correct — With a shared user namespace there is no UID remapping, so the only things standing between container-root and host-root are the reduced capability set and the AppArmor/SELinux label.
03Enumerating containers on a host, which finding most directly says a container can read and write the host's files as root?
Incorrect — That path only identifies it as a podman-managed container; every podman container has one and it says nothing about host access.
Incorrect — That is the restricted default, the safe case; it is the full capability set, not the default, that removes the guardrail.
Correct — A host path mounted in, above all /, is a direct route out; combined with a rootful container's UID 0 it is full read/write of the host.
Incorrect — The time namespace is shared by ordinary containers too and has no bearing on file access.

Related