Skip to content

child_process: validate options.shell and correctly enforce shell invocation in exec/execSync #54623

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

Closed
wants to merge 3 commits into from

Conversation

Renegade334
Copy link
Contributor

@Renegade334 Renegade334 commented Aug 28, 2024

The intention of exec and execSync is that a shell is always invoked to run the provided command, as opposed to execFile, which by default executes the target directly and does not invoke a shell. Unlike its counterparts elsewhere in child_process, exec's options.shell is documented as not accepting a boolean, as it's not intended to be possible to disable shell invocation and switch to execFile-like behaviour.

exec's options.shell is currently handled the following way:

  • passed to normalizeExecArgs()
    • if the shell parameter is not a string, it is coerced to boolean true
  • passed to normalizeSpawnArguments()
    • if non-nullish, the parameter is validated as being of type string|boolean
    • if the parameter is truthy at this point, then spawn() runs a shell; if not, it executes the target directly

The intention is clearly to force shell invocation with exec/execSync in all cases, but the current implementation has the following problems:

  • The parameter is not validated at all; instead, if it's not a string, it's silently ignored and coerced to true, which passes downstream validation in normalizeSpawnArguments(). In other words, passing an invalid value to options.shell will never raise a validation error.
  • The logic in the normalize functions combines to yield an edge case, which is when exec/execSync is called with options.shell set to the empty string:
    • the parameter is passed through unchanged by normalizeExecArgs()
    • the parameter passes validation in normalizeSpawnArguments()
    • the parameter is not truthy, so normalizeSpawnArguments() fails to set up the shell
    • the child process is spawned without a shell, with the file target set to the entire command string:
      child_process.exec('./script some-parameter', { shell: '' })
      // should invoke a shell to run "./script" and pass an argument
      // instead, attempts to execute a file called "script some-parameter"

This patch makes two simple changes. Firstly, it validates the shell option sent to exec/execSync as a string. Secondly, it coerces all falsy values to boolean true, so will never bypass shell setup. (This makes { shell: '' } equivalent to { shell: undefined }, which matches how it behaves in the other child_process functions.)

@nodejs-github-bot nodejs-github-bot added child_process Issues and PRs related to the child_process subsystem. needs-ci PRs that need a full CI run. labels Aug 28, 2024
@avivkeller
Copy link
Member

avivkeller commented Aug 28, 2024

CC @nodejs/child_process


@Renegade334 can you shorten the commit message, it's too long to land. Something like child_process: validate that `shell` is a XYZ

@Renegade334 Renegade334 force-pushed the child-process-exec-shell branch from 22673fb to ce02538 Compare August 28, 2024 23:51
@avivkeller avivkeller added the commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. label Aug 28, 2024
Copy link

codecov bot commented Aug 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.54%. Comparing base (98d4ebc) to head (79e548e).
Report is 157 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #54623   +/-   ##
=======================================
  Coverage   88.53%   88.54%           
=======================================
  Files         657      657           
  Lines      190741   190746    +5     
  Branches    36607    36605    -2     
=======================================
+ Hits       168881   168899   +18     
+ Misses      15036    15028    -8     
+ Partials     6824     6819    -5     
Files with missing lines Coverage Δ
lib/child_process.js 97.74% <100.00%> (+0.01%) ⬆️

... and 41 files with indirect coverage changes

@Renegade334 Renegade334 force-pushed the child-process-exec-shell branch from ce02538 to 2288c07 Compare August 29, 2024 07:14
@jasnell jasnell added the request-ci Add this label to start a Jenkins CI on a PR. label Sep 8, 2024
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 8, 2024
@nodejs-github-bot
Copy link
Collaborator

@Renegade334 Renegade334 force-pushed the child-process-exec-shell branch from 3cf56e3 to f61299e Compare September 8, 2024 15:07
@jasnell
Copy link
Member

jasnell commented Sep 8, 2024

PR is currently blocked from landing due to unreliable CI. Looks like the OSX runner is jammed up.

@Renegade334
Copy link
Contributor Author

@jasnell could this go back to request-ci, now that OS X is more stable?

- narrow validation type to string (previously de facto not validated)
- ensure empty string is coerced to true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
child_process Issues and PRs related to the child_process subsystem. commit-queue-squash Add this label to instruct the Commit Queue to squash all the PR commits into the first one. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants