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']