This patch enables ASan instrumentation for glibc.
1. Modifies rltd to support calling init for libasan.so
before libc.so. Also changes don't brake backward
compatibility against libpthread.so, because rtld will add
libpthread.so as a first library to initlist.
2. Changes the type of symbol __sched_cpucount from IFUNC
to FUNC, to avoid segfault at the relocation stage.
IFUCN is a spectial type of symbol which allows to call
resolver at the relocation stage (symbol binding),
so resolver instrumented with ASan will cause a segfault.
Change-Id: I99b6e230605801123e3e132f9934c75fae4e6cfa
--- /dev/null
+#!/usr/bin/env python
+import os
+import re
+import shutil
+import sys
+
+GCC = 'gcc'
+
+blacklist=['rtld', '/dl-', 'elf/', 'string/mem', 'time/time',
+ 'time/gettimeofday', 'time/timegm', 'time/timespec_get',
+ 'nptl/libc_pthread_init', 'nptl/register-atfork',
+ #In Tizen, ASan tries to write error log to procfs, in case procfs is not mounted,
+ #this action will generate a kernel panic. So, just disble errors which happens because
+ #of dword-aligned reading by glibc.
+ 'string',
+ ]
+
+def AllowAsan(out_file):
+ match = re.match(r'/.*build/(.*).os$', out_file)
+ if not match:
+ #print >>sys.stderr, 'FALLBACK_NO_MATCH: %s' % out_file
+ return False
+ obj = match.group(1)
+
+ for b in blacklist:
+ if re.search(b, obj):
+ #print >>sys.stderr, 'FALLBACK_BLACKLIST: %s' % obj
+ return False
+ return True
+
+def o():
+ try:
+ i = sys.argv.index('-o')
+ return sys.argv[i + 1]
+ except Exception:
+ return ''
+
+if __name__ == '__main__':
+ args = sys.argv[1:]
+ args = [arg for arg in args if arg != '-Wl,-z,defs']
+
+ if AllowAsan(o()):
+ print >> sys.stderr, 'ASAN:', o()
+ args.append('-fsanitize-recover=address')
+ args.append('-fsanitize=address')
+ args.append('--param')
+ args.append('asan-use-after-return=0')
+
+ args.append('-fno-omit-frame-pointer')
+ args.append('-Wno-error')
+ args.append('-DSKIP_IFUNC');
+
+ os.execvp(GCC, [GCC] + args)
if (__glibc_unlikely (GL(dl_initfirst) != NULL))
{
- call_init (GL(dl_initfirst), argc, argv, env);
+#ifdef ASAN_INIT_FIRST
+ struct initfirst_list *initfirst;
+ initfirst = GL (dl_initfirst);
+ while (initfirst)
+ {
+ if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_IMPCALLS))
+ _dl_debug_printf ("\ncalling initfirst: %s\n\n",
+ DSO_FILENAME (initfirst->l_map->l_name));
+
+ call_init (initfirst->l_map, argc, argv, env);
+ /* Can not call free () on the memory from *initfirst,
+ because ASan will detect an error. */
+ initfirst = initfirst->next;
+ }
+#else
+ call_init (GL (dl_initfirst), argc, argv, env);
+#endif
+
GL(dl_initfirst) = NULL;
}
/* Remember whether this object must be initialized first. */
if (l->l_flags_1 & DF_1_INITFIRST)
+#ifdef ASAN_INIT_FIRST
+ {
+ struct initfirst_list *initfirst;
+ /* Looks like a safe action to call malloc, because rtld
+ has own malloc, but we will not be able to free
+ that memmory because of ASan error. */
+ initfirst = malloc (sizeof (struct initfirst_list));
+ if (__glibc_likely (initfirst != NULL))
+ {
+ initfirst->l_map = l;
+ initfirst->next = NULL;
+
+ if (__glibc_likely (GL(dl_initfirst) == NULL))
+ GL(dl_initfirst) = initfirst;
+ else
+ {
+ /* Add new DSO to the front of the initlist,
+ make sure libpthread.so still come first
+ at the initlist. In this case we reproduce
+ same behavior as with .preinit section. */
+ initfirst->next = GL(dl_initfirst);
+ GL(dl_initfirst) = initfirst;
+ }
+ }
+ }
+#else
GL(dl_initfirst) = l;
+#endif
/* Finally the file information. */
l->l_file_id = id;
/* All directories after startup. */
struct r_search_path_elem *_dl_init_all_dirs;
+#ifdef ASAN_INIT_FIRST
+/* The list of objects to be initialized first. */
+struct initfirst_list *_dl_initfirst;
+#else
/* The object to be initialized first. */
struct link_map *_dl_initfirst;
+#endif
/* Descriptor to write debug messages to. */
int _dl_debug_fd = STDERR_FILENO;
--- /dev/null
+#include <regex>
+#include <string>
+#include <unistd.h>
+
+#ifdef DEBUG
+#include <errno.h>
+#include <iostream>
+#endif
+
+const char *GCC = "/usr/bin/gcc";
+static const int ASAN_OPTIONS_MAX = 7;
+static const int ASAN_OPTIONS_MIN = 3;
+
+static std::string blacklist[] = {
+ "rtld", "/dl-", "elf", "string/mem", "time/time", "time/gettimeofday",
+ "time/timegm", "time/timespec_get", "nptl/libc_pthread_init",
+ "nptl/register-atfork",
+ // In Tizen, ASan tries to write error log to procfs, in case procfs is not
+ // mounted, this action will generate a kernel panic. So, just disable
+ // errors which happens because of dword-aligned reading by glibc.
+ "string"};
+
+static const char *asan_options[ASAN_OPTIONS_MAX] = {
+ "-fno-omit-frame-pointer", "-Wno-error", "-DSKIP_IFUNC",
+ "-fsanitize-recover=address", "-fsanitize=address", "--param",
+ "asan-use-after-return=0",
+};
+
+static const char *defs_option = "-Wl,-z,defs";
+static const size_t defs_option_length = 11;
+
+static bool AllowAsan(const char *output) {
+ if (!output)
+ return false;
+
+ std::string search_string(output);
+ if (!std::regex_match(search_string, std::regex(R"(/.*build/(.*).os$)")))
+ return false;
+
+ for (auto &b : blacklist) {
+ size_t found = search_string.find(b);
+ if (found != std::string::npos)
+ return false;
+ }
+ return true;
+}
+
+static const char *o(const int argc, const char **argv) {
+ for (int i = 1; i < argc; ++i) {
+ if (!argv[i])
+ break;
+ if (argv[i][0] == '-' && argv[i][1] == 'o')
+ return argv[i + 1];
+ }
+ return nullptr;
+}
+
+static void InitASanCommandLine(const int argc, const char **argv,
+ int &asan_argc, const char **&asan_argv) {
+ asan_argv = new const char *[asan_argc + 1];
+ if (!asan_argv) {
+#ifdef DEBUG
+ std::cerr << "Can't allocate memory " << std::endl;
+#endif
+ abort();
+ }
+ asan_argv[0] = GCC;
+ int index, asan_index;
+
+ for (asan_index = index = 1; index < argc; ++index) {
+ // Look for -Wl,-z,defs, because we don't need it
+ if (__builtin_strncmp(argv[index], defs_option, defs_option_length) == 0)
+ continue;
+ asan_argv[asan_index] = argv[index];
+ ++asan_index;
+ }
+ asan_argc = asan_index;
+}
+
+int main(int argc, const char **argv) {
+ if (argc == 1)
+ return 0;
+
+ const bool allow_asan = AllowAsan(o(argc, argv));
+ int options_count = allow_asan ? ASAN_OPTIONS_MAX : ASAN_OPTIONS_MIN;
+ int asan_argc = argc + options_count;
+
+ const char **asan_argv = NULL;
+ InitASanCommandLine(argc, argv, asan_argc, asan_argv);
+
+ // copy ASan options
+ __builtin_memcpy(asan_argv + asan_argc, asan_options,
+ sizeof(char *) * options_count);
+
+ asan_argv[asan_argc + options_count] = NULL;
+ execv(GCC, (char **)asan_argv);
+#ifdef DEBUG
+ std::cerr << "Errno: " << errno;
+#endif
+}
+
# published by the Open Source Initiative.
# This will avoid building some parts of glibc
+%define asan_arch x86_64 armv7l aarch64
%bcond_with fast_build
-
Name: glibc
Summary: Standard Shared Libraries (from the GNU C Library)
License: LGPL-2.1+ and LGPL-2.1+-with-GCC-exception and GPL-2.0+
BuildRequires: gcc-c++
BuildRequires: gettext-tools
BuildRequires: libstdc++-devel
+%{?asan:
+%ifarch %asan_arch
+#BuildRequires: python
+BuildRequires: glibc-devel-static
+%endif
+}
#BuildRequires: pkgconfig(systemd)
-
%define _filter_GLIBC_PRIVATE 1
%if %_target_cpu == "i686"
# For i686 let's only build what's different from i586, so
is no gurantee that they work for you, you should try to get newer
versions of your software.
+%{?asan:
+%ifarch %asan_arch
+%package asan
+Summary: glibc with ASan instrumentation
+License: LGPL-2.1+ and LGPL-2.1+-with-GCC-exception and GPL-2.0+
+Requires: glibc = %{version}
+
+%description asan
+This package provides glibc with ASan instrumentation
+
+%post asan
+ln -s --force /%{_lib}/libc-2.24-asan.so /%{_lib}/libc.so.6
+
+%preun asan
+ln -s --force /%{_lib}/libc-2.24.so /%{_lib}/libc.so.6
+
+%endif
+}
+
%prep
%setup -n glibc-%{version} -q
cp %{SOURCE1001} .
BuildCCplus="%__cxx"
add_ons=",libidn"
-
BuildFlags="$BuildFlags -g"
%if %{disable_assert}
BuildFlags="$BuildFlags -DNDEBUG=1"
mkdir "cc-$dirname"
cd "cc-$dirname"
conf_cflags="$cflags -funwind-tables -fPIC"
+ %{?asan: conf_cflags="$conf_cflags -DASAN_INIT_FIRST "}
profile="--disable-profile"
%if %{build_profile}
cd ..
}
+configure_flags="--prefix=/usr --without-cvs --without-selinux --enable-stackguard-randomization --enable-obsolete-rpc --disable-mathvec"
+
+configure_flags="$configure_flags --disable-sanity-checks"
+
+build_asan() {
+ HERE=%{_builddir}/%{name}-%{version}
+ J=${J:-32}
+ GLIBC=$HERE
+ ASAN_BUILD=$HERE/asan-build
+ unset CXXFLAGS
+ unset CFLAGS
+ unset LDFLAGS
+ #chmod +x $HERE/asan-glibc-gcc-wrapper.py
+ #build static wrapper, because we don't want asan to intercept allocators
+ g++ -o gcc-wrapper gcc-wrapper.cc -O3 -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-exceptions -static
+
+ rm -rf $ASAN_BUILD
+ mkdir -p $ASAN_BUILD
+ cd $ASAN_BUILD
+# CC=$HERE/asan-glibc-gcc-wrapper.py $GLIBC/configure $configure_flags
+ CC=$HERE/gcc-wrapper $GLIBC/configure $configure_flags
+
+ # Quick build - builds only libraries, but does not create symlinks.
+ make -r -j $J -C $GLIBC objdir=`pwd` subdir_lib
+ #make -r -j $J -C $GLIBC objdir=`pwd` install_root=$ASAN_INST install-lib
+ cd $HERE
+}
+
#
# Build base glibc
#
configure_and_build_glibc base "$BuildFlags" "$add_ons"
+%{?asan:
+%ifarch %{asan_arch}
+build_asan
+%endif
+}
#
# Build html documentation
#
###
#######################################################################
+%{?asan:
+%ifarch %{asan_arch}
+HERE=%{_builddir}/%{name}-%{version}
+GLIBC=$HERE
+ASAN_BUILD=$HERE/asan-build
+cp $ASAN_BUILD/libc.so %{buildroot}/%{_lib}/libc-2.24-asan.so
+%endif
+}
+
%post -p %{_sbindir}/glibc_post_upgrade
%postun -p /sbin/ldconfig
%endif
%{_libdir}/gconv
+%{?asan:
+%ifarch %{asan_arch}
+%files asan
+%manifest %{name}.manifest
+/%{_lib}/libc-2.24-asan.so
+%endif
+}
+
%files devel
%manifest %{name}.manifest
%defattr(-,root,root)
/* Incremented whenever something may have been added to dl_loaded. */
EXTERN unsigned long long _dl_load_adds;
+#ifdef ASAN_INIT_FIRST
+ EXTERN struct initfirst_list
+ {
+ struct link_map *l_map;
+ struct initfirst_list *next;
+ } *_dl_initfirst;
+#else
/* The object to be initialized first. */
EXTERN struct link_map *_dl_initfirst;
+#endif
/* Map of shared object to be profiled. */
EXTERN struct link_map *_dl_profile_map;