lib/omnibus/health_check.rb in omnibus-5.0.0 vs lib/omnibus/health_check.rb in omnibus-5.1.0

- old
+ new

@@ -1,6 +1,6 @@ -# + # Copyright 2012-2014 Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,14 +12,22 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # +require 'omnibus/sugarable' +begin + require 'pedump' +rescue LoadError + STDERR.puts "pedump not found - windows health checks disabled" +end + module Omnibus class HealthCheck include Logging include Util + include Sugarable WHITELIST_LIBS = [ /ld-linux/, /libc\.so/, /libcrypt\.so/, @@ -138,10 +146,11 @@ /libthr\.so/, /libutil\.so/, /libelf\.so/, /libkvm\.so/, /libprocstat\.so/, + /libmd\.so/, ].freeze class << self # @see (HealthCheck#new) def run!(project) @@ -175,20 +184,22 @@ # # @return [true] # if the healthchecks pass # def run! - if Ohai['platform'] == 'windows' - log.warn(log_key) { 'Skipping health check on Windows' } - return true - end log.info(log_key) {"Running health on #{project.name}"} bad_libs = case Ohai['platform'] when 'mac_os_x' health_check_otool when 'aix' health_check_aix + when 'windows' + # TODO: objdump -p will provided a very limited check of + # explicit dependencies on windows. Most dependencies are + # implicit and hence not detected. + log.warn(log_key) { 'Skipping dependency health checks on Windows.' } + {} else health_check_ldd end unresolved = [] @@ -275,18 +286,118 @@ end raise HealthCheckFailed end + conflict_map = {} + + conflict_map = relocation_check if relocation_checkable? + + if conflict_map.keys.length > 0 + log.warn(log_key) { 'Multiple dlls with overlapping images detected' } + + conflict_map.each do |lib_name, data| + base = data[:base] + size = data[:size] + next_valid_base = data[:base] + data[:size] + + log.warn(log_key) do + out = "Overlapping dll detected:\n" + out << " #{lib_name} :\n" + out << " IMAGE BASE: #{hex}\n" % base + out << " IMAGE SIZE: #{hex} (#{size} bytes)\n" % size + out << " NEXT VALID BASE: #{hex}\n" % next_valid_base + out << " CONFLICTS:\n" + + data[:conflicts].each do |conflict_name| + cbase = conflict_map[conflict_name][:base] + csize = conflict_map[conflict_name][:size] + out << " - #{conflict_name} #{hex} + #{hex}\n" % [cbase, csize] + end + + out + end + end + + # Don't raise an error yet. This is only bad for FIPS mode. + end + true end + # Ensure the method relocation_check is able to run # + # @return [Boolean] + # + def relocation_checkable? + return false unless windows? + + begin + require 'pedump' + true + rescue LoadError + false + end + end + + # Check dll image location overlap/conflicts on windows. + # + # @return [Hash<String, Hash<Symbol, ...>>] + # library_name -> + # :base -> base address + # :size -> the total image size in bytes + # :conflicts -> array of library names that overlap + # + def relocation_check + conflict_map = {} + + embedded_bin = "#{project.install_dir}/embedded/bin" + Dir.glob("#{embedded_bin}/*.dll") do |lib_path| + log.debug(log_key) { "Analyzing dependencies for #{lib_path}" } + + File.open(lib_path, 'rb') do |f| + dump = PEdump.new(lib_path) + pe = dump.pe f + + # Don't scan dlls for a different architecture. + next if windows_arch_i386? == pe.x64? + + lib_name = File.basename(lib_path) + base = pe.ioh.ImageBase + size = pe.ioh.SizeOfImage + conflicts = [] + + # This can be done more smartly but O(n^2) is just fine for n = small + conflict_map.each do |candidate_name, details| + unless details[:base] >= base + size || + details[:base] + details[:size] <= base + details[:conflicts] << lib_name + conflicts << candidate_name + end + end + + conflict_map[lib_name] = { + base: base, + size: size, + conflicts: conflicts, + } + + log.debug(log_key) { "Discovered #{lib_name} at #{hex} + #{hex}" % [ base, size ] } + end + end + + # Filter out non-conflicting entries. + conflict_map.delete_if do |lib_name, details| + details[:conflicts].empty? + end + end + + # # Run healthchecks against otool. # - # @return [Array<String>] - # the bad libraries + # @return [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # def health_check_otool current_library = nil bad_libs = {} @@ -305,12 +416,12 @@ end # # Run healthchecks against aix. # - # @return [Array<String>] - # the bad libraries + # @return [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # def health_check_aix current_library = nil bad_libs = {} @@ -332,14 +443,14 @@ bad_libs end # # Run healthchecks against ldd. + # + # @return [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) # - # @return [Array<String>] - # the bad libraries - # def health_check_ldd current_library = nil bad_libs = {} read_shared_libs("find #{project.install_dir}/ -type f | xargs ldd") do |line| @@ -373,10 +484,20 @@ end private # + # This is the printf style format string to render a pointer/size_t on the + # current platform. + # + # @return [String] + # + def hex + windows_arch_i386? ? "0x%08x" : "0x%016x" + end + + # # The list of whitelisted (ignored) files from the project and softwares. # # @return [Array<String, Regexp>] # def whitelist_files @@ -401,9 +522,20 @@ end end # # Check the given path and library for "bad" libraries. + # + # @param [Hash<String, Hash<String, Hash<String, Int>>>] + # the bad libraries (library_name -> dependency_name -> satisfied_lib_path -> count) + # @param [String] + # the library being analyzed + # @param [String] + # dependency library name + # @param [String] + # actual path of library satisfying the dependency + # + # @return the modified bad_library hash # def check_for_bad_library(bad_libs, current_library, name, linked) safe = nil whitelist_libs = case Ohai['platform']