From c662e0918f0e0894511275ccd4f8962afe4efb76 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 18 Sep 2018 08:35:09 +1000 Subject: [PATCH] SECURITY: remove admin memory diagnostics routes --- .../admin/diagnostics_controller.rb | 42 ----- config/routes.rb | 3 - lib/memory_diagnostics.rb | 169 ------------------ 3 files changed, 214 deletions(-) delete mode 100644 app/controllers/admin/diagnostics_controller.rb delete mode 100644 lib/memory_diagnostics.rb diff --git a/app/controllers/admin/diagnostics_controller.rb b/app/controllers/admin/diagnostics_controller.rb deleted file mode 100644 index 81b076b0725..00000000000 --- a/app/controllers/admin/diagnostics_controller.rb +++ /dev/null @@ -1,42 +0,0 @@ -require_dependency 'memory_diagnostics' - -class Admin::DiagnosticsController < Admin::AdminController - layout false - skip_before_action :check_xhr - - def memory_stats - text = nil - - if params.key?(:diff) - if !MemoryDiagnostics.snapshot_exists? - text = "No initial snapshot exists" - else - text = MemoryDiagnostics.compare - end - elsif params.key?(:snapshot) - MemoryDiagnostics.snapshot_current_process - text = "Writing snapshot to: #{MemoryDiagnostics.snapshot_filename}\n\nTo get a diff use ?diff=1" - else - text = MemoryDiagnostics.memory_report(class_report: params.key?(:full)) - end - - render plain: text - end - - def dump_heap - begin - # ruby 2.1 - GC.start(full_mark: true) - require 'objspace' - - io = File.open("discourse-heap-#{SecureRandom.hex(3)}.json", 'w') - ObjectSpace.dump_all(output: io) - io.close - - render plain: "HEAP DUMP:\n#{io.path}" - rescue - render plain: "HEAP DUMP:\nnot supported" - end - end - -end diff --git a/config/routes.rb b/config/routes.rb index c661b0a7271..670f21e157d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -290,9 +290,6 @@ Discourse::Application.routes.draw do post "preview" => "badges#preview" end end - - get "memory_stats" => "diagnostics#memory_stats", constraints: AdminConstraint.new - get "dump_heap" => "diagnostics#dump_heap", constraints: AdminConstraint.new end # admin namespace get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect" diff --git a/lib/memory_diagnostics.rb b/lib/memory_diagnostics.rb deleted file mode 100644 index 3a3fcd60f54..00000000000 --- a/lib/memory_diagnostics.rb +++ /dev/null @@ -1,169 +0,0 @@ -module MemoryDiagnostics - - def self.snapshot_exists? - File.exists?(snapshot_filename) - end - - def self.compare(from = nil, to = nil) - - from ||= snapshot_filename - if !to - filename = snapshot_filename + ".new" - snapshot_current_process(filename) - to = filename - end - - from = Marshal::load(IO.binread(from)); - to = Marshal::load(IO.binread(to)); - - diff = from - to - - require 'objspace' - diff = diff.map do |id| - ObjectSpace._id2ref(id) rescue nil - end - diff.compact! - - report = "#{diff.length} objects have leaked\n" - - report << "Summary:\n" - - summary = {} - diff.each do |obj| - begin - summary[obj.class] ||= 0 - summary[obj.class] += 1 - rescue - # don't care - end - end - - report << summary.sort { |a, b| b[1] <=> a[1] }[0..50].map { |k, v| - "#{k}: #{v}" - }.join("\n") - - report << "\n\nSample Items:\n" - - diff[0..5000].each do |v| - report << "#{v.class}: #{String === v ? v[0..300] : (40 + ObjectSpace.memsize_of(v)).to_s + " bytes"}\n" rescue nil - end - - report - end - - def self.snapshot_path - "#{Rails.root}/tmp/mem_snapshots" - end - - def self.snapshot_filename - "#{snapshot_path}/#{Process.pid}.snapshot" - end - - def self.snapshot_current_process(filename = nil) - filename ||= snapshot_filename - pid = fork do - snapshot(filename) - end - - Process.wait(pid) - end - - def self.snapshot(filename) - require 'objspace' - FileUtils.mkdir_p snapshot_path - object_ids = [] - - full_gc - - ObjectSpace.each_object do |o| - begin - object_ids << o.object_id - rescue - # skip - end - end - - IO.binwrite(filename, Marshal::dump(object_ids)) - end - - def self.memory_report(opts = {}) - begin - # ruby 2.1 - GC.start(full_mark: true) - rescue - GC.start - end - - classes = {} - large_objects = [] - - if opts[:class_report] - require 'objspace' - ObjectSpace.each_object do |o| - begin - classes[o.class] ||= 0 - classes[o.class] += 1 - if (size = ObjectSpace.memsize_of(o)) > 200 - large_objects << [size, o] - end - rescue - # all sorts of stuff can happen here BasicObject etc. - classes[:unknown] ||= 0 - classes[:unknown] += 1 - end - end - classes = classes.sort { |a, b| b[1] <=> a[1] }[0..40].map { |klass, count| "#{klass}: #{count}" } - - classes << "\nLarge Objects (#{large_objects.length} larger than 200 bytes total size #{large_objects.map { |x, _| x }.sum}):\n" - - classes += large_objects.sort { |a, b| b[0] <=> a[0] }[0..800].map do |size, object| - rval = "#{object.class}: size #{size}" - rval << " " << object.to_s[0..500].gsub("\n", "") if (String === object) || (Regexp === object) - rval << "\n" - rval - end - end - - stats = GC.stat.map { |k, v| "#{k}: #{v}" } - counts = ObjectSpace.count_objects.sort { |a, b| b[1] <=> a[1] }.map { |k, v| "#{k}: #{v}" } - - < 0 ? classes.join("\n") : "Class report omitted use ?full=1 to include it"} - -TEXT - - end - - def self.full_gc - # gc start may not collect everything - GC.start while new_count = decreased_count(new_count) - end - - def self.decreased_count(old) - count = count_objects - if !old || count < old - count - else - nil - end - end - - def self.count_objects - i = 0 - ObjectSpace.each_object do |obj| - i += 1 - end - end -end