|
|
8a8cfb |
commit f8ed116aa574435c6e28260f21963233682d3b57
|
|
|
8a8cfb |
Author: Florian Weimer <fweimer@redhat.com>
|
|
|
8a8cfb |
Date: Fri Dec 13 10:18:46 2019 +0100
|
|
|
8a8cfb |
|
|
|
8a8cfb |
dlopen: Rework handling of pending NODELETE status
|
|
|
8a8cfb |
|
|
|
8a8cfb |
Commit a2e8aa0d9ea648068d8be52dd7b15f1b6a008e23 ("Block signals during
|
|
|
8a8cfb |
the initial part of dlopen") was deemed necessary because of
|
|
|
8a8cfb |
read-modify-write operations like the one in add_dependency in
|
|
|
8a8cfb |
elf/dl-lookup.c. In the old code, we check for any kind of NODELETE
|
|
|
8a8cfb |
status and bail out:
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Redo the NODELETE check, as when dl_load_lock wasn't held
|
|
|
8a8cfb |
yet this could have changed. */
|
|
|
8a8cfb |
if (map->l_nodelete != link_map_nodelete_inactive)
|
|
|
8a8cfb |
goto out;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
And then set pending status (during relocation):
|
|
|
8a8cfb |
|
|
|
8a8cfb |
if (flags & DL_LOOKUP_FOR_RELOCATE)
|
|
|
8a8cfb |
map->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
else
|
|
|
8a8cfb |
map->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
If a signal arrives during relocation and the signal handler, through
|
|
|
8a8cfb |
lazy binding, adds a global scope dependency on the same map, it will
|
|
|
8a8cfb |
set map->l_nodelete to link_map_nodelete_active. This will be
|
|
|
8a8cfb |
overwritten with link_map_nodelete_pending by the dlopen relocation
|
|
|
8a8cfb |
code.
|
|
|
8a8cfb |
|
|
|
8a8cfb |
To avoid such problems in relation to the l_nodelete member, this
|
|
|
8a8cfb |
commit introduces two flags for active NODELETE status (irrevocable)
|
|
|
8a8cfb |
and pending NODELETE status (revocable until activate_nodelete is
|
|
|
8a8cfb |
invoked). As a result, NODELETE processing in dlopen does not
|
|
|
8a8cfb |
introduce further reasons why lazy binding from signal handlers
|
|
|
8a8cfb |
is unsafe during dlopen, and a subsequent commit can remove signal
|
|
|
8a8cfb |
blocking from dlopen.
|
|
|
8a8cfb |
|
|
|
8a8cfb |
This does not address pre-existing issues (unrelated to the NODELETE
|
|
|
8a8cfb |
changes) which make lazy binding in a signal handler during dlopen
|
|
|
8a8cfb |
unsafe, such as the use of malloc in both cases.
|
|
|
8a8cfb |
|
|
|
8a8cfb |
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
|
|
8a8cfb |
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
|
|
|
8a8cfb |
|
|
|
8a8cfb |
diff --git a/elf/dl-close.c b/elf/dl-close.c
|
|
|
8a8cfb |
index 243a028c443173c1..fa7f3e8174576e46 100644
|
|
|
8a8cfb |
--- a/elf/dl-close.c
|
|
|
8a8cfb |
+++ b/elf/dl-close.c
|
|
|
8a8cfb |
@@ -197,7 +197,7 @@ _dl_close_worker (struct link_map *map, bool force)
|
|
|
8a8cfb |
/* Check whether this object is still used. */
|
|
|
8a8cfb |
if (l->l_type == lt_loaded
|
|
|
8a8cfb |
&& l->l_direct_opencount == 0
|
|
|
8a8cfb |
- && l->l_nodelete != link_map_nodelete_active
|
|
|
8a8cfb |
+ && !l->l_nodelete_active
|
|
|
8a8cfb |
/* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why
|
|
|
8a8cfb |
acquire is sufficient and correct. */
|
|
|
8a8cfb |
&& atomic_load_acquire (&l->l_tls_dtor_count) == 0
|
|
|
8a8cfb |
@@ -279,8 +279,7 @@ _dl_close_worker (struct link_map *map, bool force)
|
|
|
8a8cfb |
|
|
|
8a8cfb |
if (!used[i])
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
- assert (imap->l_type == lt_loaded
|
|
|
8a8cfb |
- && imap->l_nodelete != link_map_nodelete_active);
|
|
|
8a8cfb |
+ assert (imap->l_type == lt_loaded && !imap->l_nodelete_active);
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Call its termination function. Do not do it for
|
|
|
8a8cfb |
half-cooked objects. Temporarily disable exception
|
|
|
8a8cfb |
@@ -820,7 +819,7 @@ _dl_close (void *_map)
|
|
|
8a8cfb |
before we took the lock. There is no way to detect this (see below)
|
|
|
8a8cfb |
so we proceed assuming this isn't the case. First see whether we
|
|
|
8a8cfb |
can remove the object at all. */
|
|
|
8a8cfb |
- if (__glibc_unlikely (map->l_nodelete == link_map_nodelete_active))
|
|
|
8a8cfb |
+ if (__glibc_unlikely (map->l_nodelete_active))
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
/* Nope. Do nothing. */
|
|
|
8a8cfb |
__rtld_lock_unlock_recursive (GL(dl_load_lock));
|
|
|
8a8cfb |
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
|
|
|
8a8cfb |
index 35a3f96a6296294a..01724a54f8840f9f 100644
|
|
|
8a8cfb |
--- a/elf/dl-lookup.c
|
|
|
8a8cfb |
+++ b/elf/dl-lookup.c
|
|
|
8a8cfb |
@@ -187,6 +187,28 @@ enter_unique_sym (struct unique_sym *table, size_t size,
|
|
|
8a8cfb |
table[idx].map = map;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
|
|
|
8a8cfb |
+/* Mark MAP as NODELETE according to the lookup mode in FLAGS. During
|
|
|
8a8cfb |
+ initial relocation, NODELETE state is pending only. */
|
|
|
8a8cfb |
+static void
|
|
|
8a8cfb |
+mark_nodelete (struct link_map *map, int flags)
|
|
|
8a8cfb |
+{
|
|
|
8a8cfb |
+ if (flags & DL_LOOKUP_FOR_RELOCATE)
|
|
|
8a8cfb |
+ map->l_nodelete_pending = true;
|
|
|
8a8cfb |
+ else
|
|
|
8a8cfb |
+ map->l_nodelete_active = true;
|
|
|
8a8cfb |
+}
|
|
|
8a8cfb |
+
|
|
|
8a8cfb |
+/* Return true if MAP is marked as NODELETE according to the lookup
|
|
|
8a8cfb |
+ mode in FLAGS> */
|
|
|
8a8cfb |
+static bool
|
|
|
8a8cfb |
+is_nodelete (struct link_map *map, int flags)
|
|
|
8a8cfb |
+{
|
|
|
8a8cfb |
+ /* Non-pending NODELETE always counts. Pending NODELETE only counts
|
|
|
8a8cfb |
+ during initial relocation processing. */
|
|
|
8a8cfb |
+ return map->l_nodelete_active
|
|
|
8a8cfb |
+ || ((flags & DL_LOOKUP_FOR_RELOCATE) && map->l_nodelete_pending);
|
|
|
8a8cfb |
+}
|
|
|
8a8cfb |
+
|
|
|
8a8cfb |
/* Utility function for do_lookup_x. Lookup an STB_GNU_UNIQUE symbol
|
|
|
8a8cfb |
in the unique symbol table, creating a new entry if necessary.
|
|
|
8a8cfb |
Return the matching symbol in RESULT. */
|
|
|
8a8cfb |
@@ -311,8 +333,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
|
|
|
8a8cfb |
enter_unique_sym (entries, size,
|
|
|
8a8cfb |
new_hash, strtab + sym->st_name, sym, map);
|
|
|
8a8cfb |
|
|
|
8a8cfb |
- if (map->l_type == lt_loaded
|
|
|
8a8cfb |
- && map->l_nodelete == link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ if (map->l_type == lt_loaded && !is_nodelete (map, flags))
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
/* Make sure we don't unload this object by
|
|
|
8a8cfb |
setting the appropriate flag. */
|
|
|
8a8cfb |
@@ -320,10 +341,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
|
|
|
8a8cfb |
_dl_debug_printf ("\
|
|
|
8a8cfb |
marking %s [%lu] as NODELETE due to unique symbol\n",
|
|
|
8a8cfb |
map->l_name, map->l_ns);
|
|
|
8a8cfb |
- if (flags & DL_LOOKUP_FOR_RELOCATE)
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
- else
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ mark_nodelete (map, flags);
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
++tab->n_elements;
|
|
|
8a8cfb |
@@ -586,7 +604,7 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
|
|
|
8a8cfb |
dependencies may pick an dependency which can be dlclose'd, but
|
|
|
8a8cfb |
such IFUNC resolvers are undefined anyway. */
|
|
|
8a8cfb |
assert (map->l_type == lt_loaded);
|
|
|
8a8cfb |
- if (map->l_nodelete != link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ if (is_nodelete (map, flags))
|
|
|
8a8cfb |
return 0;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
struct link_map_reldeps *l_reldeps
|
|
|
8a8cfb |
@@ -694,17 +712,16 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Redo the NODELETE check, as when dl_load_lock wasn't held
|
|
|
8a8cfb |
yet this could have changed. */
|
|
|
8a8cfb |
- if (map->l_nodelete != link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ if (is_nodelete (map, flags))
|
|
|
8a8cfb |
goto out;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* If the object with the undefined reference cannot be removed ever
|
|
|
8a8cfb |
just make sure the same is true for the object which contains the
|
|
|
8a8cfb |
definition. */
|
|
|
8a8cfb |
- if (undef_map->l_type != lt_loaded
|
|
|
8a8cfb |
- || (undef_map->l_nodelete != link_map_nodelete_inactive))
|
|
|
8a8cfb |
+ if (undef_map->l_type != lt_loaded || is_nodelete (map, flags))
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
|
|
|
8a8cfb |
- && map->l_nodelete == link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ && !is_nodelete (map, flags))
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
if (undef_map->l_name[0] == '\0')
|
|
|
8a8cfb |
_dl_debug_printf ("\
|
|
|
8a8cfb |
@@ -716,11 +733,7 @@ marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
|
|
|
8a8cfb |
map->l_name, map->l_ns,
|
|
|
8a8cfb |
undef_map->l_name, undef_map->l_ns);
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
-
|
|
|
8a8cfb |
- if (flags & DL_LOOKUP_FOR_RELOCATE)
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
- else
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ mark_nodelete (map, flags);
|
|
|
8a8cfb |
goto out;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
|
|
|
8a8cfb |
@@ -746,17 +759,14 @@ marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
|
|
|
8a8cfb |
cannot be unloaded. This is semantically the correct
|
|
|
8a8cfb |
behavior. */
|
|
|
8a8cfb |
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
|
|
|
8a8cfb |
- && map->l_nodelete == link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ && !is_nodelete (map, flags))
|
|
|
8a8cfb |
_dl_debug_printf ("\
|
|
|
8a8cfb |
marking %s [%lu] as NODELETE due to memory allocation failure\n",
|
|
|
8a8cfb |
map->l_name, map->l_ns);
|
|
|
8a8cfb |
- if (flags & DL_LOOKUP_FOR_RELOCATE)
|
|
|
8a8cfb |
- /* In case of non-lazy binding, we could actually
|
|
|
8a8cfb |
- report the memory allocation error, but for now, we
|
|
|
8a8cfb |
- use the conservative approximation as well. */
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
- else
|
|
|
8a8cfb |
- map->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ /* In case of non-lazy binding, we could actually report
|
|
|
8a8cfb |
+ the memory allocation error, but for now, we use the
|
|
|
8a8cfb |
+ conservative approximation as well. */
|
|
|
8a8cfb |
+ mark_nodelete (map, flags);
|
|
|
8a8cfb |
goto out;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
else
|
|
|
8a8cfb |
diff --git a/elf/dl-open.c b/elf/dl-open.c
|
|
|
8a8cfb |
index c7ed85b7ee99a296..a382bfae8aa3a2f8 100644
|
|
|
8a8cfb |
--- a/elf/dl-open.c
|
|
|
8a8cfb |
+++ b/elf/dl-open.c
|
|
|
8a8cfb |
@@ -440,13 +440,17 @@ activate_nodelete (struct link_map *new)
|
|
|
8a8cfb |
NODELETE status for objects outside the local scope. */
|
|
|
8a8cfb |
for (struct link_map *l = GL (dl_ns)[new->l_ns]._ns_loaded; l != NULL;
|
|
|
8a8cfb |
l = l->l_next)
|
|
|
8a8cfb |
- if (l->l_nodelete == link_map_nodelete_pending)
|
|
|
8a8cfb |
+ if (l->l_nodelete_pending)
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
|
|
|
8a8cfb |
_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
|
|
|
8a8cfb |
l->l_name, l->l_ns);
|
|
|
8a8cfb |
|
|
|
8a8cfb |
- l->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ l->l_nodelete_active = true;
|
|
|
8a8cfb |
+
|
|
|
8a8cfb |
+ /* This is just a debugging aid, to indicate that
|
|
|
8a8cfb |
+ activate_nodelete has run for this map. */
|
|
|
8a8cfb |
+ l->l_nodelete_pending = false;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
|
|
|
8a8cfb |
@@ -549,10 +553,10 @@ dl_open_worker (void *a)
|
|
|
8a8cfb |
if (__glibc_unlikely (mode & RTLD_NODELETE))
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES)
|
|
|
8a8cfb |
- && new->l_nodelete == link_map_nodelete_inactive)
|
|
|
8a8cfb |
+ && !new->l_nodelete_active)
|
|
|
8a8cfb |
_dl_debug_printf ("marking %s [%lu] as NODELETE\n",
|
|
|
8a8cfb |
new->l_name, new->l_ns);
|
|
|
8a8cfb |
- new->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ new->l_nodelete_active = true;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Finalize the addition to the global scope. */
|
|
|
8a8cfb |
@@ -568,7 +572,7 @@ dl_open_worker (void *a)
|
|
|
8a8cfb |
/* Schedule NODELETE marking for the directly loaded object if
|
|
|
8a8cfb |
requested. */
|
|
|
8a8cfb |
if (__glibc_unlikely (mode & RTLD_NODELETE))
|
|
|
8a8cfb |
- new->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
+ new->l_nodelete_pending = true;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Load that object's dependencies. */
|
|
|
8a8cfb |
_dl_map_object_deps (new, NULL, 0, 0,
|
|
|
8a8cfb |
@@ -680,7 +684,7 @@ dl_open_worker (void *a)
|
|
|
8a8cfb |
_dl_start_profile ();
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Prevent unloading the object. */
|
|
|
8a8cfb |
- GL(dl_profile_map)->l_nodelete = link_map_nodelete_active;
|
|
|
8a8cfb |
+ GL(dl_profile_map)->l_nodelete_active = true;
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
else
|
|
|
8a8cfb |
@@ -879,9 +883,9 @@ no more namespaces available for dlmopen()"));
|
|
|
8a8cfb |
happens inside dl_open_worker. */
|
|
|
8a8cfb |
__libc_signal_restore_set (&args.original_signal_mask);
|
|
|
8a8cfb |
|
|
|
8a8cfb |
- /* All link_map_nodelete_pending objects should have been
|
|
|
8a8cfb |
- deleted at this point, which is why it is not necessary
|
|
|
8a8cfb |
- to reset the flag here. */
|
|
|
8a8cfb |
+ /* All l_nodelete_pending objects should have been deleted
|
|
|
8a8cfb |
+ at this point, which is why it is not necessary to reset
|
|
|
8a8cfb |
+ the flag here. */
|
|
|
8a8cfb |
}
|
|
|
8a8cfb |
else
|
|
|
8a8cfb |
__libc_signal_restore_set (&args.original_signal_mask);
|
|
|
8a8cfb |
diff --git a/elf/get-dynamic-info.h b/elf/get-dynamic-info.h
|
|
|
8a8cfb |
index ea286abaea0128d1..78ba7e76db9706cc 100644
|
|
|
8a8cfb |
--- a/elf/get-dynamic-info.h
|
|
|
8a8cfb |
+++ b/elf/get-dynamic-info.h
|
|
|
8a8cfb |
@@ -164,7 +164,7 @@ elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
|
|
|
8a8cfb |
{
|
|
|
8a8cfb |
l->l_flags_1 = info[VERSYMIDX (DT_FLAGS_1)]->d_un.d_val;
|
|
|
8a8cfb |
if (l->l_flags_1 & DF_1_NODELETE)
|
|
|
8a8cfb |
- l->l_nodelete = link_map_nodelete_pending;
|
|
|
8a8cfb |
+ l->l_nodelete_pending = true;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
/* Only DT_1_SUPPORTED_MASK bits are supported, and we would like
|
|
|
8a8cfb |
to assert this, but we can't. Users have been setting
|
|
|
8a8cfb |
diff --git a/include/link.h b/include/link.h
|
|
|
8a8cfb |
index a277b77cad6b52b1..e90fa79a0b332087 100644
|
|
|
8a8cfb |
--- a/include/link.h
|
|
|
8a8cfb |
+++ b/include/link.h
|
|
|
8a8cfb |
@@ -79,22 +79,6 @@ struct r_search_path_struct
|
|
|
8a8cfb |
int malloced;
|
|
|
8a8cfb |
};
|
|
|
8a8cfb |
|
|
|
8a8cfb |
-/* Type used by the l_nodelete member. */
|
|
|
8a8cfb |
-enum link_map_nodelete
|
|
|
8a8cfb |
-{
|
|
|
8a8cfb |
- /* This link map can be deallocated. */
|
|
|
8a8cfb |
- link_map_nodelete_inactive = 0, /* Zero-initialized in _dl_new_object. */
|
|
|
8a8cfb |
-
|
|
|
8a8cfb |
- /* This link map cannot be deallocated. */
|
|
|
8a8cfb |
- link_map_nodelete_active,
|
|
|
8a8cfb |
-
|
|
|
8a8cfb |
- /* This link map cannot be deallocated after dlopen has succeded.
|
|
|
8a8cfb |
- dlopen turns this into link_map_nodelete_active. dlclose treats
|
|
|
8a8cfb |
- this intermediate state as link_map_nodelete_active. */
|
|
|
8a8cfb |
- link_map_nodelete_pending,
|
|
|
8a8cfb |
-};
|
|
|
8a8cfb |
-
|
|
|
8a8cfb |
-
|
|
|
8a8cfb |
/* Structure describing a loaded shared object. The `l_next' and `l_prev'
|
|
|
8a8cfb |
members form a chain of all the shared objects loaded at startup.
|
|
|
8a8cfb |
|
|
|
8a8cfb |
@@ -218,10 +202,17 @@ struct link_map
|
|
|
8a8cfb |
freed, ie. not allocated with
|
|
|
8a8cfb |
the dummy malloc in ld.so. */
|
|
|
8a8cfb |
|
|
|
8a8cfb |
- /* Actually of type enum link_map_nodelete. Separate byte due to
|
|
|
8a8cfb |
- a read in add_dependency in elf/dl-lookup.c outside the loader
|
|
|
8a8cfb |
- lock. Only valid for l_type == lt_loaded. */
|
|
|
8a8cfb |
- unsigned char l_nodelete;
|
|
|
8a8cfb |
+ /* NODELETE status of the map. Only valid for maps of type
|
|
|
8a8cfb |
+ lt_loaded. Lazy binding sets l_nodelete_active directly,
|
|
|
8a8cfb |
+ potentially from signal handlers. Initial loading of an
|
|
|
8a8cfb |
+ DF_1_NODELETE object set l_nodelete_pending. Relocation may
|
|
|
8a8cfb |
+ set l_nodelete_pending as well. l_nodelete_pending maps are
|
|
|
8a8cfb |
+ promoted to l_nodelete_active status in the final stages of
|
|
|
8a8cfb |
+ dlopen, prior to calling ELF constructors. dlclose only
|
|
|
8a8cfb |
+ refuses to unload l_nodelete_active maps, the pending status is
|
|
|
8a8cfb |
+ ignored. */
|
|
|
8a8cfb |
+ bool l_nodelete_active;
|
|
|
8a8cfb |
+ bool l_nodelete_pending;
|
|
|
8a8cfb |
|
|
|
8a8cfb |
#include <link_map.h>
|
|
|
8a8cfb |
|