docker-outside-of-docker: processes started before the entrypoint's `groupmod` keep stale groups and cannot use the socket
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 18/100
Hướng nghiên cứu
The bug is in the groupmod branch of the docker-outside-of-docker Feature, whose entrypoint is docker-init.sh; the issue also refers to the Feature's socat proxy fallback. Read how the Feature chooses between groupmod and the proxy, then reproduce by checking /proc/1/status against a fresh process's groups. Done means the container's own process can use the socket, and the maintainers have decided whether the fix is an opt-in option or a change to the default or to the CLI wrapper.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
When the host Docker socket's group ID is nonzero and not used by any group in the container, the Feature's
entrypoint (docker-init.sh) runs groupmod --gid <socket gid> docker. That changes /etc/group but not the
supplementary groups of processes that are already running. Those processes keep getting permission denied
on the socket. Processes started afterwards (docker exec, terminals, lifecycle commands) get fresh groups
and work, which is why this is easy to miss.
With the dev container CLI, the already-running process is the shell that starts the container, so anything
it runs directly (for example the image's command when overrideCommand is false) is affected.
How the CLI starts the container
The CLI (checked in 0.90.0, dist/spec-node/devContainersSpecCLI.js) runs the container as:
/bin/sh -c 'echo Container started
trap "exit 0" 15
/usr/local/share/docker-init.sh # the Feature entrypoints, called with no arguments
exec "$@" # the image entrypoint/cmd when overrideCommand is false
while sleep 1 & wait $!; do :; done' -
docker-init.sh runs as a child of that shell, after the shell has already received its groups. The same
wrapper is generated for Docker Compose. So the script cannot repair the credentials of the process that
started it.
Reproduction
Needs a Linux Docker host whose socket GID is nonzero, differs from the container's docker group GID, and
is not used by another group in the container. (If the socket is owned by root, the Feature already
uses its socat proxy and this does not occur.)
devcontainer.json:
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": { "moby": false }
}
}
Open it, then in a terminal in the container:
stat -c %g /var/run/docker-host.sock # host socket GID, 997 in my run
getent group docker # now has that GID, 997
id # fresh process: includes 997(docker)
docker ps # works
grep Groups /proc/1/status # container's own process: Groups: 999 1000 (the original docker GID)
# Run docker with exactly the groups of PID 1:
sudo setpriv --reuid "$(id -u)" --regid "$(id -g)" \
--groups "$(awk '/^Groups:/ {$1=""; print}' /proc/1/status | xargs | tr ' ' ',')" \
env HOME=/tmp docker ps
# permission denied while trying to connect to the docker API at unix:///var/run/docker.sock
Observed with: Feature 1.10.1, dev containers CLI 0.90.0, Docker 29.9.2 on Linux arm64 (Docker Desktop VM,
linuxkit kernel), base image mcr.microsoft.com/devcontainers/base:ubuntu-24.04, containerUser: vscode.
Nothing here is specific to arm64.
Expected
Processes that start with the container can use the Docker socket, as processes started later can.
Why tests do not catch it
Feature tests and docker exec --user start a new process, which resolves supplementary groups from the
current /etc/group. They pass while the container's own process is stale. A test has to inspect the
container's own process (for example /proc/1/status) or run docker with its group list, as above.
Impact
I only verified the mechanism above, not an end-to-end Compose service. From the CLI wrapper it follows that
a workload started with overrideCommand: false would hit this; terminals, lifecycle commands and
extensions use docker exec and do not. A startup race (an exec before the entrypoint finishes) is also
conceivable but I did not test it. #1005 (permission denied when host and container docker GIDs differ) may
be related, but I could not tell from that thread whether this is its mechanism.
What does not fix it
- Changing the group, or adding the user to a new group with the host GID, in
docker-init.sh(same
problem: already-running processes keep their groups). - Re-launching the command from inside
docker-init.sh(for example viasudo -u). I tried this. It works
if the script is given a command, but the CLI calls it with no arguments, so it never runs there.
Proposed fix
I have a PR ready: an opt-in alwaysUseSocketProxy option (default false) that always uses the Feature's
existing socat proxy instead of groupmod. The proxy socket is owned by the user, so no group membership is
needed. The default behavior is unchanged. It comes with a scenario that checks the container's own
process, which fails without the option.
Questions for maintainers:
-
Opt-in option, or change the default for this case? "This case" is the
groupmodbranch: the host
socket GID is nonzero and unused in the container. I chose opt-in because it changes nothing for existing
users. The alternative is to use the proxy there by default, which would fix this for everyone but would
give all of those users a separate user-owned socket instead of the host's own, an extra long-lived
process, and the proxy's trade-offs. I'd keep it opt-in unless you prefer the default change, and I'd
expect that to be a major version bump. Which do you prefer? -
Should this be fixed in the dev containers CLI instead (or as well)? The cause is the CLI's startup
wrapper (above): it runs the entrypoints as a child, then runs the container command from a shell that
started beforegroupmod. A fix there, such as running the command after the entrypoints in a way that
re-reads groups, would cover every Feature entrypoint, not just this one. I have not tried it, and my
attempt to do the equivalent insidedocker-init.shshowed it changes the process tree (sudoas PID 1),
the environment and stop timing, and needssudo. I also have not checked whether the VS Code extension and
Codespaces use the same wrapper. The Feature-level option works regardless of the tool. If you would rather
handle it in the CLI, I can move this there, and the option could stay as a stopgap or be dropped.
Related
- #483:
socatdrops output that arrives after the client stops sending. This affects the existing proxy
fallback and would affect the new option. I have a separate small fix (socat -t) ready as a second PR. - #1749 (open): changes the same branch logic (reads the docker GID at runtime, uses
getent). It does not
address this. - #1005: possibly related (see above).
- Ngôn ngữ chính
- Shell
- Star
- 1.6k
- Fork
- 628
- Merge trung bình
- 1 ngày 19 giờ
- Pull request đã merge (30 ngày)
- 5
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của devcontainers/features
-
Terraform feature fails to install due to tflint checksumCó thể đã có người làm @v-Kaniska244 đã nhận 4 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
devcontainers/features#1757 · 1 bình luận · 1 reaction · 2 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 52/100
devcontainers/features#1764 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Can the onCreateCommand be run background and use the Output tab instead of the Terminal tab?Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 38/100
devcontainers/features#1754 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
use mise for java feature installation or make sdkman configuable to download JDK from mirrosĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
devcontainers/features#1730 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Version resolution over unauthenticated git makes feature installs fail hard, with no fallbackCó thể đã có người làm @v-Kaniska244 đã nhận 57 ngày trước. Đang mở
devcontainers/features#1726 · 2 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của devcontainers/features
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Graphify-Labs/graphify#4977 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
-
lesson-submission pending-review
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Ikalus1988/MisakaNet#4393 ·
Maintainer thường phản hồi trong vòng 1 ngày