Skip to content

Conversation

@servusdei2018
Copy link

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

cmdline := fmt.Sprintf("%v setup %v %v\n", o.runtime, o.runtimeArgs, toolkitDir)
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
cmd := exec.Command("sh", "-c", cmdline)
and
cmdline := fmt.Sprintf("%v cleanup %v %v\n", o.runtime, o.runtimeArgs, toolkitDir)
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection
cmd := exec.Command("sh", "-c", cmdline)
where the string o.runtimeArgs is directly interpolated into cmdline, leaving it susceptible to injection attacks. To address this, a more comprehensive solution would involve reimplementing o.runtimeArgs as []string, allowing for proper sanitization using the introduced oci.Escape() function; however, this likely involves a breaking change.

@servusdei2018
Copy link
Author

cc @elezar

Copy link
Member

@elezar elezar left a 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.

@servusdei2018
Copy link
Author

servusdei2018 commented Oct 13, 2024

Hmm I believe I did look at runc and couldn't find the exact logic they used. I also looked at Docker and they use shellescape internally.

@ArangoGutierrez
Copy link
Collaborator

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

This comment was marked as outdated.

@copy-pr-bot
Copy link

copy-pr-bot bot commented Jul 11, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Nate Bracy <nate@bracy.dev>
@servusdei2018
Copy link
Author

Rebased @ArangoGutierrez

This comment was marked as outdated.

Copy link

Copilot AI left a 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 EscapeArg and Escape functions with accompanying tests.
  • Updates syscall.Exec and exec.Command calls 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 EscapeArg handles all defined metacharacters.
			input:    []string{"echo", "Hello World", "and", "goodbye | cat"},

internal/oci/runtime_syscall_exec.go:65

  • [nitpick] Clarify in the docstring that Escape is intended for commands passed through a shell, not for direct use with syscall.Exec or exec.Command without 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.Exec directly, as execve bypasses the shell and does not interpret these characters. This transformation will corrupt arguments like spaces or dollar signs. Consider removing Escape here and relying on direct exec without shell escaping.
	args = Escape(args)

Copy link
Collaborator

@ArangoGutierrez ArangoGutierrez left a 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>
@github-actions
Copy link

github-actions bot commented Nov 5, 2025

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.

@github-actions github-actions bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Nov 5, 2025
@servusdei2018 servusdei2018 requested a review from Copilot November 6, 2025 03:55
Copy link

Copilot AI left a 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.
Copy link

Copilot AI Nov 6, 2025

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'.

Suggested change
// metacharRegex matches any shell metacharcter.
// metacharRegex matches any shell metacharacter.

Copilot uses AI. Check for mistakes.
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)
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
args = Escape(args)
// Do not escape arguments; syscall.Exec passes them as-is to the new process.

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Nov 6, 2025

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.

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Nov 6, 2025

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.

Copilot uses AI. Check for mistakes.
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
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
return syscall.Exec(path, oci.Escape(args), envv) //nolint:gosec
return syscall.Exec(path, args, envv) //nolint:gosec

Copilot uses AI. Check for mistakes.
//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"})...)
Copy link

Copilot AI Nov 6, 2025

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.

Copilot uses AI. Check for mistakes.
//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"})...)
Copy link

Copilot AI Nov 6, 2025

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.

Copilot uses AI. Check for mistakes.
//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"})...)
Copy link

Copilot AI Nov 6, 2025

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.

Copilot uses AI. Check for mistakes.
//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()})...)
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
cmd := exec.Command("cp", oci.Escape([]string{c.unmodifiedSpecFile(), c.specFilePath()})...)
cmd := exec.Command("cp", c.unmodifiedSpecFile(), c.specFilePath())

Copilot uses AI. Check for mistakes.
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)
Copy link

Copilot AI Nov 6, 2025

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.

Suggested change
args = oci.Escape(args)

Copilot uses AI. Check for mistakes.
@github-actions github-actions bot removed the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Nov 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants