-
Notifications
You must be signed in to change notification settings - Fork 285
Fix slight cmdline parsing issue for job container/misc cleanup #1113
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| package jobcontainers | ||
|
|
||
| import ( | ||
| "io/ioutil" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
@@ -31,52 +33,159 @@ func TestSearchPath(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| func TestGetApplicationName(t *testing.T) { | ||
| func TestGetApplicationNamePing(t *testing.T) { | ||
| expected := "C:\\WINDOWS\\system32\\ping.exe" | ||
|
|
||
| cwd, err := os.Getwd() | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| path, _, err := getApplicationName("ping", cwd, os.Getenv("PATH")) | ||
| pathEnv := os.Getenv("PATH") | ||
|
|
||
| path, _, err := getApplicationName("ping", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
|
||
| path, _, err = getApplicationName("./ping", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
dcantah marked this conversation as resolved.
|
||
|
|
||
| path, _, err = getApplicationName("./ping", cwd, os.Getenv("PATH")) | ||
| path, _, err = getApplicationName(".\\ping", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
|
||
| path, _, err = getApplicationName(".\\ping", cwd, os.Getenv("PATH")) | ||
| // Test relative path with different cwd | ||
| newCwd := `C:\Windows\` | ||
| _, _, err = getApplicationName("./system32/ping", newCwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
|
||
| pingWithCmd := "cmd /c ping 127.0.0.1" | ||
| path, cmdLine, err := getApplicationName("cmd /c ping 127.0.0.1", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, cmdLine, pingWithCmd) | ||
| assertStr(t, "C:\\windows\\system32\\cmd.exe", path) | ||
|
|
||
| // Test that we only find the first element of the commandline if the binary exists. | ||
| path, _, err = getApplicationName("ping test", cwd, os.Getenv("PATH")) | ||
| path, _, err = getApplicationName("ping test", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
|
||
| // Test quoted application name with an argument afterwards. | ||
| path, cmdLine, err := getApplicationName("\"ping\" 127.0.0.1", cwd, os.Getenv("PATH")) | ||
| path, cmdLine, err = getApplicationName("\"ping\" 127.0.0.1", cwd, pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, expected, path) | ||
|
|
||
| args := splitArgs(cmdLine) | ||
| cmd := &exec.Cmd{ | ||
| Path: path, | ||
| Args: args, | ||
| Args: splitArgs(cmdLine), | ||
| } | ||
| if err := cmd.Run(); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| func TestGetApplicationNameRandomBinary(t *testing.T) { | ||
| pathEnv := os.Getenv("PATH") | ||
|
dcantah marked this conversation as resolved.
|
||
|
|
||
| tempDir, err := ioutil.TempDir("", "") | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| defer os.RemoveAll(tempDir) | ||
|
|
||
| // Create fake executables in a temporary directory to use for the below tests. | ||
| testExe := filepath.Join(tempDir, "test.exe") | ||
| _, err = os.Create(testExe) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| test2Exe := filepath.Join(tempDir, "test 2.exe") | ||
| _, err = os.Create(test2Exe) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| exeWithSpace := filepath.Join(tempDir, "exe with space.exe") | ||
| _, err = os.Create(exeWithSpace) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| // See if we can successfully find "exe with space.exe" with no quoting, it should first try "exe.exe", then "exe with.exe" and then finally | ||
| // "exe with space.exe" | ||
| path, _, err := getApplicationName("exe with space.exe", filepath.Dir(testExe), os.Getenv("PATH")) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we turn all these test cases into a loop over structs with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, they're getting a bit unwieldy.. |
||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, exeWithSpace, path) | ||
|
|
||
| // See if we can successfully find "exe with space.exe" with quoting, it should try "exe with space.exe" only. | ||
| path, _, err = getApplicationName("\"exe with space.exe\"", filepath.Dir(testExe), os.Getenv("PATH")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, exeWithSpace, path) | ||
|
|
||
| // Try a quoted commandline, so that we find the actual "C:\rest\of\the\path\test 2.exe" binary | ||
| path, _, err = getApplicationName("\"test 2.exe\"", filepath.Dir(test2Exe), os.Getenv("PATH")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, test2Exe, path) | ||
|
|
||
| // We should find the test.exe binary, and the 2 will be treated as an argument in this case | ||
| path, _, err = getApplicationName("test 2", filepath.Dir(test2Exe), os.Getenv("PATH")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, testExe, path) | ||
|
|
||
| // Test relative path with the current working directory set to the directory that contains the binary. | ||
| path, _, err = getApplicationName("./test.exe", filepath.Dir(testExe), pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, testExe, path) | ||
|
|
||
| // Test relative path with backslashes with the current working directory set to the directory that contains the binary. | ||
| path, _, err = getApplicationName(".\\test.exe", filepath.Dir(testExe), pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, testExe, path) | ||
|
|
||
| // Test no file extension | ||
| path, _, err = getApplicationName(testExe[0:len(testExe)-4], filepath.Dir(testExe), pathEnv) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, testExe, path) | ||
|
|
||
| // Add test binary path to PATH and try to find it by just 'test.exe' | ||
| if err := os.Setenv("PATH", os.Getenv("PATH")+filepath.Dir(testExe)); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| path, _, err = getApplicationName("test.exe", filepath.Dir(testExe), os.Getenv("PATH")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| assertStr(t, testExe, path) | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,21 +92,30 @@ func (p *JobProcess) Signal(ctx context.Context, options interface{}) (bool, err | |
| func (p *JobProcess) CloseStdin(ctx context.Context) error { | ||
| p.stdioLock.Lock() | ||
| defer p.stdioLock.Unlock() | ||
| return p.stdin.Close() | ||
| if p.stdin != nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move this to a separate PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| return p.stdin.Close() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // CloseStdout closes the stdout pipe of the process. | ||
| func (p *JobProcess) CloseStdout(ctx context.Context) error { | ||
| p.stdioLock.Lock() | ||
| defer p.stdioLock.Unlock() | ||
| return p.stdout.Close() | ||
| if p.stdout != nil { | ||
| return p.stdout.Close() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // CloseStderr closes the stderr pipe of the process. | ||
| func (p *JobProcess) CloseStderr(ctx context.Context) error { | ||
| p.stdioLock.Lock() | ||
| defer p.stdioLock.Unlock() | ||
| return p.stderr.Close() | ||
| if p.stderr != nil { | ||
| return p.stderr.Close() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Wait waits for the process to exit. If the process has already exited returns | ||
|
|
@@ -217,7 +226,9 @@ func signalProcess(pid uint32, signal int) error { | |
| if err != nil { | ||
| return errors.Wrap(err, "failed to open process") | ||
| } | ||
| defer windows.Close(hProc) | ||
| defer func() { | ||
| _ = windows.Close(hProc) | ||
| }() | ||
|
|
||
| // We can't use GenerateConsoleCtrlEvent since that only supports CTRL_C_EVENT and CTRL_BREAK_EVENT. | ||
| // Instead, to handle an arbitrary signal we open a CtrlRoutine thread inside the target process and | ||
|
|
@@ -231,7 +242,9 @@ func signalProcess(pid uint32, signal int) error { | |
| if err != nil { | ||
| return errors.Wrap(err, "failed to load kernel32 library") | ||
| } | ||
| defer windows.Close(k32) | ||
| defer func() { | ||
| _ = windows.FreeLibrary(k32) | ||
| }() | ||
|
|
||
| proc, err := windows.GetProcAddress(k32, "CtrlRoutine") | ||
| if err != nil { | ||
|
|
@@ -242,6 +255,8 @@ func signalProcess(pid uint32, signal int) error { | |
| if err != nil { | ||
| return errors.Wrapf(err, "failed to open remote thread in target process %d", pid) | ||
| } | ||
| defer windows.Close(threadHandle) | ||
| defer func() { | ||
| _ = windows.Close(threadHandle) | ||
| }() | ||
| return nil | ||
| } | ||
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.
We can't rely on
filepath.Cleanhere as it will simply replace./ping->ping, since it assumes the path will be evaluated relative to the working directory, rather than searched for in a list. See this comment as well.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.
Probably just need to check for
./and replace it withworkingDirectory.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.
For a relative path without a dot (e.g.
pingorfoo/ping), we will need to determine the correct behavior. We should understand what the default Windows Server container behavior and CreateProcess behavior is in this case.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.
Good point
Check for only a first occurrence or to also handle your mention of "././" or ".." that you made in another comment? I feel like if Windows Server containers don't handle even the first case that might be fine to get away with. "././mybin.exe" seems like an odd command line to provide anyways. "../mybin.exe", "../../mybin.exe" however seems like it actually has its uses, but now we're back to your comment of writing custom parsing code
Uh oh!
There was an error while loading. Please reload this page.
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.
I think we should be pretty inline with the Windows Server container behavior. It seemed like everything was behaving the same before the relative path with dot discussion was brought up and the
filepath.Cleanchange.. afaik cexecsvc tries to mirror theCreateProcessbehavior and we tried to mirror the cexecsvc behavior, so they should be essentially the same before this work in regards to how they handle relative paths with no dot. Granted I'd have to go checkCreateProcess's logic to see just how similar cexec and it actually are.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.
I think this all comes back to whether we want to support relative paths or not. The answer to that question will inform our approach here. Does the host process KEP say anything about relative paths?
Uh oh!
There was an error while loading. Please reload this page.
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.
Not that I could find. There's a mention that to access container mounts you could use a relative path (as they're symlinked to the container volume), but nothing regarding the cmdline. https://github.com/kubernetes/enhancements/blob/master/keps/sig-windows/1981-windows-privileged-container-support/README.md#container-mounts
Uh oh!
There was an error while loading. Please reload this page.
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.
Ok after syncing with James, we didn't call out anywhere that relative paths with a dot would work for these cmdline wise, and they don't work already for ws contains so I'm just going to omit this. I'll change this PR to updating the tests, and a small fix for the environment variable replacement logic