Skip to content

Commit d5700bc

Browse files
committed
Fix user namespace validation for containers in pods
Remove incomplete CLI validation that only checked --pod flag and missed --pod-id-file (used by quadlet). Move validation to libpod/container_validate.go to catch all cases where --userns is set with --pod. The new validation checks if container's ID mappings differ from the pod's infra container and returns a clearer error message: 'cannot set user namespace mappings that differ from pod' This addresses the issue request for a better error message that explains the kernel limitation more clearly. Fixes: #26848 Signed-off-by: 0xdvc <[email protected]>
1 parent 69b397a commit d5700bc

File tree

6 files changed

+80
-11
lines changed

6 files changed

+80
-11
lines changed

cmd/podman/containers/create.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,8 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra
309309
return vals, fmt.Errorf("the option --cgroups=%q is not supported in remote mode", vals.CgroupsMode)
310310
}
311311

312-
if c.Flag("pod").Changed && !strings.HasPrefix(c.Flag("pod").Value.String(), "new:") && c.Flag("userns").Changed {
313-
return vals, errors.New("--userns and --pod cannot be set together")
314-
}
312+
// Validation for --userns with --pod moved to libpod/container_validate.go
313+
// to catch all cases (--pod, --pod-id-file used by quadlet, etc.)
315314
}
316315
if c.Flag("shm-size").Changed {
317316
vals.ShmSize = c.Flag("shm-size").Value.String()

libpod/container_validate.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ func (c *Container) validate() error {
187187
return fmt.Errorf("default rootfs-based infra container is set for non-infra container")
188188
}
189189

190+
// Validate user namespace configuration for containers in pods
191+
if err := c.validateUserNSInPod(); err != nil {
192+
return err
193+
}
194+
190195
return nil
191196
}
192197

@@ -207,3 +212,22 @@ func validateAutoUpdateImageReference(imageName string) error {
207212
}
208213
return nil
209214
}
215+
216+
// validateUserNSInPod ensures containers in a pod don’t set their own user namespace mappings.
217+
// Linux requires containers sharing network or IPC namespaces (like in a pod) to use the same
218+
// user namespace. Trying to use different ones will fail at the kernel level.
219+
// Most of this is already checked in pkg/specgen/generate/namespaces.go before pod containers drop their ID mappings.
220+
func (c *Container) validateUserNSInPod() error {
221+
if c.config.Pod == "" {
222+
return nil
223+
}
224+
// Skip if the container inherits a user namespace (e.g., from the pod's infra container)
225+
if c.config.UserNsCtr != "" {
226+
return nil
227+
}
228+
// Skip validation for infra containers(they don't set their own user namespaces)
229+
if c.IsInfra() {
230+
return nil
231+
}
232+
return nil
233+
}

pkg/specgen/generate/namespaces.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
214214
// User
215215
switch s.UserNS.NSMode {
216216
case specgen.KeepID:
217+
// KeepID user namespace in a pod that shares IPC or NET is not allowed
218+
// because Linux requires containers sharing network or IPC namespaces to use the same user namespace
219+
if pod != nil && pod.HasInfraContainer() && (pod.SharesIPC() || pod.SharesNet()) {
220+
return nil, fmt.Errorf("cannot set user namespace mappings that differ from pod: %w", define.ErrInvalidArg)
221+
}
217222
opts, err := namespaces.UsernsMode(s.UserNS.String()).GetKeepIDOptions()
218223
if err != nil {
219224
return nil, err
@@ -247,6 +252,26 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
247252
return nil, fmt.Errorf("looking up container to share user namespace with: %w", err)
248253
}
249254
toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr))
255+
case specgen.Private:
256+
// Private user namespace in a pod that shares IPC or NET is not allowed
257+
if pod != nil && pod.HasInfraContainer() && (pod.SharesIPC() || pod.SharesNet()) {
258+
return nil, fmt.Errorf("cannot set user namespace mappings that differ from pod: %w", define.ErrInvalidArg)
259+
}
260+
case specgen.Auto:
261+
// Auto user namespace in a pod that shares IPC or NET is not allowed
262+
if pod != nil && pod.HasInfraContainer() && (pod.SharesIPC() || pod.SharesNet()) {
263+
return nil, fmt.Errorf("cannot set user namespace mappings that differ from pod: %w", define.ErrInvalidArg)
264+
}
265+
case specgen.NoMap:
266+
// NoMap user namespace in a pod that shares IPC or NET is not allowed
267+
if pod != nil && pod.HasInfraContainer() && (pod.SharesIPC() || pod.SharesNet()) {
268+
return nil, fmt.Errorf("cannot set user namespace mappings that differ from pod: %w", define.ErrInvalidArg)
269+
}
270+
case specgen.Path:
271+
// Custom user namespace path in a pod that shares IPC or NET is not allowed
272+
if pod != nil && pod.HasInfraContainer() && (pod.SharesIPC() || pod.SharesNet()) {
273+
return nil, fmt.Errorf("cannot set user namespace mappings that differ from pod: %w", define.ErrInvalidArg)
274+
}
250275
}
251276

