Hi All, I helped Wei Dai wrestle with a similar problem for Crypto++. We wrote a couple audit tools, one of which is attached. I just completed an audit on my /usr/lib. The following OpenSSL modules crashed during a simple load/unload cycle:
/usr/lib/debug/lib/libcrypto.so.0.9.8 /usr/lib/debug/lib/libssl.so.0.9.8 /usr/lib/debug/usr/lib/ssl/engines/lib4758cca.so /usr/lib/debug/usr/lib/ssl/engines/libaep.so /usr/lib/debug/usr/lib/ssl/engines/libatalla.so /usr/lib/debug/usr/lib/ssl/engines/libcapi.so /usr/lib/debug/usr/lib/ssl/engines/libchil.so Since OpenSSL is a C library, a packager could be doing something with side effects. Or if there are globals and an Uninitialize function, the function might be whacking data that's still in use. In the Crypto++ case, there was a global object with a destructor that would destroy an object during unload. No problem, since that's what destructors do. However, if you want to catch a C++ exception across a module boundary, you need RTLD_GLOBAL. But RTLD_GLOBAL causes the runtime linker-loader to fold non-private symbols into one. So if two processes have the shared library open, both processes will call the dtor on the same object. The first will exit cleanly, the second process will continue to run [for a while] with a bad object. The interactions described above are specified in the Linux ABI, and result in a One Definition Rule (ODR) violation. The attached tool stemmed from a feature request [1] (and another similar problem which materialized while I was making the request [2]) on the GCC mailing list. I requested a warning for 'global variables' in compilation units since they [globals] are the cause of the crash under certain circumstances. So far, the response has been RTFM. Unfortunately, nearly 300 additional modules have not yet read the manual, including a couple of GTK modules. Jeffrey Walton Baltimore, MD, US [1] Switch to warn of global variables in a C++ shared object, http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46097 [2] Global variable in static library - double free or corruption error, http://gcc.gnu.org/ml/gcc-help/2010-10/msg00248.html ======================================= jeff...@bruno:~/$ uname -a Linux bruno 2.6.32-25-generic #45-Ubuntu SMP Sat Oct 16 19:52:42 UTC 2010 x86_64 GNU/Linux jeff...@bruno:~/$ apt-cache pkgnames | grep openssl libcrypt-openssl-x509-perl libcrypt-openssl-rsa-perl libcurl3-openssl-dev libcrypt-openssl-random-perl python-openssl libcrypt-openssl-dsa-perl python2.3-pyopenssl openssl-blacklist-extra libglobus-openssl-module0 python-openssl-dbg libopensc-openssl libopenssl-ruby1.9.1 libengine-pkcs11-openssl libglobus-openssl-module-dev python-openssl-doc libcurl4-openssl-dev libglobus-openssl-module-doc libopenssl-ruby python2.4-pyopenssl globus-openssl-progs python2.6-openssl libxmlsec1-openssl libglobus-openssl-dev openssl-blacklist python2-pyopenssl openssl openssl-doc python-pyopenssl libglobus-gsi-openssl-error-dev libopenssl-ruby1.8 libopenssl-ruby1.9 libglobus-gsi-openssl-error-doc libengine-tpm-openssl aolserver4-nsopenssl libcrypt-openssl-bignum-perl libglobus-gsi-openssl-error0 libglobus-openssl jeff...@bruno:~/$ apt-cache showpkg openssl Package: openssl Versions: 0.9.8k-7ubuntu8.3 (/var/lib/apt/lists/us.archive.ubuntu.com_ubuntu_dists_lucid-updates_main_binary-amd64_Packages) (/var/lib/apt/lists/security.ubuntu.com_ubuntu_dists_lucid-security_main_binary-amd64_Packages) (/var/lib/dpkg/status) Description Language: File: /var/lib/apt/lists/us.archive.ubuntu.com_ubuntu_dists_lucid-updates_main_binary-amd64_Packages MD5: 977022bc5545601176b69704acc5df9b 0.9.8k-7ubuntu8 (/var/lib/apt/lists/us.archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages) Description Language: File: /var/lib/apt/lists/us.archive.ubuntu.com_ubuntu_dists_lucid_main_binary-amd64_Packages MD5: 977022bc5545601176b69704acc5df9b Reverse Depends: apache2-threaded-dev,openssl apache2-prefork-dev,openssl slurm-llnl,openssl 0.9.8g-9 quassel-core,openssl ganeti2,openssl openvpn,openssl libssl0.9.8,openssl 0.9.6-2 dovecot-common,openssl apache2-threaded-dev,openssl apache2-prefork-dev,openssl docbookwiki,openssl yaws,openssl xmail,openssl x11vnc,openssl wl-beta,openssl wl,openssl uw-imapd,openssl tunneldigger,openssl topal,openssl tinyca,openssl 0.9.7e telnetd-ssl,openssl 0.9.8g-9 sympa,openssl 0.9.5a stunnel4,openssl stone,openssl ssvnc,openssl slurm-llnl,openssl 0.9.8g-9 sisu,openssl sendmail-bin,openssl quassel-core,openssl pyca,openssl 0.9.6 pyca,openssl 0.9.7 prosody,openssl proftpd-basic,openssl osptoolkit,openssl openvas-server,openssl openswan,openssl nufw,openssl nuauth,openssl lighttpd,openssl libstrongswan,openssl libapache2-mod-shib2,openssl kvpnc,openssl kolabd,openssl jabberd14,openssl ipopd,openssl httping,openssl gsoap,openssl gnus,openssl gnumed-server,openssl globus-openssl-progs,openssl ganeti2,openssl ftpd-ssl,openssl 0.9.2b etpan-ng,openssl ensymble,openssl ejabberd,openssl ebox-openvpn,openssl ebox,openssl 0.9.7e dtc-xen,openssl dtc-common,openssl dsniff,openssl dkimproxy,openssl cryptmount,openssl courier-ssl,openssl courier-pop-ssl,openssl courier-imap-ssl,openssl citadel-webcit,openssl citadel-server,openssl boxbackup-server,openssl boxbackup-client,openssl bincimap,openssl bcfg2-server,openssl aolserver4-nsopenssl,openssl 0.9.6 ssl-cert,openssl 0.9.8g-9 openvpn,openssl openssl-blacklist,openssl 0.9.8g-9 mutt,openssl libssl0.9.8,openssl 0.9.6-2 libpam-mount,openssl exim4-base,openssl dovecot-common,openssl ca-certificates,openssl ca-certificates,openssl apache2-threaded-dev,openssl apache2-prefork-dev,openssl Dependencies: 0.9.8k-7ubuntu8.3 - libc6 (2 2.7) libssl0.9.8 (2 0.9.8k-1) zlib1g (2 1:1.1.4) ca-certificates (0 (null)) openssl-doc (0 (null)) ssleay (3 0.9.2b) 0.9.8k-7ubuntu8 - libc6 (2 2.7) libssl0.9.8 (2 0.9.8k-1) zlib1g (2 1:1.1.4) ca-certificates (0 (null)) openssl-doc (0 (null)) ssleay (3 0.9.2b) Provides: 0.9.8k-7ubuntu8.3 - 0.9.8k-7ubuntu8 - Reverse Provides: jeff...@bruno:~/$
// load-unload.exe - load and unload shared objects to see which developers // and packagers did not pay attention to *ALL* the details of a C++ shared // object, RTLD_GLOBAL (so exceptions can be caught), the ABI, and ODR. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program 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, see <http://www.gnu.org/licenses/>. // // This notice must not be removed from any files, and original // attribution must remain with the files. // // Copyright (C) 2010, Jeffrey Walton (noloa...@gmail.com) // Need pthreads so gdb can follow the call to dlopen(2) // g++ -g -ggdb -O0 -Wall -Wextra load-unload.cpp -o load-unload.exe -ldl -lpthread #include <iostream> using std::cout; using std::cerr; using std::endl; #include <fstream> using std::ifstream; #include <string> using std::string; #include <vector> using std::vector; #include <stdexcept> using std::runtime_error; #include <cstdlib> using std::exit; #include <cctype> using std::isdigit; #include <cstring> using std::strerror; #include <algorithm> using std::find; using std::sort; using std::unique; using std::binary_search; // Include after the standard c/c++ stuff #include <errno.h> #include <dirent.h> #include <dlfcn.h> #include <link.h> #include <elf.h> #include <sys/types.h> #include <sys/stat.h> typedef vector<string> FileList; typedef vector<string> DirList; typedef vector<string> ErrList; typedef size_t MaxDepth; MaxDepth Unlimited = static_cast<MaxDepth>(-1); MaxDepth RootOnly = static_cast<MaxDepth>(1); // See APPLICATION BINARY INTERFACE, Chapter 4. #if !defined(ELFMAG0) # define ELFMAG0 0x7f #endif #if !defined(ELFMAG1) # define ELFMAG1 'E' #endif #if !defined(ELFMAG2) # define ELFMAG2 'L' #endif #if !defined(ELFMAG3) # define ELFMAG3 'F' #endif class ProgramError : public runtime_error { public: ProgramError(const char* msg, int err = -1) : runtime_error(msg), m_err(err) { } ProgramError(const string& msg, int err = -1) : runtime_error(msg.c_str()), m_err(err) { } int error() const { return m_err; } private: int m_err; }; class AutoDir { public: AutoDir(DIR*& dir) : m_dir(dir) { } virtual ~AutoDir() { if(m_dir) { closedir(m_dir); m_dir = NULL; } } private: DIR*& m_dir; }; class AutoLib { public: AutoLib(void*& lib) : m_lib(lib) { } virtual ~AutoLib() { if(m_lib) { dlclose(m_lib); m_lib = NULL; } } private: void*& m_lib; }; void DoDirectoryWalk(const string& entry, FileList& files, ErrList errata, MaxDepth = Unlimited); void DoLoadUnload(const FileList& files, ErrList errata); bool IsSharedObject(const string& file); bool IsElfFormat(const string& file); bool IsKnownToCrash(const string& file); int main(int argc, char* argv[]) { ErrList errata; // non fatal, such as access denied string libdir = "/usr/lib"; // assume /usr/lib if(argc >= 2) libdir = argv[1]; cout << "Testing shared objects in " << libdir << endl; cout << "Press CTLT-C to early exit" << endl; cout << endl; size_t processed = 0; try { FileList files; // MaxDepth depth = RootOnly; MaxDepth depth = Unlimited; if(depth == Unlimited) { cout << "Recursion depth = unlimited" << endl; } else if(depth == RootOnly) { cout << "Recursion depth = root directory only" << endl; } else { cout << "Recursion depth = " << depth << endl; } // Errata is non-fatal errors, such as EACCES. // Recursion depth is RootOnly, Unlimited, or any number in between. DoDirectoryWalk(libdir, files, errata, depth /*recursion*/); char* ldpath = getenv("LD_PATH"); if(ldpath) { // Since LD_PATH was set, recurse all directories // to test all SOs on the alternate path DoDirectoryWalk(ldpath, files, errata, Unlimited /*recursion depth*/); } // Sort for sane viewing processed = files.size(); sort(files.begin(), files.end()); // Remove any duplicates (should there be any duplicates?) FileList::iterator last = unique(files.begin(), files.end()); files.erase(last, files.end()); if(processed) { cout << "Attempting to process " << processed << " files" << endl; DoLoadUnload(files, errata); } } catch(const ProgramError& err) { cerr << err.what() << endl; exit(err.error()); } if(errata.size()) { cout << endl; cout << "Errata:" << endl; for(size_t i = 0; i < errata.size(); i++) { cout << errata[i] << endl; } } if(processed) { cout << endl; cout << "Processed " << processed << " files" << endl; } return 0; } void DoLoadUnload(const FileList& files, ErrList errata) { for(size_t i = 0; i < files.size(); i++) { cout << "Loading/unloading " << files[i] << endl; if(IsKnownToCrash(files[i])) { cout << " * known to crash (skipping): " << files[i] << endl; continue; } void* lib = NULL; AutoLib cleanup(lib); lib = dlopen(files[i].c_str(), RTLD_GLOBAL); if(!lib) lib = dlopen(files[i].c_str(), RTLD_GLOBAL | RTLD_LAZY); if(!lib) { errata.push_back(dlerror()); } } } // Adapted from W. Richard Stevens, Advanced Programming in the Unix Environment void DoDirectoryWalk(const string& entry, FileList& files, ErrList errata, MaxDepth maxDepth) { if(!entry.length()) { throw ProgramError("Entry is not valid", EINVAL); } DirList dirs; struct stat sbuf; struct dirent* dbuf; int ret = 0; // http://linux.die.net/man/2/lstat ret = lstat(entry.c_str(), &sbuf); if(ret < 0) { string err; if(EACCES == errno) { err = string("lstat failed for ") + entry + " (EACCES)"; errata.push_back(err); return; } err = string("lstat failed for ") + entry + ": "; throw ProgramError(err + strerror(ret), errno); } if(S_ISREG(sbuf.st_mode)) { // if(IsSharedObject(entry)) // files.push_back(entry); if(IsElfFormat(entry)) files.push_back(entry); return; } if(!S_ISDIR(sbuf.st_mode)) return; string path = entry; if(path[path.size()-1] != '/') path += '/'; DIR* dp = NULL; AutoDir cleanup(dp); dp = opendir(path.c_str()); if(!dp) throw ProgramError(strerror(ret), errno); if(maxDepth--) { // Only add directories if we are still recursing while(NULL != (dbuf = readdir(dp))) { if(0 == strcmp(".", dbuf->d_name)) continue; if(0 == strcmp("..", dbuf->d_name)) continue; dirs.push_back(path + dbuf->d_name); } for(size_t i = 0; i < dirs.size(); i++) { DoDirectoryWalk(dirs[i], files, errata, maxDepth); } } } bool IsKnownToCrash(const string& file) { // How large is this list going to get? Should it be sorted and searched? if(file == "/usr/lib/libwxsmithlib.so.0.0.1") return true; if(file == "/usr/lib/libwx_gtk2u_core-2.8.so.0") return true; if(file == "/usr/lib/debug/usr/lib/libwxsmithlib.so.0.0.1") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libcswift.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libgmp.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libnuron.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libsureware.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libubsec.so") return true; // And beloved crypto libraries and its ssl companion.... if(file == "/usr/lib/debug/lib/libcrypto.so.0.9.8") return true; if(file == "/usr/lib/debug/lib/libssl.so.0.9.8") return true; if(file == "/usr/lib/debug/usr/lib/libcrypto++.so.8.0.0") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/lib4758cca.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libaep.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libatalla.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libcapi.so") return true; if(file == "/usr/lib/debug/usr/lib/ssl/engines/libchil.so") return true; // It looks like every thing from CodeBlocks is broken.... if(string::npos != file.find("/usr/lib/codeblocks/plugins/")) return true; if(file == "/usr/lib/debug/usr/lib/libcodeblocks.so.0.0.1") return true; // And about half the stuff from OpenOffice if(string::npos != file.find("/usr/lib/openoffice/basis3.2/program/")) return true; return false; } // Cheap, but keeps us from opening each file looking for an elf signature and other attributes. bool IsSharedObject(const string& file) { static const string ext = "so"; string::size_type pos = file.rfind(ext); if(string::npos == pos) return false; // This catches all files that end with '.so' if(file.substr(pos) == ext) return true; // This catches all files that end with '.so.W',, '.so.W.X', ..., '.so.W.X.Y.Z'. It will // probably miss some interesting SOs like OpenSSL since a letter is used in versioning. pos += ext.length(); while(pos < file.length()) { const char c = file[pos]; if(c != '.' && !isdigit(c)) return false; pos++; } return true; } // Heavier weight, but more reliable. bool IsElfFormat(const string& file) { try { // We are only interested in the first four bytes of Elf32_Ehdr. // ELFMAG0 can take on a few values. See APPLICATION BINARY INTERFACE, Chapter 4. static const char elf1[] = { 0x7F, ELFMAG1, ELFMAG2, ELFMAG3 }; static const char elf2[] = { 0xB1, ELFMAG1, ELFMAG2, ELFMAG3 }; std::ifstream stream; stream.open(file.c_str()); char ident[4] = {0,0,0,0}; stream.read(ident, sizeof(ident)); if(0 == memcmp(elf1, ident, 4) || 0 == memcmp(elf2, ident, 4)) return true; } catch(std::exception& e) { } return false; }