elf: Avoid unnecessary slowdown from profiling with audit (BZ#15533)

The rtld-audit interfaces introduces a slowdown due to enabling
profiling instrumentation (as if LD_AUDIT implied LD_PROFILE).
However, instrumenting is only necessary if one of audit libraries
provides PLT callbacks (la_pltenter or la_pltexit symbols).  Otherwise,
the slowdown can be avoided.

The following patch adjusts the logic that enables profiling to iterate
over all audit modules and check if any of those provides a PLT hook.
To keep la_symbind to work even without PLT callbacks, _dl_fixup now
calls the audit callback if the modules implements it.

Co-authored-by: Alexander Monakov <amonakov@ispras.ru>

Checked on x86_64-linux-gnu, i686-linux-gnu, and aarch64-linux-gnu.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
(cherry picked from commit 063f9ba220)

Resolved conflicts:
	NEWS
	elf/Makefile
This commit is contained in:
Adhemerval Zanella 2021-06-30 10:24:09 -03:00 committed by Carlos O'Donell
parent a8e211daea
commit 29496b3103
11 changed files with 295 additions and 9 deletions

5
NEWS
View File

@ -7,6 +7,11 @@ using `glibc' in the "product" field.
Version 2.34.1 Version 2.34.1
Major new features:
* The audit libraries will avoid unnecessary slowdown if it is not required
PLT tracking (by not implementing the la_pltenter or la_pltexit callbacks).
Security related changes: Security related changes:
CVE-2022-23219: Passing an overlong file name to the clnt_create CVE-2022-23219: Passing an overlong file name to the clnt_create

View File

@ -354,6 +354,7 @@ tests += \
tst-audit16 \ tst-audit16 \
tst-audit17 \ tst-audit17 \
tst-audit18 \ tst-audit18 \
tst-audit19b \
tst-auditmany \ tst-auditmany \
tst-auxobj \ tst-auxobj \
tst-auxobj-dlopen \ tst-auxobj-dlopen \
@ -441,6 +442,7 @@ tests-internal += \
neededtest2 \ neededtest2 \
neededtest3 \ neededtest3 \
neededtest4 \ neededtest4 \
tst-audit19a \
tst-create_format1 \ tst-create_format1 \
tst-dl-hwcaps_split \ tst-dl-hwcaps_split \
tst-dlmopen2 \ tst-dlmopen2 \
@ -610,6 +612,7 @@ modules-names = \
tst-audit12mod3 \ tst-audit12mod3 \
tst-audit13mod1 \ tst-audit13mod1 \
tst-audit18mod \ tst-audit18mod \
tst-audit19bmod \
tst-auditlogmod-1 \ tst-auditlogmod-1 \
tst-auditlogmod-2 \ tst-auditlogmod-2 \
tst-auditlogmod-3 \ tst-auditlogmod-3 \
@ -628,6 +631,8 @@ modules-names = \
tst-auditmod11 \ tst-auditmod11 \
tst-auditmod12 \ tst-auditmod12 \
tst-auditmod18 \ tst-auditmod18 \
tst-auditmod19a \
tst-auditmod19b \
tst-auxvalmod \ tst-auxvalmod \
tst-big-note-lib \ tst-big-note-lib \
tst-deep1mod1 \ tst-deep1mod1 \
@ -1963,6 +1968,13 @@ $(objpfx)tst-audit18.out: $(objpfx)tst-auditmod18.so \
$(objpfx)tst-audit18mod.so $(objpfx)tst-audit18mod.so
tst-audit18-ARGS = -- $(host-test-program-cmd) tst-audit18-ARGS = -- $(host-test-program-cmd)
$(objpfx)tst-audit19a.out: $(objpfx)tst-auditmod19a.so
tst-audit19a-ENV = LD_AUDIT=$(objpfx)tst-auditmod19a.so
$(objpfx)tst-audit19b.out: $(objpfx)tst-auditmod19b.so
$(objpfx)tst-audit19b: $(objpfx)tst-audit19bmod.so
tst-audit19b-ARGS = -- $(host-test-program-cmd)
# tst-sonamemove links against an older implementation of the library. # tst-sonamemove links against an older implementation of the library.
LDFLAGS-tst-sonamemove-linkmod1.so = \ LDFLAGS-tst-sonamemove-linkmod1.so = \
-Wl,--version-script=tst-sonamemove-linkmod1.map \ -Wl,--version-script=tst-sonamemove-linkmod1.map \

View File

@ -205,12 +205,28 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
int skip_ifunc = reloc_mode & __RTLD_NOIFUNC; int skip_ifunc = reloc_mode & __RTLD_NOIFUNC;
#ifdef SHARED #ifdef SHARED
bool consider_symbind = false;
/* If we are auditing, install the same handlers we need for profiling. */ /* If we are auditing, install the same handlers we need for profiling. */
if ((reloc_mode & __RTLD_AUDIT) == 0) if ((reloc_mode & __RTLD_AUDIT) == 0)
consider_profiling |= GLRO(dl_audit) != NULL; {
struct audit_ifaces *afct = GLRO(dl_audit);
for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
{
/* Profiling is needed only if PLT hooks are provided. */
if (afct->ARCH_LA_PLTENTER != NULL
|| afct->ARCH_LA_PLTEXIT != NULL)
consider_profiling = 1;
if (afct->symbind != NULL)
consider_symbind = true;
afct = afct->next;
}
}
#elif defined PROF #elif defined PROF
/* Never use dynamic linker profiling for gprof profiling code. */ /* Never use dynamic linker profiling for gprof profiling code. */
# define consider_profiling 0 # define consider_profiling 0
#else
# define consider_symbind 0
#endif #endif
if (l->l_relocated) if (l->l_relocated)
@ -272,7 +288,7 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc); ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc);
#ifndef PROF #ifndef PROF
if (__glibc_unlikely (consider_profiling) if ((consider_profiling || consider_symbind)
&& l->l_info[DT_PLTRELSZ] != NULL) && l->l_info[DT_PLTRELSZ] != NULL)
{ {
/* Allocate the array which will contain the already found /* Allocate the array which will contain the already found

View File

@ -124,6 +124,37 @@ _dl_fixup (
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
#ifdef SHARED
/* Auditing checkpoint: we have a new binding. Provide the auditing
libraries the possibility to change the value and tell us whether further
auditing is wanted.
The l_reloc_result is only allocated if there is an audit module which
provides a la_symbind. */
if (l->l_reloc_result != NULL)
{
/* This is the address in the array where we store the result of previous
relocations. */
struct reloc_result *reloc_result
= &l->l_reloc_result[reloc_index (pltgot, reloc_arg, sizeof (PLTREL))];
unsigned int init = atomic_load_acquire (&reloc_result->init);
if (init == 0)
{
_dl_audit_symbind (l, reloc_result, sym, &value, result);
/* Store the result for later runs. */
if (__glibc_likely (! GLRO(dl_bind_not)))
{
reloc_result->addr = value;
/* Guarantee all previous writes complete before init is
updated. See CONCURRENCY NOTES below. */
atomic_store_release (&reloc_result->init, 1);
}
}
else
value = reloc_result->addr;
}
#endif
/* Finally, fix up the plt itself. */ /* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not))) if (__glibc_unlikely (GLRO(dl_bind_not)))
return value; return value;

View File

@ -1015,13 +1015,7 @@ ERROR: audit interface '%s' requires version %d (maximum supported version %d);
"la_objsearch\0" "la_objsearch\0"
"la_objopen\0" "la_objopen\0"
"la_preinit\0" "la_preinit\0"
#if __ELF_NATIVE_CLASS == 32 LA_SYMBIND "\0"
"la_symbind32\0"
#elif __ELF_NATIVE_CLASS == 64
"la_symbind64\0"
#else
# error "__ELF_NATIVE_CLASS must be defined"
#endif
#define STRING(s) __STRING (s) #define STRING(s) __STRING (s)
"la_" STRING (ARCH_LA_PLTENTER) "\0" "la_" STRING (ARCH_LA_PLTENTER) "\0"
"la_" STRING (ARCH_LA_PLTEXIT) "\0" "la_" STRING (ARCH_LA_PLTEXIT) "\0"

38
elf/tst-audit19a.c Normal file
View File

@ -0,0 +1,38 @@
/* Check if DT_AUDIT a module without la_plt{enter,exit} symbols does not incur
in profiling (BZ#15533).
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <link.h>
#include <support/xdlfcn.h>
#include <support/check.h>
static int
do_test (void)
{
void *h = xdlopen ("tst-auditmod19a.so", RTLD_NOW);
struct link_map *lmap;
TEST_VERIFY_EXIT (dlinfo (h, RTLD_DI_LINKMAP, &lmap) == 0);
/* The internal array is only allocated if profiling is enabled. */
TEST_VERIFY (lmap->l_reloc_result == NULL);
return 0;
}
#include <support/test-driver.c>

94
elf/tst-audit19b.c Normal file
View File

@ -0,0 +1,94 @@
/* Check if DT_AUDIT a module with la_plt{enter,exit} call la_symbind
for lazy resolution.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <getopt.h>
#include <support/capture_subprocess.h>
#include <support/check.h>
#include <support/xstdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
static int restart;
#define CMDLINE_OPTIONS \
{ "restart", no_argument, &restart, 1 },
int tst_audit18bmod1_func (void);
static int
handle_restart (void)
{
TEST_COMPARE (tst_audit18bmod1_func (), 10);
return 0;
}
static inline bool
startswith (const char *str, const char *pre)
{
size_t lenpre = strlen (pre);
size_t lenstr = strlen (str);
return lenstr < lenpre ? false : memcmp (pre, str, lenpre) == 0;
}
static int
do_test (int argc, char *argv[])
{
/* We must have either:
- One our fource parameters left if called initially:
+ path to ld.so optional
+ "--library-path" optional
+ the library path optional
+ the application name */
if (restart)
return handle_restart ();
char *spargv[9];
int i = 0;
for (; i < argc - 1; i++)
spargv[i] = argv[i + 1];
spargv[i++] = (char *) "--direct";
spargv[i++] = (char *) "--restart";
spargv[i] = NULL;
setenv ("LD_AUDIT", "tst-auditmod18b.so", 0);
struct support_capture_subprocess result
= support_capture_subprogram (spargv[0], spargv);
support_capture_subprocess_check (&result, "tst-audit18b", 0, sc_allow_stderr);
bool find_symbind = false;
FILE *out = fmemopen (result.err.buffer, result.err.length, "r");
TEST_VERIFY (out != NULL);
char *buffer = NULL;
size_t buffer_length = 0;
while (xgetline (&buffer, &buffer_length, out))
if (startswith (buffer, "la_symbind: tst_audit18bmod1_func") == 0)
find_symbind = true;
TEST_COMPARE (find_symbind, true);
free (buffer);
xfclose (out);
return 0;
}
#define TEST_FUNCTION_ARGV do_test
#include <support/test-driver.c>

23
elf/tst-audit19bmod.c Normal file
View File

@ -0,0 +1,23 @@
/* Extra module for tst-audit18b.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
int
tst_audit18bmod1_func (void)
{
return 10;
}

25
elf/tst-auditmod19a.c Normal file
View File

@ -0,0 +1,25 @@
/* Audit module for tst-audit18a.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <link.h>
unsigned int
la_version (unsigned int version)
{
return LAV_CURRENT;
}

46
elf/tst-auditmod19b.c Normal file
View File

@ -0,0 +1,46 @@
/* Audit module for tst-audit18b.
Copyright (C) 2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <link.h>
#include <string.h>
#include <stdio.h>
unsigned int
la_version (unsigned int version)
{
return LAV_CURRENT;
}
unsigned int
la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
{
return LA_FLG_BINDTO | LA_FLG_BINDFROM;
}
uintptr_t
#if __ELF_NATIVE_CLASS == 32
la_symbind32 (Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
uintptr_t *defcook, unsigned int *flags, const char *symname)
#else
la_symbind64 (Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook,
uintptr_t *defcook, unsigned int *flags, const char *symname)
#endif
{
fprintf (stderr, "la_symbind: %s\n", symname);
return sym->st_value;
}

View File

@ -358,8 +358,10 @@ struct auditstate
#if __ELF_NATIVE_CLASS == 32 #if __ELF_NATIVE_CLASS == 32
# define symbind symbind32 # define symbind symbind32
# define LA_SYMBIND "la_symbind32"
#elif __ELF_NATIVE_CLASS == 64 #elif __ELF_NATIVE_CLASS == 64
# define symbind symbind64 # define symbind symbind64
# define LA_SYMBIND "la_symbind64"
#else #else
# error "__ELF_NATIVE_CLASS must be defined" # error "__ELF_NATIVE_CLASS must be defined"
#endif #endif