docker exec: fail early on exec create if specified user doesn't exist

Before this patch, and error would be produced when starting the exec,
but the CLI would wait for the exec to complete, timing out after 10
seconds (default). With this change, an error is returned immediately
when creating the exec.

Note that "technically" this check may have some TOCTOU issues, because
'/etc/passwd' and '/etc/groups' may be mutated by the container in between
creating the exec and starting it.

This is very likely a corner-case, but something we can consider changing
in future (either allow creating an invalid exec, and checking before
starting, or checking both before create and before start).

With this patch:

    printf 'FROM alpine\nRUN rm -f /etc/group' | docker build -t nogroup -
    ID=$(docker run -dit nogroup)

    time docker exec -u 0:root $ID echo hello
    Error response from daemon: unable to find group root: no matching entries in group file

    real	0m0.014s
    user	0m0.010s
    sys	0m0.003s

    # numericc uid/gid (should not require lookup);
    time docker exec -u 0:0 $ID echo hello
    hello

    real	0m0.059s
    user	0m0.007s
    sys	0m0.008s

    # no user specified (should not require lookup);
    time docker exec $ID echo hello
    hello

    real	0m0.057s
    user	0m0.013s
    sys	0m0.008s

    docker rm -fv $ID

    # container that does have a valid /etc/groups

    ID=$(docker run -dit alpine)
    time docker exec -u 0:root $ID echo hello
    hello

    real	0m0.063s
    user	0m0.010s
    sys	0m0.009s

    # non-existing user or group
    time docker exec -u 0:blabla $ID echo hello
    Error response from daemon: unable to find group blabla: no matching entries in group file

    real	0m0.013s
    user	0m0.004s
    sys	0m0.009s

    docker rm -fv $ID

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-04-25 14:29:41 +02:00
parent 82fc83ec41
commit b54a038bec
3 changed files with 143 additions and 4 deletions

View File

@@ -98,6 +98,22 @@ func (daemon *Daemon) ContainerExecCreate(name string, options *containertypes.E
if err != nil {
return "", err
}
if user := options.User; user != "" {
// Lookup the user inside the container before starting the exec to
// allow for an early exit.
//
// Note that "technically" this check may have some TOCTOU issues,
// because '/etc/passwd' and '/etc/groups' may be mutated by the
// container in between creating the exec and starting it.
//
// This is very likely a corner-case, but something we can consider
// changing in future (either allow creating an invalid exec, and
// checking before starting, or checking both before create and
// before start).
if _, err := getUser(cntr, user); err != nil {
return "", errdefs.InvalidParameter(err)
}
}
keys := []byte{}
if options.DetachKeys != "" {