Adds Rubocop rule to detect calls to old cmd_exec API

This commit is contained in:
cgranleese-r7
2025-11-13 15:49:20 +00:00
parent 871ac7db61
commit 7722d19ca3
3 changed files with 154 additions and 0 deletions
+7
View File
@@ -24,6 +24,7 @@ require:
- ./lib/rubocop/cop/lint/module_enforce_notes.rb
- ./lib/rubocop/cop/lint/detect_invalid_pack_directives.rb
- ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb
- ./lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb
Layout/SpaceBeforeBrackets:
Enabled: true
@@ -676,3 +677,9 @@ Style/UnpackFirst:
Lint/DetectMetadataTrailingLeadingWhitespace:
Enabled: true
Lint/DetectOutdatedCmdExecApi:
Description: >-
Detects outdated usage of cmd_exec with separate arguments.
Use `create_process(executable, args: [], time_out: 15, opts: {})` API with an args array instead.
Enabled: true
@@ -0,0 +1,66 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Lint
# Detects outdated usage of the `cmd_exec` API where arguments are passed as a second parameter.
# The modern API is `create_process(executable, args: [])` which properly handles argument arrays.
#
# `cmd_exec` should only be used with a single static command string. When you need to pass
# arguments or construct commands dynamically, use `create_process` instead.
#
# @example
# # bad - outdated API with args parameter
# cmd_exec('cmd.exe', '/c echo hello')
# cmd_exec(binary, args, timeout)
# cmd_exec("ls", "-la /tmp")
#
# # good - static command strings
# cmd_exec('id -u')
# cmd_exec('hostname')
# cmd_exec("echo $PPID")
#
# # good - modern API with args array
# create_process('cmd.exe', args: ['/c', 'echo', 'hello'])
# create_process(binary, args: args_array, time_out: timeout)
class DetectOutdatedCmdExecApi < Base
MSG = 'Do not use cmd_exec with separate arguments. ' \
"Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`"
# Called for every method in the code
# Checks if it's a cmd_exec call with separate arguments and registers an offense if so
# @param node [RuboCop::AST::SendNode] The method call node being checked
def on_send(node)
return unless cmd_exec_with_args?(node)
add_offense(node, message: MSG)
end
private
# Check if this is a cmd_exec call with a second argument (args parameter)
# @param node [RuboCop::AST::SendNode]
# @return [Boolean]
def cmd_exec_with_args?(node)
return false unless node.method_name == :cmd_exec
# cmd_exec with 2 or more arguments (cmd, args, ...) is outdated
# cmd_exec with 1 argument (just the command) is acceptable
node.arguments.length >= 2 && !nil_second_arg?(node)
end
# Check if the second argument is explicitly nil
# cmd_exec(cmd, nil, timeout) might be used to skip args but set timeout
# @param node [RuboCop::AST::SendNode]
# @return [Boolean]
def nil_second_arg?(node)
return false if node.arguments.length < 2
second_arg = node.arguments[1]
# Use nil_type? to check if the node represents a nil literal in the code (e.g., `nil`)
second_arg.nil_type?
end
end
end
end
end
@@ -0,0 +1,81 @@
# frozen_string_literal: true
require 'rubocop/cop/lint/detect_outdated_cmd_exec_api'
require 'rubocop/rspec/support'
RSpec.describe RuboCop::Cop::Lint::DetectOutdatedCmdExecApi, :config do
subject(:cop) { described_class.new(config) }
let(:config) { RuboCop::Config.new }
it 'registers an offense when cmd_exec is called with separate arguments' do
expect_offense(<<~RUBY)
cmd_exec('cmd.exe', '/c echo hello')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`
RUBY
end
it 'registers an offense when cmd_exec is called with variable arguments' do
expect_offense(<<~RUBY)
cmd_exec(binary, args, timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`
RUBY
end
it 'registers an offense when cmd_exec is called with command and args string' do
expect_offense(<<~RUBY)
cmd_exec("ls", "-la /tmp")
^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`
RUBY
end
it 'registers an offense when cmd_exec is called with command and timeout without explicit nil' do
expect_offense(<<~RUBY)
cmd_exec('cmd.exe', "/c \#{rasdial_cmd}", 60)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Lint/DetectOutdatedCmdExecApi: Do not use cmd_exec with separate arguments. Use create_process with an args array instead use: `create_process(executable, args: [], time_out: 15, opts: {})`
RUBY
end
it 'does not register an offense when cmd_exec is called with a single static command' do
expect_no_offenses(<<~RUBY)
cmd_exec('id -u')
RUBY
end
it 'does not register an offense when cmd_exec is called with a single command string' do
expect_no_offenses(<<~RUBY)
cmd_exec('hostname')
RUBY
end
it 'does not register an offense when cmd_exec is called with a single interpolated command' do
expect_no_offenses(<<~RUBY)
cmd_exec("echo $PPID")
RUBY
end
it 'does not register an offense when cmd_exec is called with nil as second argument' do
expect_no_offenses(<<~RUBY)
cmd_exec(cmd, nil, timeout)
RUBY
end
it 'does not register an offense when cmd_exec is called with explicit nil args and timeout' do
expect_no_offenses(<<~RUBY)
cmd_exec("./\#{exploit_name} \#{arg}", nil, timeout)
RUBY
end
it 'does not register an offense for create_process calls' do
expect_no_offenses(<<~RUBY)
create_process('cmd.exe', args: ['/c', 'echo', 'hello'])
RUBY
end
it 'does not register an offense for create_process with variable args' do
expect_no_offenses(<<~RUBY)
create_process(binary, args: args_array, time_out: timeout)
RUBY
end
end