Exheredludis/paludis/elf_linkage_checker.cc
2016-12-07 20:05:31 -08:00

363 lines
13 KiB
C++

/* vim: set sw=4 sts=4 et foldmethod=syntax : */
/*
* Copyright (c) 2007 David Leverton
*
* This file is part of the Paludis package manager. Paludis is free software;
* you can redistribute it and/or modify it under the terms of the GNU General
* Public License version 2, as published by the Free Software Foundation.
*
* Paludis is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "elf_linkage_checker.hh"
#include <paludis/util/elf.hh>
#include <paludis/util/elf_dynamic_section.hh>
#include <paludis/util/elf_types.hh>
#include <paludis/util/elf_relocation_section.hh>
#include <paludis/util/elf_symbol_section.hh>
#include <paludis/util/realpath.hh>
#include <paludis/util/join.hh>
#include <paludis/util/log.hh>
#include <paludis/util/pimp-impl.hh>
#include <paludis/util/set.hh>
#include <paludis/util/sequence.hh>
#include <paludis/util/member_iterator-impl.hh>
#include <paludis/util/visitor_cast.hh>
#include <paludis/util/wrapped_forward_iterator.hh>
#include <paludis/util/safe_ifstream.hh>
#include <paludis/util/fs_path.hh>
#include <paludis/util/fs_stat.hh>
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <map>
#include <set>
#include <vector>
#include <mutex>
using namespace paludis;
namespace
{
struct ElfArchitecture
{
// not in elf.h; from glibc-2.5/sysdeps/s390/s390-32/dl-machine.h
static const unsigned S390_OLD = 0xA390;
// not in FreeBSD elf.h
static const unsigned MIPS_ABI2 = 32;
unsigned _machine;
unsigned char _class;
bool _bigendian, _mips_n32;
bool operator< (const ElfArchitecture &) const PALUDIS_ATTRIBUTE((warn_unused_result));
static unsigned normalise_arch(unsigned arch)
{
switch (arch)
{
case EM_MIPS_RS3_LE:
return EM_MIPS;
case S390_OLD:
return EM_S390;
}
return arch;
}
template <typename ElfType_>
ElfArchitecture(const ElfObject<ElfType_> & elf) :
_machine(normalise_arch(elf.get_arch())),
_class(ElfType_::elf_class),
_bigendian(elf.is_big_endian()),
_mips_n32(EM_MIPS == _machine && MIPS_ABI2 & elf.get_flags())
{
}
};
bool
ElfArchitecture::operator< (const ElfArchitecture & other) const
{
if (_machine != other._machine)
return _machine < other._machine;
if (_class != other._class)
return _class < other._class;
if (_bigendian != other._bigendian)
return _bigendian < other._bigendian;
return _mips_n32 < other._mips_n32;
}
}
typedef std::multimap<FSPath, FSPath, FSPathComparator> Symlinks;
typedef std::map<ElfArchitecture, std::map<std::string, std::vector<FSPath> > > Needed;
namespace paludis
{
template <>
struct Imp<ElfLinkageChecker>
{
FSPath root;
std::set<std::string> check_libraries;
std::mutex mutex;
std::map<FSPath, ElfArchitecture, FSPathComparator> seen;
Symlinks symlinks;
std::map<ElfArchitecture, std::vector<std::string> > libraries;
Needed needed;
std::vector<FSPath> extra_lib_dirs;
template <typename> bool check_elf(const FSPath &, std::istream &);
void handle_library(const FSPath &, const ElfArchitecture &);
template <typename> bool check_extra_elf(const FSPath &, std::istream &, std::set<ElfArchitecture> &);
Imp(const FSPath & the_root, const std::shared_ptr<const Sequence<std::string>> & the_libraries) :
root(the_root)
{
for (const auto & library : *the_libraries)
check_libraries.insert(library);
}
};
}
ElfLinkageChecker::ElfLinkageChecker(const FSPath & root, const std::shared_ptr<const Sequence<std::string>> & libraries) :
_imp(root, libraries)
{
}
ElfLinkageChecker::~ElfLinkageChecker() = default;
bool
ElfLinkageChecker::check_file(const FSPath & file)
{
std::string basename(file.basename());
if (! (std::string::npos != basename.find(".so.") ||
(3 <= basename.length() && ".so" == basename.substr(basename.length() - 3)) ||
(0 != (file.stat().permissions() & S_IXUSR))))
return false;
SafeIFStream stream(file);
return _imp->check_elf<Elf32Type>(file, stream) || _imp->check_elf<Elf64Type>(file, stream);
}
template <typename ElfType_>
bool
Imp<ElfLinkageChecker>::check_elf(const FSPath & file, std::istream & stream)
{
if (! ElfObject<ElfType_>::is_valid_elf(stream))
return false;
try
{
Context ctx("When checking '" + stringify(file) + "' as a " +
stringify<int>(ElfType_::elf_class * 32) + "-bit ELF file:");
ElfObject<ElfType_> elf(stream);
if (ET_EXEC != elf.get_type() && ET_DYN != elf.get_type())
{
Log::get_instance()->message("broken_linkage_finder.not_interesting", ll_debug, lc_context)
<< "File is not an executable or shared library";
return true;
}
ElfArchitecture arch(elf);
elf.resolve_all_strings();
std::unique_lock<std::mutex> l(mutex);
if (check_libraries.empty() && ET_DYN == elf.get_type())
handle_library(file, arch);
for (const auto & section : elf.sections())
{
if (const auto *dyn_sec = visitor_cast<const DynamicSection<ElfType_>>(section))
{
for (const auto & entry : dyn_sec->entries())
{
if (const auto *ent_str = visitor_cast<const DynamicEntryString<ElfType_>>(entry))
{
if (ent_str->tag_name() != "NEEDED")
continue;
const std::string & req((*ent_str)());
if (check_libraries.empty() || check_libraries.end() != check_libraries.find(req))
{
Log::get_instance()->message("broken_linkage_finder.depends", ll_debug, lc_context)
<< "File depends on " << req;
needed[arch][req].push_back(file);
}
}
}
}
}
}
catch (const InvalidElfFileError & e)
{
Log::get_instance()->message("broken_linkage_finder.invalid", ll_warning, lc_no_context)
<< "'" << file << "' appears to be invalid or corrupted: " << e.message();
}
return true;
}
void
Imp<ElfLinkageChecker>::handle_library(const FSPath & file, const ElfArchitecture & arch)
{
seen.insert(std::make_pair(file, arch));
std::pair<Symlinks::const_iterator, Symlinks::const_iterator> range(symlinks.equal_range(file));
libraries[arch].push_back(file.basename());
libraries[arch].push_back(stringify(file.strip_leading(root)));
if (range.first != range.second)
{
Log::get_instance()->message("broken_linkage_finder.known_symlinks_are",
ll_debug, lc_context) << "Known symlinks are " <<
join(second_iterator(range.first), second_iterator(range.second), " ");
std::transform(second_iterator(range.first), second_iterator(range.second),
std::back_inserter(libraries[arch]), std::mem_fn(&FSPath::basename));
using namespace std::placeholders;
std::transform(second_iterator(range.first), second_iterator(range.second),
std::back_inserter(libraries[arch]),
std::bind(static_cast<std::string (*)(const FSPath &)>(stringify),
std::bind(&FSPath::strip_leading, _1, root)));
}
}
void
ElfLinkageChecker::note_symlink(const FSPath & link, const FSPath & target)
{
if (_imp->check_libraries.empty())
{
std::unique_lock<std::mutex> l(_imp->mutex);
std::map<FSPath, ElfArchitecture, FSPathComparator>::const_iterator it(_imp->seen.find(target));
if (_imp->seen.end() != it)
{
Log::get_instance()->message("broken_linkage_finder.note_symlink", ll_debug, lc_context)
<< "'" << link << "' is a symlink to known library '" << target << "'";
_imp->libraries[it->second].push_back(link.basename());
_imp->libraries[it->second].push_back(stringify(link.strip_leading(_imp->root)));
}
else
_imp->symlinks.insert(std::make_pair(target, link));
}
}
void
ElfLinkageChecker::add_extra_lib_dir(const FSPath & dir)
{
_imp->extra_lib_dirs.push_back(dir);
}
void
ElfLinkageChecker::need_breakage_added(
const std::function<void (const FSPath &, const std::string &)> & callback)
{
using namespace std::placeholders;
typedef std::map<std::string, std::set<ElfArchitecture> > AllMissing;
AllMissing all_missing;
for (const auto & arch : _imp->needed)
{
std::sort(_imp->libraries[arch.first].begin(), _imp->libraries[arch.first].end());
_imp->libraries[arch.first].erase(std::unique(_imp->libraries[arch.first].begin(),
_imp->libraries[arch.first].end()),
_imp->libraries[arch.first].end());
std::vector<std::string> missing;
std::set_difference(first_iterator(arch.second.begin()),
first_iterator(arch.second.end()),
_imp->libraries[arch.first].begin(),
_imp->libraries[arch.first].end(),
std::back_inserter(missing));
for (const auto & item : missing)
all_missing[item].insert(arch.first);
}
for (const auto & directory : _imp->extra_lib_dirs)
{
Context ctx("When searching for missing libraries in '" + stringify(directory) + "':");
for (auto & missing : all_missing)
{
if (missing.second.empty())
continue;
FSPath file(dereference_with_root(directory / missing.first, _imp->root));
if (! file.stat().is_regular_file())
{
Log::get_instance()->message("broken_linkage_finder.missing", ll_debug, lc_context)
<< "'" << file << "' is missing or not a regular file";
continue;
}
try
{
SafeIFStream stream(file);
if (! (_imp->check_extra_elf<Elf32Type>(file, stream, missing.second) ||
_imp->check_extra_elf<Elf64Type>(file, stream, missing.second)))
Log::get_instance()->message("broken_linkage_finder.not_an_elf", ll_debug, lc_no_context)
<< "'" << file << "' is not an ELF file";
}
catch (const SafeIFStreamError & e)
{
Log::get_instance()->message("broken_linkage_finder.failure", ll_warning, lc_no_context)
<< "Error opening '" << file << "': '" << e.message() << "' (" << e.what() << ")";
continue;
}
}
}
for (const auto & missing : all_missing)
for (const auto & arch : missing.second)
std::for_each(_imp->needed[arch][missing.first].begin(),
_imp->needed[arch][missing.first].end(),
std::bind(callback, _1, missing.first));
}
template <typename ElfType_>
bool
Imp<ElfLinkageChecker>::check_extra_elf(const FSPath & file, std::istream & stream, std::set<ElfArchitecture> & arches)
{
if (! ElfObject<ElfType_>::is_valid_elf(stream))
return false;
Context ctx("When checking '" + stringify(file) + "' as a " + stringify<int>(ElfType_::elf_class * 32) + "-bit ELF file");
try
{
ElfObject<ElfType_> elf(stream);
if (ET_DYN == elf.get_type())
{
Log::get_instance()->message("broken_linkage_finder.is_library", ll_debug, lc_context)
<< "'" << file << "' is a library";
arches.erase(ElfArchitecture(elf));
}
else
Log::get_instance()->message("broken_linkage_finder.is_not_library", ll_debug, lc_context)
<< "'" << file << "' is not a library";
}
catch (const InvalidElfFileError & e)
{
Log::get_instance()->message("broken_linkage_finder.invalid", ll_warning, lc_no_context)
<< "'" << file << "' appears to be invalid or corrupted: " << e.message();
}
return true;
}