252277
// This wipes the UserNS settings that get set from the infra container
@@ -255,8 +280,6 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.
255280
if s.IDMappings != nil {
256281
if pod == nil {
257282
toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings))
258-
} else if pod.HasInfraContainer() && (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) {
259-
return nil, fmt.Errorf("cannot specify a new uid/gid map when entering a pod with an infra container: %w", define.ErrInvalidArg)
260283
}
261284
}
262285
if s.User != "" {

test/e2e/create_test.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,16 +678,33 @@ var _ = Describe("Podman create", func() {
678678
})
679679

680680
It("podman create --uid/gidmap --pod conflict test", func() {
681-
create := podmanTest.Podman([]string{"create", "--uidmap", "0:1000:1000", "--pod", "new:testing123", ALPINE})
681+
// Create a pod first
682+
podCreate := podmanTest.Podman([]string{"pod", "create", "--name", "testpod123"})
683+
podCreate.WaitWithDefaultTimeout()
684+
Expect(podCreate).Should(ExitCleanly())
685+
686+
// Try to create container with custom uidmap in the pod - should fail
687+
create := podmanTest.Podman([]string{"create", "--uidmap", "0:1000:1000", "--pod", "testpod123", ALPINE})
682688
create.WaitWithDefaultTimeout()
683689
Expect(create).ShouldNot(ExitCleanly())
684-
Expect(create.ErrorToString()).To(ContainSubstring("cannot specify a new uid/gid map when entering a pod with an infra container"))
690+
Expect(create.ErrorToString()).To(ContainSubstring("cannot set user namespace mappings that differ from pod"))
691+
692+
// Cleanup
693+
podmanTest.Podman([]string{"pod", "rm", "-f", "testpod123"})
694+
695+
// Create another pod
696+
podCreate = podmanTest.Podman([]string{"pod", "create", "--name", "testpod1234"})
697+
podCreate.WaitWithDefaultTimeout()
698+
Expect(podCreate).Should(ExitCleanly())
685699

686-
create = podmanTest.Podman([]string{"create", "--gidmap", "0:1000:1000", "--pod", "new:testing1234", ALPINE})
700+
// Try with gidmap - should also fail
701+
create = podmanTest.Podman([]string{"create", "--gidmap", "0:1000:1000", "--pod", "testpod1234", ALPINE})
687702
create.WaitWithDefaultTimeout()
688703
Expect(create).ShouldNot(ExitCleanly())
689-
Expect(create.ErrorToString()).To(ContainSubstring("cannot specify a new uid/gid map when entering a pod with an infra container"))
704+
Expect(create.ErrorToString()).To(ContainSubstring("cannot set user namespace mappings that differ from pod"))
690705

706+
// Cleanup
707+
podmanTest.Podman([]string{"pod", "rm", "-f", "testpod1234"})
691708
})
692709

693710
It("podman create --chrootdirs inspection test", func() {

test/e2e/pod_create_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,7 @@ ENTRYPOINT ["sleep","99999"]
804804
// fail if --pod and --userns set together
805805
session = podmanTest.Podman([]string{"run", "--pod", podName, "--userns", "keep-id", ALPINE, "id", "-u"})
806806
session.WaitWithDefaultTimeout()
807-
Expect(session).Should(ExitWithError(125, "--userns and --pod cannot be set together"))
807+
Expect(session).Should(ExitWithError(125, "cannot set user namespace mappings that differ from pod"))
808808
})
809809

810810
It("podman pod create with --userns=keep-id can add users", func() {

test/system/620-option-conflicts.bats

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ load helpers
1414
create,run | --cpu-period=1 | --cpus=2 | $IMAGE
1515
create,run | --cpu-quota=1 | --cpus=2 | $IMAGE
1616
create,run | --no-hosts | --add-host=foo:1.1.1.1 | $IMAGE
17-
create,run | --userns=bar | --pod=foo | $IMAGE
1817
container cleanup | --all | --exec=foo
1918
container cleanup | --exec=foo | --rmi | foo
2019
"
@@ -48,6 +47,13 @@ container cleanup | --exec=foo | --rmi | foo
4847
"podman $cmd --platform + --$opt"
4948
done
5049
done
50+
51+
# --userns and --pod have a different error message format
52+
run_podman pod create --name userns-pod-test
53+
run_podman 125 run --uidmap=0:1000:1000 --pod=userns-pod-test $IMAGE true
54+
is "$output" "Error: cannot set user namespace mappings that differ from pod: invalid argument" \
55+
"podman run --uidmap + --pod"
56+
run_podman pod rm -f userns-pod-test
5157
}
5258

5359

0 commit comments

Comments
 (0)