[ASan] Enable build with ASan.
authorDenis Khalikov <d.khalikov@partner.samsung.com>
Fri, 8 Sep 2017 14:38:06 +0000 (17:38 +0300)
committerDongkyun Son <dongkyun.s@samsung.com>
Wed, 3 May 2023 10:47:28 +0000 (19:47 +0900)
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

asan-glibc-gcc-wrapper.py [new file with mode: 0755]
elf/dl-init.c
elf/dl-load.c
elf/dl-support.c
gcc-wrapper.cc [new file with mode: 0644]
packaging/glibc.spec
sysdeps/generic/ldsodefs.h

diff --git a/asan-glibc-gcc-wrapper.py b/asan-glibc-gcc-wrapper.py
new file mode 100755 (executable)
index 0000000..8b389cc
--- /dev/null
@@ -0,0 +1,53 @@
+#!/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)
index 5b07325..be76fea 100644 (file)
@@ -81,7 +81,24 @@ _dl_init (struct link_map *main_map, int argc, char **argv, char **env)
 
   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;
     }
 
index fcb39a7..e2e6e79 100644 (file)
@@ -1443,7 +1443,34 @@ cannot enable executable stack as shared object requires");
 
   /* 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;
index 9714f75..de7e14e 100644 (file)
@@ -151,8 +151,13 @@ struct r_search_path_elem *_dl_all_dirs;
 /* 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;
diff --git a/gcc-wrapper.cc b/gcc-wrapper.cc
new file mode 100644 (file)
index 0000000..3ab9323
--- /dev/null
@@ -0,0 +1,101 @@
+#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
+}
+
index ce6ba0d..fd7882a 100644 (file)
@@ -13,8 +13,8 @@
 # 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+
@@ -25,8 +25,13 @@ BuildRequires:  xz
 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
@@ -197,6 +202,25 @@ binaries working, but since this libraries are not supported and there
 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} .
@@ -237,7 +261,6 @@ BuildCC="%__cc"
 BuildCCplus="%__cxx"
 add_ons=",libidn"
 
-
 BuildFlags="$BuildFlags -g"
 %if %{disable_assert}
        BuildFlags="$BuildFlags -DNDEBUG=1"
@@ -260,6 +283,7 @@ configure_and_build_glibc() {
        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}
@@ -293,11 +317,44 @@ configure_and_build_glibc() {
        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
 #
@@ -472,6 +529,15 @@ ln -s %{_libexecdir}/getconf/getconf %{buildroot}%{_bindir}/getconf
 ###
 #######################################################################
 
+%{?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
@@ -617,6 +683,14 @@ done
 %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)
index c99dad7..35a8b3d 100644 (file)
@@ -380,8 +380,16 @@ struct rtld_global
   /* 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;