Skip to content

Tool for locating internal symbols unnecessarily exported from shared libraries.

License

Notifications You must be signed in to change notification settings

yugr/ShlibVisibilityChecker

Repository files navigation

License Build Status codecov Total alerts Coverity Scan

What's this?

ShlibVisibilityChecker is a small tool which locates internal symbols that are unnecessarily exported from shared libraries. Such symbols are undesirable because they cause

ShlibVisibilityChecker compares APIs declared in public headers against APIs exported from shared libraries and warns about discrepancies. In majority of cases such symbols are internal library symbols which should be hidden (in rare cases these are internal symbols which are used by other libraries or executables in the same package and shlibvischeck-debian tries hard to not report such cases).

Such discrepancies should then be fixed by recompiling package with -fvisibility=hidden (see here for details). A typical fix, for a typical Autoconf project can be found here.

ShlibVisibilityChecker not meant to be 100% precise but rather provide assistance in locating packages which may benefit the most from visibility annotations (and to understand how bad the situation with visibility is in modern distros).

How to use

To check a raw package, i.e. a bunch of headers and shared libs, collect source and binary interfaces and compare them:

$ bin/read_header_api --only-args /usr/include/xcb/* > api.txt
$ ./read_binary_api --permissive /usr/lib/x86_64-linux-gnu/libxcb*.so > abi.txt
$ vimdiff api.txt abi.txt  # Or `comm -13 api.txt abi.txt'

Another useful scenario is locating symbols that are exported from Debian package's shared libraries but are not declared in it's headers. The main tool for this is a shlibvischeck-debian script.

To apply it to a package, run

$ shlibvischeck-debian libacl1
The following exported symbols in package 'libacl1' are private:
  __acl_extended_file
  __acl_from_xattr
  __acl_to_xattr
  __bss_start
  _edata
  _end
  _fini
  _init
  closed
  head
  high_water_alloc
  next_line
  num_dir_handles
  walk_tree

To skip autogenerated symbols like _init or _edata (caused by ld linker scripts and libgcc startup files) add --permissive.

You can also check visibility issues in arbitrary set of headers and libraries:

$ shlibvischeck-common --permissive --cflags="-I/usr/include -I$AUDIT_INSTALL/include -I/usr/lib/llvm-5.0/lib/clang/5.0.0/include" $AUDIT_INSTALL/include/*.h $AUDIT_INSTALL/lib/*.so*

How to install

Build-time prerequisites are python3 (setuptools module), clang, llvm, libclang-dev, g++ and make. Run-time dependencies are python3 (python-magic module), pkg-config and aptitude. To install everything on Ubuntu, run

$ sudo apt-get install python3 clang llvm libclang-dev g++ make pkg-config aptitude
$ sudo python3 -mensurepip
$ sudo pip3 install setuptools python-magic

(you could also use script scripts/install-deps.sh).

You also need to enable access to Ubuntu source packages via

$ sudo sed -Ei 's/^# *deb-src /deb-src /' /etc/apt/sources.list
$ sudo apt-get update

Python and binary components are built separately:

$ make clean all && make install
$ ./setup.py build && pip3 install .

During analysis shlibvischeck-debian installs new Debian packages so it's recommended to run it under chroot or in VM. There are many instructions on setting up chroot e.g. this one.

Where to find packages

A list of packages for analysis can be obtained from Debian rating:

$ curl https://popcon.debian.org/by_vote | awk '/^[0-9]+ +lib/{print $2}' > by_vote
$ shlibvischeck-debian $(head -500 by_vote | tr '\n' ' ')

How to fix a package

Once you found a problematic package, you can fix it by restricting visibility of internal symbols. The best way to control symbol visibility in a package is to

  • hide all symbols by default by adding -fvisibility=hidden to CFLAGS in project buildscripts (Makefile.in or CMakeLists.txt)
  • explicitly annotate publicly visible functions with __attribute__((visibility("default")))

See fix in libcaca for example.

Issues and limitations

At the moment tool works only on Debian-based systems (e.g. Ubuntu). This should be fine as buildscripts are the same across all distros so detecting issues on Ubuntu would serve everyone else too.

An important design issue is that the tool can not detect symbols which are used indirectly i.e. not through an API but through dlsym or explicit out-of-header prototype declaration in source file. This happens in plugins or tightly interconnected shlibs within the same project. Such cases should hopefully be rare.

ShlibVisibilityChecker is a heuristic tool so it will not be able to analyze all packages. Current success rate is around 60%. Major reasons for errors are

  • badly-structured headers i.e. the ones which do not #include all their dependencies (e.g. libatasmart fails to include stddef.h and tdb fails to include sys/types.h).
  • internal headers which should not be #included directly (e.g. lzma/container.h)
  • experimental headers which require custom macro definitions (not listed in pkgconfig) (e.g. dpkg/macros.h requires LIBDPKG_VOLATILE_API)
  • missing dependencies (e.g. libverto-dev uses Glib headers but does not declare this)

Other issues:

  • TODOs are scattered all over the codebase
  • would be interesting to go over dependent packages and check if they use invalid symbols

Trophies

The tool found huge number of packages that lacked visibility annotations (in practice every second package has spurious exports). Here are some which I tried to fix:

More perspective packages (from Debian top-100): libpopt1, libgpg-error0, libxml2, libwrap0, libpcre3, libkeyutils1, libedit2, liblcms2-2.