Skip to content

Commit 3e669ca

Browse files
committed
(PUP-11772) Resolve Security/Eval
Both actions and functions/data types already define arbitrary code and are loaded from trusted locations, so using eval isn't any worse. I updated the ActionBuilder to delegate specific methods to the action. For example, if an action calls the DSL method `summary "something"`, then the ActionBuilder will call the corresponding setter on the Action, e.g. Action#summary = "something". The Action code is bit more complicated because the arity of the block passed to `when_invoked=` may be 0, positive or negative, depending on whether it accepts optional arguments. Since we don't support Ruby 1.8 - 2.6, it could be improved in the future to not call `eval`, but I didn't feel like bothering. (cherry picked from commit 1e4316b)
1 parent 0b2adc0 commit 3e669ca

File tree

6 files changed

+11
-22
lines changed

6 files changed

+11
-22
lines changed

.rubocop_todo.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -927,14 +927,6 @@ Naming/VariableName:
927927
Naming/VariableNumber:
928928
Enabled: false
929929

930-
Security/Eval:
931-
Exclude:
932-
- 'lib/puppet/interface/action.rb'
933-
- 'lib/puppet/interface/action_builder.rb'
934-
- 'lib/puppet/pops/loader/ruby_data_type_instantiator.rb'
935-
- 'lib/puppet/pops/loader/ruby_function_instantiator.rb'
936-
- 'lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb'
937-
938930
# Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols.
939931
# SupportedStyles: inline, group
940932
Style/AccessModifierDeclarations:

lib/puppet/interface/action.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,14 @@ def #{@name}(#{decl.join(", ")})
264264
end
265265
WRAPPER
266266

267+
# It should be possible to rewrite this code to use `define_method`
268+
# instead of `class/instance_eval` since Ruby 1.8 is long dead.
267269
if @face.is_a?(Class)
268-
@face.class_eval do eval wrapper, nil, file, line end
270+
@face.class_eval do eval wrapper, nil, file, line end # rubocop:disable Security/Eval
269271
@face.send(:define_method, internal_name, &block)
270272
@when_invoked = @face.instance_method(name)
271273
else
272-
@face.instance_eval do eval wrapper, nil, file, line end
274+
@face.instance_eval do eval wrapper, nil, file, line end # rubocop:disable Security/Eval
273275
@face.meta_def(internal_name, &block)
274276
@when_invoked = @face.method(name).unbind
275277
end

lib/puppet/interface/action_builder.rb

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
# within the context of a new instance of this class.
55
# @api public
66
class Puppet::Interface::ActionBuilder
7+
extend Forwardable
8+
79
# The action under construction
810
# @return [Puppet::Interface::Action]
911
# @api private
@@ -141,15 +143,8 @@ def render_as(value = nil)
141143
property = setter.to_s.chomp('=')
142144

143145
unless method_defined? property
144-
# Using eval because the argument handling semantics are less awful than
145-
# when we use the define_method/block version. The later warns on older
146-
# Ruby versions if you pass the wrong number of arguments, but carries
147-
# on, which is totally not what we want. --daniel 2011-04-18
148-
eval <<-METHOD
149-
def #{property}(value)
150-
@action.#{property} = value
151-
end
152-
METHOD
146+
# ActionBuilder#<property> delegates to Action#<setter>
147+
def_delegator :@action, setter, property
153148
end
154149
end
155150

lib/puppet/pops/loader/ruby_data_type_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
1919
# make the private loader available in a binding to allow it to be passed on
2020
loader_for_type = loader.private_loader
2121
here = get_binding(loader_for_type)
22-
created = eval(ruby_code_string, here, source_ref, 1)
22+
created = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
2323
unless created.is_a?(Puppet::Pops::Types::PAnyType)
2424
raise ArgumentError, _("The code loaded from %{source_ref} did not produce a data type when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class }
2525
end

lib/puppet/pops/loader/ruby_function_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
1919
# make the private loader available in a binding to allow it to be passed on
2020
loader_for_function = loader.private_loader
2121
here = get_binding(loader_for_function)
22-
created = eval(ruby_code_string, here, source_ref, 1)
22+
created = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
2323
unless created.is_a?(Class)
2424
raise ArgumentError, _("The code loaded from %{source_ref} did not produce a Function class when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class }
2525
end

lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def self.create(loader, typed_name, source_ref, ruby_code_string)
3737
# This will do the 3x loading and define the "function_<name>" and "real_function_<name>" methods
3838
# in the anonymous module used to hold function definitions.
3939
#
40-
func_info = eval(ruby_code_string, here, source_ref, 1)
40+
func_info = eval(ruby_code_string, here, source_ref, 1) # rubocop:disable Security/Eval
4141

4242
# Validate what was loaded
4343
unless func_info.is_a?(Hash)

0 commit comments

Comments
 (0)