This is a question, not a confirmed finding. It was raised while auditing the create path for #172 and could not be settled without a Linux host with running containers. It is filed so it is not lost, and because if it holds it is an elevation path independent of allow_privileged.
The observation
pkg/dind/exec.go hand-builds the exec process spec rather than deriving it from the container's own spec — exec.go:194, :370, :681. Two properties of that hand-built spec:
User: {UID: 0, GID: 0} — docker exec always runs as root, even when the image or container specifies a non-root user. That deviates from real Docker, which defaults to the container's user.
Process.Capabilities is nil.
The question
With Process.Capabilities nil, does runc's setns_init leave the exec'd process holding the caller's capability set — i.e. host root — rather than the container's bounding set?
If it does, then docker exec into an otherwise unprivileged sibling yields a full-capability process, and dind.allow_privileged = false does not constrain it. That would matter a great deal on a host with no VM fence, which is the Linux case the flag exists for.
If runc instead applies the container's bounding set regardless, this is a non-issue for capabilities and reduces to the User: 0 deviation, which is a compatibility wart rather than a security one.
Why it wasn't settled
runc's source is not vendored here, and the check needs a live container. It was not appropriate to run against fleet nodes.
The cheap check
On a scratch Linux node with ephemerd running and dind.allow_privileged = false:
docker run -d --name t alpine sleep 600
docker exec t grep Cap /proc/self/status
docker exec t sh -c 'grep Cap /proc/1/status'
Compare CapEff/CapBnd for the exec'd process against PID 1 in the same container. If the exec'd process has a wider set, the concern is real. Decoding: capsh --decode=<hex>.
Related
This is a question, not a confirmed finding. It was raised while auditing the create path for #172 and could not be settled without a Linux host with running containers. It is filed so it is not lost, and because if it holds it is an elevation path independent of
allow_privileged.The observation
pkg/dind/exec.gohand-builds the exec process spec rather than deriving it from the container's own spec —exec.go:194,:370,:681. Two properties of that hand-built spec:User: {UID: 0, GID: 0}—docker execalways runs as root, even when the image or container specifies a non-root user. That deviates from real Docker, which defaults to the container's user.Process.Capabilitiesis nil.The question
With
Process.Capabilitiesnil, does runc'ssetns_initleave the exec'd process holding the caller's capability set — i.e. host root — rather than the container's bounding set?If it does, then
docker execinto an otherwise unprivileged sibling yields a full-capability process, anddind.allow_privileged = falsedoes not constrain it. That would matter a great deal on a host with no VM fence, which is the Linux case the flag exists for.If runc instead applies the container's bounding set regardless, this is a non-issue for capabilities and reduces to the
User: 0deviation, which is a compatibility wart rather than a security one.Why it wasn't settled
runc's source is not vendored here, and the check needs a live container. It was not appropriate to run against fleet nodes.
The cheap check
On a scratch Linux node with ephemerd running and
dind.allow_privileged = false:Compare
CapEff/CapBndfor the exec'd process against PID 1 in the same container. If the exec'd process has a wider set, the concern is real. Decoding:capsh --decode=<hex>.Related
--security-optbypass, which is confirmed and fixed separately.execCreateRequestdoes not decodePrivileged, sodocker exec --privilegedis already a silent no-op. This issue is about the default path, not that flag.