Repository navigation
dockivad: remove, copy and archive trees with 0555 and 000 directories - #36
Merged
Merged
Conversation
The engine is the Mac user, not root, so a guest's chmod 555 or chmod 000 of
a directory (Go's module cache is 0555) is enforced against the engine too.
With such a directory holding files:
- docker rmi removed image.json, then failed on the image's files ("unlinkat
...: permission denied"). The files stayed, and every docker images after
it answered "Host operation failed" (lstat image.json).
- docker commit failed: "mkdir <store>/.commit-*/rootfs/...: permission
denied". Builds left their work directories behind for the same reason.
- docker cp and docker export of a tree with a 000 directory or file sent a
truncated tar.
restricted.go has what the engine now does everywhere it removes, copies or
archives guest files: the directory or file gets the owner bits it lacks for
as long as they are needed, and its own mode back after, so what is stored and
exported keeps the guest's modes.
- Removal: every removal of guest files is removeTree. An image's directory
leaves the cache in one rename (.removing-*) before its files go, so the
image is listed whole or not at all; what a failed removal leaves goes at
the next start. A cache directory an earlier engine left without its
image.json is skipped by docker images and docker rmi, and removed at start.
- Copies: cloneTree unlocks an unreadable tree for clonefile, which copies a
tree whole or not at all (EACCES), and locks source and copy again.
applyUpper (commit) keeps the directories it fills writable and sets their
modes last. Layer diffs, the build cache's hash and size, and build exports
walk with walkTree and read through 000 directories of the base.
- Archives: docker cp out, docker export and docker diff read 000 directories
and files. docker cp into a container, the engine's own files and mount
points, and a build's COPY write into a 0555 directory as Docker's root does.
Not changed: a path given to docker cp that itself runs through a 000
directory is still "not found"; unpacked image layers and populated volumes
keep the owner bits the engine adds; bind mounts are read as they are.
restricted_test.go: docker rmi of an image with such directories and docker
images beside a half-removed one; a commit's copy and its layer; docker cp
out, docker export and docker cp in. All three fail on main.
A build's work directory stayed in <store>/build whenever one of its stages held a directory without write permission (os.RemoveAll in the background, its error dropped): 21 were left on an installed 0.6.10, and 5 more in <home>/build from before the store. The previous commit stops new ones; nothing removed the old. At a start, before the API serves anything but /_ping, staleWork renames aside what an engine that has gone left in the store, and in the home before it: build, load, .commit-* and what an earlier sweep did not finish (.removing-*). They are removed in the background with removeTree, so a start does not wait for gigabytes to go; what an interrupted removal leaves goes at the next start. TestStaleWorkRestrictedDirs: such leftovers with 0555 and 000 directories go, images, containers, the build cache and volumes stay.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
The engine runs as the Mac user, not root, so a guest's
chmod 555orchmod 000of a directory is enforced againstdockivadtoo. Go's module cache is made of 0555 directories, and #34 lets guest root make more of them. With such a directory holding files (engineaf13b2b):docker rmiof an image with a non-empty 0555 directoryunlinkat …: permission denied;image.jsongone, files left; every laterdocker imagesanswersHost operation faileddocker imagesworksdocker commitof a container with nested 0555 directoriesmkdir <store>/.commit-*/rootfs/root/a/d: permission denieddocker cp c:/dir -,docker exportwith a 000 directory or filedocker build<store>/buildwhen a stage held a 0555 directory (21 left on my installed 0.6.10)The fix
dockivad/restricted.goholds the helpers. Where the engine removes, copies or archives guest files, the directory or file gets the owner bits it lacks for as long as they are needed and its own mode back after, so what is stored and exported keeps the guest's modes.Removal. Every removal of guest files goes through
removeTree. An image directory leaves the cache in one rename (.removing-*) before its files go: the image is listed whole or not at all, and what a failed removal leaves goes at the next start. A cache directory left withoutimage.jsonby an earlier engine is skipped bydocker imagesanddocker rmi, and removed at start.Copies.
clonefilecopies a tree whole or not at all (EACCES on one 000 entry), socloneTreeunlocks such a tree, clones, and locks source and copy again.applyUpper(commit) keeps the directories it fills writable and sets their modes last. Layer diffs, the build cache's hash and size and build exports walk withwalkTree, and look up the base through 000 directories.Archives.
docker cpout,docker exportanddocker diffread 000 directories and files.docker cpinto a container, the engine's own files and mount points, and a build'sCOPYwrite into a 0555 directory, as Docker's root does.Leftovers. At a start, before the API serves requests, what an engine that has gone left behind is renamed aside and removed in the background:
build,loadand.commit-*in the store, andbuildandloadin the home from before the store.A running container sees the wider mode for the moment the engine holds it, as with the runtime's own override in #34.
Not changed
docker cpthat itself runs through a 000 directory is still "not found" (layers.lookup).Checks
dockivad/restricted_test.go:docker rmiof an image with such directories anddocker imagesbeside a half-removed one; a commit's copy and its layer;docker cpout,docker exportanddocker cpin (these three fail onmain); the start-up sweep of leftovers.go vet ./...andgo test ./...indockivad/pass.scripts/verify_dockivad.py: 31/31 on both layouts (default and--new-home).main's runtime and with Root changes directories and opens files whatever their mode (CAP_DAC_OVERRIDE) #34's (which is what writes files into 0555 and 000 directories): thermisequence thendocker images; the commit, with the committed image run and its file read back;docker cpanddocker exportof a tree with a 000 directory and a 000 file;docker build FROMthe committed image with aRUNand aCOPY;docker cpinto a 0555 directory;docker rm,docker rmianddocker builder pruneleaving nothing behind; a half-removed image directory is listed around and gone after a restart; plantedbuild,loadand.commit-*leftovers with 0555 directories are gone after a restart, and a build after it works.