-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
exec: allow running commands from host volume #14851
Conversation
2908d76
to
a46c91d
Compare
Would close #11781 |
Is the only reason for this to "speed up" errors due to the executable not being reachable to pre-chroot instead of post? Seems like just letting the command fail to execute in the chroot would fix this surprising behavior and simplify code at the cost of "slower" task failures? If we used an overlayfs we could remove that slowdown for the failure case as well as the happy path. |
It turns out we also set the executable bit on the command binary for the user. For the |
a46c91d
to
54046a1
Compare
2e3735c
to
52926d9
Compare
The exec driver and other drivers derived from the shared executor check the path of the command before handing off to libcontainer to ensure that the command doesn't escape the sandbox. But we don't check any host volume mounts, which should be safe to use as a source for executables if we're letting the user mount them to the container in the first place. Check the mount config to verify the executable lives in the mount's host path, but then return an absolute path within the mount's task path so that we can hand that off to libcontainer to run. Includes a good bit of refactoring here because the anchoring of the final task path has different code paths for inside the task dir vs inside a mount. But I've fleshed out the test coverage of this a good bit to ensure we haven't created any regressions in the process.
52926d9
to
e4fc11e
Compare
path := absPath | ||
|
||
// Ensure that the path is contained in the chroot, and find it relative to the container | ||
rel, err := filepath.Rel(command.TaskDir, path) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to determine relative path base=%q target=%q: %v", command.TaskDir, path, err) | ||
} | ||
|
||
// Turn relative-to-chroot path into absolute path to avoid | ||
// libcontainer trying to resolve the binary using $PATH. | ||
// Do *not* use filepath.Join as it will translate ".."s returned by | ||
// filepath.Rel. Prepending "/" will cause the path to be rooted in the | ||
// chroot which is the desired behavior. | ||
path = "/" + rel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for reviewers: this has been folded in to lookupTaskBin
's inner functions. The alternative was to have lookupTaskBin
pass back flags to the caller about where it got the paths, and that was all pretty gross.
// lookPathIn looks for a file with PATH inside the directory root. Like exec.LookPath | ||
func lookPathIn(path string, root string, bin string) (string, error) { | ||
// exec.LookPath(file string) | ||
for _, dir := range filepath.SplitList(path) { | ||
if dir == "" { | ||
// match unix shell behavior, empty path element == . | ||
dir = "." | ||
} | ||
path := filepath.Join(root, dir, bin) | ||
f, err := os.Stat(path) | ||
if err != nil { | ||
continue | ||
} | ||
if m := f.Mode(); !m.IsDir() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for reviewers: this was copied almost verbatim out of the stdlib but passed a mock PATH. But since we control the mock path there's no need to do any PATH parsing and we can just iterate over a known set of directories instead. That lets us use getPathInTaskDir
for searching the task dir, the local dir, and these paths rather than having special cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM; see thoughts
// identical for binaries that don't come from mounts). | ||
// | ||
// See also executor.lookupBin for a version used by non-isolated drivers. | ||
func lookupTaskBin(command *ExecCommand) (string, string, error) { | ||
taskDir := command.TaskDir | ||
bin := command.Cmd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it make sense to search task/local/
and task/
if the bin
is given as absolute? don't think i've ever tried that, but it sounds confusing if it works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does sound confusing, and I spent much of yesterday puzzled over that! But it's the existing behavior (ref executor_linux.go#L814-L819
) so changing it now will break backwards compatibility. Let's save changing it for exec
v2.
I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions. |
Fixes #11781
The exec driver and other drivers derived from the shared executor check the path of the command before handing off to libcontainer to ensure that the command doesn't escape the sandbox. But we don't check any host volume mounts, which should be safe to use as a source for executables if we're letting the user mount them to the container in the first place.
Check the mount config to verify the executable lives in the mount's host path, but then return an absolute path within the mount's task path so that we can hand that off to libcontainer to run.
Includes a good bit of refactoring here because the anchoring of the final task path has different code paths for inside the task dir vs inside a mount. But I've fleshed out the test coverage of this a good bit to ensure we haven't created any regressions in the process.
Longer-term with some sort of
exec
v2 I'd like to switch all of this to an overlay FS, but we'll need a new FSIsolation mode so as not to break any 3rd-party drivers relying on the existing alloc dir behavior.Example jobspec that this enables, assuming a binary named
hello
at the host volume namedshared_data
: