Skip to content
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

Specify a maximum number of runs to limit the request params -> DoS. #191

Merged
merged 1 commit into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions lib/rack/contrib/profiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ def initialize(app, options = {})
@profile = nil
@printer = parse_printer(options[:printer] || DEFAULT_PRINTER)
@times = (options[:times] || 1).to_i
@maximum_runs = options.fetch(:maximum_runs, 10)
ioquatix marked this conversation as resolved.
Show resolved Hide resolved
end

attr :maximum_runs

def call(env)
if mode = profiling?(env)
profile(env, mode)
Expand All @@ -61,14 +64,32 @@ def profiling?(env)
end
end

# How many times to run the request within the profiler.
# If the profiler_runs query parameter is set, use that.
# Otherwise, use the :times option passed to `#initialize`.
# If the profiler_runs query parameter is greater than the
# :maximum option passed to `#initialize`, use the :maximum
# option.
def runs(request)
if profiler_runs = request.params['profiler_runs']
profiler_runs = profiler_runs.to_i
if profiler_runs > @maximum_runs
return @maximum_runs
else
return profiler_runs
end
ioquatix marked this conversation as resolved.
Show resolved Hide resolved
else
return @times
end
end

def profile(env, mode)
@profile = ::RubyProf::Profile.new(measure_mode: ::RubyProf.const_get(mode.upcase))

GC.enable_stats if GC.respond_to?(:enable_stats)
request = Rack::Request.new(env.clone)
runs = (request.params['profiler_runs'] || @times).to_i
result = @profile.profile do
runs.times { @app.call(env) }
runs(request).times { @app.call(env) }
end
GC.disable_stats if GC.respond_to?(:disable_stats)

Expand Down
7 changes: 7 additions & 0 deletions test/spec_rack_profiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ def profiler(app, options = {})
_(body.to_enum.to_a.join).must_match(/\[#{runs} calls, #{runs} total\]/)
end

specify 'called more than the default maximum times via query params' do
runs = 20
req = Rack::MockRequest.env_for("/", :params => "profile=process_time&profiler_runs=#{runs}")
body = profiler(app).call(req)[2]
_(body.to_enum.to_a.join).must_match(/\[10 calls, 10 total\]/)
end

specify 'CallStackPrinter has content-type test/html' do
headers = profiler(app, :printer => :call_stack).call(request)[1]
_(headers).must_equal "content-type"=>"text/html"
Expand Down