-
Notifications
You must be signed in to change notification settings - Fork 430
feat: Introduce shell metacharacter escaping for exec #491
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
base: main
Are you sure you want to change the base?
Conversation
|
cc @elezar |
elezar
left a comment
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.
My one question would be whether the logic that we're implementing here exists in some third-pary package somewhere. I'm sure that runc for example needs to also check the arguments that are passed to its hooks.
|
Hmm I believe I did look at |
|
Hey @servusdei2018, let's revise this valuable contribution. Recently, we have done a lot of hardening work, so it's worth revisiting your contribution, starting with a rebase |
Signed-off-by: Nate Bracy <nate@bracy.dev>
|
Rebased @ArangoGutierrez |
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.
Pull Request Overview
This PR introduces functions to escape shell metacharacters in command-line arguments and updates various exec invocations to use these escaping utilities to mitigate injection risks.
- Adds
EscapeArgandEscapefunctions with accompanying tests. - Updates
syscall.Execandexec.Commandcalls across multiple packages to use escaped arguments. - Includes a new test suite for argument escaping behavior.
Reviewed Changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/oci/runtime_syscall_exec.go | Implements EscapeArg and Escape; applies escaping in Exec |
| internal/oci/runtime_syscall_exec_test.go | Adds tests for the Escape function |
| internal/ldconfig/safe-exec_other.go | Applies oci.Escape to syscall.Exec in non-Linux SafeExec |
| internal/ldconfig/safe-exec_linux.go | Applies oci.Escape to both branches of SafeExec on Linux |
| cmd/nvidia-ctk-installer/.../container.go | Uses oci.Escape before building exec.Command |
| cmd/nvidia-container-runtime/main_test.go | Updates tests to use oci.EscapeArg and oci.Escape |
| cmd/nvidia-container-runtime-hook/main.go | Applies oci.Escape before syscall.Exec in hook logic |
Comments suppressed due to low confidence (3)
internal/oci/runtime_syscall_exec_test.go:42
- Current tests cover spaces and pipes but miss other metacharacters like backticks, dollar signs, semicolons, quotes, and newlines. Add cases for those to ensure
EscapeArghandles all defined metacharacters.
input: []string{"echo", "Hello World", "and", "goodbye | cat"},
internal/oci/runtime_syscall_exec.go:65
- [nitpick] Clarify in the docstring that
Escapeis intended for commands passed through a shell, not for direct use withsyscall.Execorexec.Commandwithout a shell, to avoid confusion about its purpose.
// Escape escapes shell metacharacters in a slice of command-line arguments
internal/oci/runtime_syscall_exec.go:42
- Escaping shell metacharacters is unnecessary when calling
syscall.Execdirectly, as execve bypasses the shell and does not interpret these characters. This transformation will corrupt arguments like spaces or dollar signs. Consider removingEscapehere and relying on direct exec without shell escaping.
args = Escape(args)
ArangoGutierrez
left a comment
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.
Can you prepend to the commits that start with nit a prefix [no-relnote] , thanks
Signed-off-by: Nate Bracy <nate@bracy.dev>
Signed-off-by: Nate Bracy <nate@bracy.dev>
…kage Signed-off-by: Nate Bracy <nate@bracy.dev>
Signed-off-by: Nate Bracy <nate@bracy.dev>
Signed-off-by: Nate Bracy <nate@bracy.dev>
|
This PR is stale because it has been open 90 days with no activity. This PR will be closed in 30 days unless new comments are made or the stale label is removed. To skip these checks, apply the "lifecycle/frozen" label. |
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.
Pull Request Overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated 12 comments.
| // These metacharacters include: | & ; ( ) < > \t \n $ \ ` | ||
| const shellMetachars = "|&;()<> \t\n$\\`'\"" | ||
|
|
||
| // metacharRegex matches any shell metacharcter. |
Copilot
AI
Nov 6, 2025
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.
Corrected spelling of 'metacharcter' to 'metacharacter'.
| // metacharRegex matches any shell metacharcter. | |
| // metacharRegex matches any shell metacharacter. |
| func (r syscallExec) Exec(args []string) error { | ||
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| err := syscall.Exec(args[0], args, os.Environ()) | ||
| args = Escape(args) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to syscall.Exec is incorrect. The syscall.Exec function directly invokes the execve system call, which does not interpret shell metacharacters - it passes arguments as-is to the new process. Adding backslash escaping will cause the literal backslashes to be passed to the program, breaking argument parsing. Shell escaping is only needed when passing commands through a shell interpreter (e.g., /bin/sh -c). Remove the Escape call here.
| args = Escape(args) | |
| // Do not escape arguments; syscall.Exec passes them as-is to the new process. |
| if err != nil { | ||
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| return syscall.Exec(path, args, envv) | ||
| return syscall.Exec(path, oci.Escape(args), envv) //nolint:gosec |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to syscall.Exec is incorrect. The syscall.Exec function directly invokes the execve system call without shell interpretation, so shell metacharacter escaping will cause literal backslashes to be passed to the program. Remove the oci.Escape call.
| exePath := "/proc/self/fd/" + strconv.Itoa(int(safeExe.Fd())) | ||
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| return syscall.Exec(exePath, args, envv) | ||
| return syscall.Exec(exePath, oci.Escape(args), envv) //nolint:gosec |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to syscall.Exec is incorrect. The syscall.Exec function directly invokes the execve system call without shell interpretation, so shell metacharacter escaping will cause literal backslashes to be passed to the program. Remove the oci.Escape call.
| func SafeExec(path string, args []string, envv []string) error { | ||
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| return syscall.Exec(path, args, envv) | ||
| return syscall.Exec(path, oci.Escape(args), envv) //nolint:gosec |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to syscall.Exec is incorrect. The syscall.Exec function directly invokes the execve system call without shell interpretation, so shell metacharacter escaping will cause literal backslashes to be passed to the program. Remove the oci.Escape call.
| return syscall.Exec(path, oci.Escape(args), envv) //nolint:gosec | |
| return syscall.Exec(path, args, envv) //nolint:gosec |
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| cmdRun := exec.Command(nvidiaRuntime, "run", "--bundle", cfg.bundlePath(), "testcontainer") | ||
| //nolint:gosec | ||
| cmdRun := exec.Command(oci.Escape([]string{nvidiaRuntime})[0], oci.Escape([]string{"run", "--bundle", cfg.bundlePath(), "testcontainer"})...) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to exec.Command is incorrect. The exec.Command function executes binaries directly without shell interpretation. The escaped strings will include literal backslashes, breaking the command. Remove the oci.Escape calls.
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer") | ||
| //nolint:gosec | ||
| cmdCreate := exec.Command(oci.Escape([]string{nvidiaRuntime})[0], oci.Escape([]string{"create", "--bundle", cfg.bundlePath(), "testcontainer"})...) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to exec.Command is incorrect. The exec.Command function executes binaries directly without shell interpretation. The escaped strings will include literal backslashes, breaking the command. Remove the oci.Escape calls.
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| cmdCreate := exec.Command(nvidiaRuntime, "create", "--bundle", cfg.bundlePath(), "testcontainer") | ||
| //nolint:gosec | ||
| cmdCreate := exec.Command(oci.Escape([]string{nvidiaRuntime})[0], oci.Escape([]string{"create", "--bundle", cfg.bundlePath(), "testcontainer"})...) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to exec.Command is incorrect. The exec.Command function executes binaries directly without shell interpretation. The escaped strings will include literal backslashes, breaking the command. Remove the oci.Escape calls.
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection | ||
| cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath()) | ||
| //nolint:gosec | ||
| cmd := exec.Command("cp", oci.Escape([]string{c.unmodifiedSpecFile(), c.specFilePath()})...) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to exec.Command is incorrect. The exec.Command function executes binaries directly without shell interpretation. The escaped strings will include literal backslashes, which will be interpreted as part of the file paths, causing the cp command to fail. Remove the oci.Escape call.
| cmd := exec.Command("cp", oci.Escape([]string{c.unmodifiedSpecFile(), c.specFilePath()})...) | |
| cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath()) |
| env := append(os.Environ(), cli.Environment...) | ||
| //nolint:gosec // TODO: Can we harden this so that there is less risk of command injection? | ||
| err = syscall.Exec(args[0], args, env) | ||
| args = oci.Escape(args) |
Copilot
AI
Nov 6, 2025
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.
Escaping arguments passed to syscall.Exec is incorrect. The syscall.Exec function directly invokes the execve system call without shell interpretation, so shell metacharacter escaping will cause literal backslashes to be passed to the program. Remove the oci.Escape call.
| args = oci.Escape(args) |
In order to mitigate potential security vulnerabilities arising from shell injection attacks, this PR introduces a function to escape shell metacharacters which may be present in command-line arguments.
It's worth noting that two potential vulnerabilities still exist in
nvidia-container-toolkit/tools/container/nvidia-toolkit/run.go
Lines 249 to 252 in 973a663
nvidia-container-toolkit/tools/container/nvidia-toolkit/run.go
Lines 275 to 278 in 973a663
o.runtimeArgsis directly interpolated intocmdline, leaving it susceptible to injection attacks. To address this, a more comprehensive solution would involve reimplementingo.runtimeArgsas[]string, allowing for proper sanitization using the introducedoci.Escape()function; however, this likely involves a breaking change.