January 9, 2019·5 min read·engineeringdockersecuritygolang

Docker 18.09.1 and the security boundary inside a Go binary

Docker 18.09.1 updates its Go toolchain and authorization handling. The fixes show why an Engine upgrade and an application rebuild address different parts of a container system.

A compiler security update can concern both the tools that build an executable and the library code inside the executable. Docker 18.09.1, released on January 9, brings that distinction into an otherwise routine maintenance release. Its release notes identify Go 1.10.6 as the toolchain update addressing three security issues. Those issues have different prerequisites, so treating them as one generic container vulnerability obscures what needs to change.

I value Docker for making a built environment portable between machines. A repeatable build also makes the inputs to that environment easier to identify. This release is a useful reminder that identifying those inputs includes the language implementation used to produce each executable, even when the resulting image contains very little else.

Two vulnerabilities in fetching source

The Go project disclosed the relevant fixes in December. The Go security announcement separates two problems in cmd/go from a third in crypto/x509. That division matters because a command used while fetching dependencies and a certificate verifier linked into a service have different places in the deployment process.

Go issue 29230 describes CVE-2018-16873. In GOPATH mode, an attacker could arrange a vanity import path so a repository was cloned into a directory named .git. With carefully arranged repository contents and operation ordering, go get -u could then treat the parent directory as the repository root. Git would read attacker-controlled configuration, which could cause commands to execute on the machine fetching the package. The Go report explicitly excludes module mode from this vulnerability.

The intended operation was downloading, updating and building source. Executing arbitrary commands supplied through repository configuration exceeded that operation. The distinction also explains why the problem belongs in a discussion of build infrastructure. A service does not need to execute go get -u while handling requests for its build process to have encountered this risk earlier.

Go issue 29231 describes CVE-2018-16874. Curly braces in a malicious package import path could cause directory traversal and arbitrary filesystem writes. The report again limits the affected behavior to GOPATH mode and notes that the write could lead to code execution. The common factor is a dependency name influencing operations outside the files that a source download should create.

These reports do not establish that every Docker daemon exposes either go get flaw through its API. They describe the Go commands and their prerequisites. Docker updating its build toolchain is sensible maintenance, but the release-note association alone cannot turn a build-time command vulnerability into a remotely reachable daemon exploit. Keeping that distinction explicit makes the advisory more useful to an operator deciding which executable or build environment is affected.

Certificate verification carries a runtime cost

The third issue reaches a different stage of the software lifecycle. Go issue 29233, CVE-2018-16875, explains that crypto/x509 failed to bound the work performed during certificate-chain verification. An attacker could supply pathological inputs that consumed excessive CPU. The report identifies both TLS clients verifying certificates and TLS servers accepting client certificates as affected uses.

Certificate validation receives data from a peer whose identity is still being established. An implementation must evaluate that data before it can decide whether to trust the peer. A valid rejection at the end of the process is insufficient if reaching the rejection consumes unreasonable resources. The security property therefore includes the cost of handling invalid input.

That is why this issue cannot be understood only as a compiler executable that needs replacing. The affected package is part of the code an application uses for verification. An application built with affected library code has its own remediation question, separate from the version of Docker that starts it. Conversely, a package-fetching flaw requires examining the build commands that run, rather than assuming every application built with Go invokes those commands at runtime.

My preference for a small image containing a static Go binary makes this distinction especially relevant. A short image inventory is useful, but the executable still incorporates decisions made by its compiler and libraries. The size of the final filesystem does not establish which certificate-verification implementation the application uses.

Authorization has its own input validation

Docker 18.09.1 also corrects authorization-plugin handling for zero-length content and path validation. The release notes state those corrections separately from the Go update. That separation deserves to survive in any operational description of the patch, because an authorization decision and a certificate-chain calculation are different mechanisms.

The notes do not give enough detail to derive a complete exploit narrative for the authorization changes. In particular, the brief description does not establish that every empty request bypassed policy or that every installation used an authorization plugin. Repeating either claim would manufacture a stronger advisory than Docker published. What the notes do establish is that request representation matters at the point where Docker consults authorization logic.

An authorization integration must make decisions about the request the daemon will actually interpret. Empty content and path handling are therefore substantive correctness concerns. The practical reading is to include the authorization integration in the reason for updating when that integration is present. The Go CVE list does not exhaust the security work in this patch.

The runtime service is a separate upgrade concern

The release also documents an operational consequence of the 18.09 architecture. Containerd now runs under systemd instead of being managed by the Engine daemon. Docker warns that custom mount settings on docker.service, including MountFlags=slave, can break interaction with containerd and prevent container startup. Its documented inspection command is small enough to retain here.

sudo systemctl show --property=MountFlags docker.service

Docker says a nonempty value requires updating the configuration and restarting the Docker service. This is a specific compatibility issue with the service arrangement. A daemon configuration that previously worked can no longer be judged solely against the old process hierarchy, because the process responsible for the runtime has changed.

The same release notes identify an Ubuntu upgrade problem in which moving from 18.09.0 to 18.09.1 does not upgrade containerd to the correct version. That makes the package boundary relevant alongside the language-library boundary. An Engine version alone does not describe the entire installed runtime combination.

For this release, the useful upgrade description names the Go fixes by their actual prerequisites, retains the separate authorization correction, and accounts for the containerd service arrangement. Each item concerns a different part of running an image. A rebuilt application can address its embedded verification code while the host still needs its own Engine and runtime updates.