Skip to content
This repository was archived by the owner on Feb 15, 2026. It is now read-only.
/ glibc Public archive

Commit 035c012

Browse files
committed
Reduce the statically linked startup code [BZ #23323]
It turns out the startup code in csu/elf-init.c has a perfect pair of ROP gadgets (see Marco-Gisbert and Ripoll-Ripoll, "return-to-csu: A New Method to Bypass 64-bit Linux ASLR"). These functions are not needed in dynamically-linked binaries because DT_INIT/DT_INIT_ARRAY are already processed by the dynamic linker. However, the dynamic linker skipped the main program for some reason. For maximum backwards compatibility, this is not changed, and instead, the main map is consulted from __libc_start_main if the init function argument is a NULL pointer. For statically linked binaries, the old approach based on linker symbols is still used because there is nothing else available. A new symbol version __libc_start_main@@GLIBC_2.34 is introduced because new binaries running on an old libc would not run their ELF constructors, leading to difficult-to-debug issues.
1 parent a79328c commit 035c012

File tree

63 files changed

+276
-315
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+276
-315
lines changed

csu/Makefile

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@ subdir := csu
2727
include ../Makeconfig
2828

2929
routines = init-first libc-start $(libc-init) sysdep version check_fds \
30-
libc-tls elf-init dso_handle
30+
libc-tls dso_handle
3131
aux = errno
3232
elide-routines.os = libc-tls
33-
static-only-routines = elf-init
3433
csu-dummies = $(filter-out $(start-installed-name),crt1.o Mcrt1.o)
3534
extra-objs = start.o \
3635
$(start-installed-name) g$(start-installed-name) $(csu-dummies) \
@@ -59,13 +58,6 @@ CFLAGS-.os += $(no-stack-protector)
5958
# but it does not matter for this source file.
6059
CFLAGS-static-reloc.os += $(stack-protector)
6160

62-
# This file is not actually part of the startup code in the nonshared
63-
# case and statically linked into applications. See
64-
# <https://sourceware.org/bugzilla/show_bug.cgi?id=23323>,
65-
# <https://sourceware.org/ml/libc-alpha/2018-06/msg00717.html>.
66-
# Also see the note above regarding STACK_PROTECTOR_LEVEL.
67-
CFLAGS-elf-init.oS += $(stack-protector)
68-
6961
ifeq (yes,$(build-shared))
7062
extra-objs += S$(start-installed-name) gmon-start.os
7163
ifneq ($(start-installed-name),$(static-start-installed-name))

csu/Versions

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ libc {
77
# New special glibc functions.
88
gnu_get_libc_release; gnu_get_libc_version;
99
}
10+
GLIBC_2.34 {
11+
__libc_start_main;
12+
}
1013
GLIBC_PRIVATE {
1114
errno;
1215
}

csu/elf-init.c

Lines changed: 0 additions & 107 deletions
This file was deleted.

csu/libc-start.c

Lines changed: 155 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/* Copyright (C) 1998-2021 Free Software Foundation, Inc.
1+
/* Perform initialization and invoke main.
2+
Copyright (C) 1998-2021 Free Software Foundation, Inc.
23
This file is part of the GNU C Library.
34
45
The GNU C Library is free software; you can redistribute it and/or
@@ -15,10 +16,15 @@
1516
License along with the GNU C Library; if not, see
1617
<https://www.gnu.org/licenses/>. */
1718

19+
/* Note: This code is only part of the startup code proper for
20+
statically linked binaries. For dynamically linked binaries, it
21+
resides in libc.so. */
22+
1823
/* Mark symbols hidden in static PIE for early self relocation to work. */
1924
#if BUILD_PIE_DEFAULT
2025
# pragma GCC visibility push(hidden)
2126
#endif
27+
2228
#include <assert.h>
2329
#include <stdlib.h>
2430
#include <stdio.h>
@@ -29,6 +35,8 @@
2935
#include <libc-internal.h>
3036
#include <elf/libc-early-init.h>
3137
#include <stdbool.h>
38+
#include <elf-initfini.h>
39+
#include <shlib-compat.h>
3240

3341
#include <elf/dl-tunables.h>
3442

@@ -95,9 +103,11 @@ apply_irel (void)
95103
# else
96104
# define STATIC static inline __attribute__ ((always_inline))
97105
# endif
106+
# define DO_DEFINE_LIBC_START_MAIN_VERSION 0
98107
#else
99108
# define STATIC
100-
# define LIBC_START_MAIN __libc_start_main
109+
# define LIBC_START_MAIN __libc_start_main_impl
110+
# define DO_DEFINE_LIBC_START_MAIN_VERSION 1
101111
#endif
102112

103113
#ifdef MAIN_AUXVEC_ARG
@@ -113,6 +123,92 @@ apply_irel (void)
113123
# define ARCH_INIT_CPU_FEATURES()
114124
#endif
115125

126+
#ifdef SHARED
127+
/* Initialization for dynamic executables. Find the main executable
128+
link map and run its init functions. */
129+
static void
130+
call_init (int argc, char **argv, char **env)
131+
{
132+
/* Obtain the main map of the executable. */
133+
struct link_map *l = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
134+
135+
/* DT_PREINIT_ARRAY is not processed here. It is already handled in
136+
_dl_init in elf/dl-init.c. Also see the call_init function in
137+
the same file. */
138+
139+
if (ELF_INITFINI && l->l_info[DT_INIT] != NULL)
140+
DL_CALL_DT_INIT(l, l->l_addr + l->l_info[DT_INIT]->d_un.d_ptr,
141+
argc, argv, env);
142+
143+
ElfW(Dyn) *init_array = l->l_info[DT_INIT_ARRAY];
144+
if (init_array != NULL)
145+
{
146+
unsigned int jm
147+
= l->l_info[DT_INIT_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr));
148+
ElfW(Addr) *addrs = (void *) (init_array->d_un.d_ptr + l->l_addr);
149+
for (unsigned int j = 0; j < jm; ++j)
150+
((dl_init_t) addrs[j]) (argc, argv, env);
151+
}
152+
}
153+
154+
#else /* !SHARED */
155+
156+
/* These magic symbols are provided by the linker. */
157+
extern void (*__preinit_array_start []) (int, char **, char **)
158+
attribute_hidden;
159+
extern void (*__preinit_array_end []) (int, char **, char **)
160+
attribute_hidden;
161+
extern void (*__init_array_start []) (int, char **, char **)
162+
attribute_hidden;
163+
extern void (*__init_array_end []) (int, char **, char **)
164+
attribute_hidden;
165+
extern void (*__fini_array_start []) (void) attribute_hidden;
166+
extern void (*__fini_array_end []) (void) attribute_hidden;
167+
168+
# if ELF_INITFINI
169+
/* These function symbols are provided for the .init/.fini section entry
170+
points automagically by the linker. */
171+
extern void _init (void);
172+
extern void _fini (void);
173+
# endif
174+
175+
/* Initialization for static executables. There is no dynamic
176+
segment, so we access the symbols directly. */
177+
static void
178+
call_init (int argc, char **argv, char **envp)
179+
{
180+
/* For static executables, preinit happens right before init. */
181+
{
182+
const size_t size = __preinit_array_end - __preinit_array_start;
183+
size_t i;
184+
for (i = 0; i < size; i++)
185+
(*__preinit_array_start [i]) (argc, argv, envp);
186+
}
187+
188+
# if ELF_INITFINI
189+
_init ();
190+
# endif
191+
192+
const size_t size = __init_array_end - __init_array_start;
193+
for (size_t i = 0; i < size; i++)
194+
(*__init_array_start [i]) (argc, argv, envp);
195+
}
196+
197+
/* Likewise for the destructor. */
198+
static void
199+
call_fini (void *unused)
200+
{
201+
size_t i = __fini_array_end - __fini_array_start;
202+
while (i-- > 0)
203+
(*__fini_array_start [i]) ();
204+
205+
# if ELF_INITFINI
206+
_fini ();
207+
# endif
208+
}
209+
210+
#endif /* !SHARED */
211+
116212
#include <libc-start.h>
117213

118214
STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
@@ -129,9 +225,16 @@ STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
129225
__attribute__ ((noreturn));
130226

131227

132-
/* Note: the fini parameter is ignored here for shared library. It
133-
is registered with __cxa_atexit. This had the disadvantage that
134-
finalizers were called in more than one place. */
228+
/* Note: The init and fini parameters are no longer used. fini is
229+
completely unused, init is still called if not NULL, but the
230+
current startup code always passes NULL. (In the future, it would
231+
be possible to use fini to pass a version code if init is NULL, to
232+
indicate the link-time glibc without introducing a hard
233+
incompatibility for new programs with older glibc versions.)
234+
235+
For dynamically linked executables, the dynamic segment is used to
236+
locate constructors and destructors. For statically linked
237+
executables, the relevant symbols are access directly. */
135238
STATIC int
136239
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
137240
int argc, char **argv,
@@ -258,25 +361,33 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
258361
run the constructors in `_dl_start_user'. */
259362
__libc_init_first (argc, argv, __environ);
260363

261-
/* Register the destructor of the program, if any. */
262-
if (fini)
263-
__cxa_atexit ((void (*) (void *)) fini, NULL, NULL);
364+
/* Register the destructor of the statically-linked program. */
365+
__cxa_atexit (call_fini, NULL, NULL);
264366

265367
/* Some security at this point. Prevent starting a SUID binary where
266368
the standard file descriptors are not opened. We have to do this
267369
only for statically linked applications since otherwise the dynamic
268370
loader did the work already. */
269371
if (__builtin_expect (__libc_enable_secure, 0))
270372
__libc_check_standard_fds ();
271-
#endif
373+
#endif /* !SHARED */
272374

273375
/* Call the initializer of the program, if any. */
274376
#ifdef SHARED
275377
if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS, 0))
276378
GLRO(dl_debug_printf) ("\ninitialize program: %s\n\n", argv[0]);
277-
#endif
278-
if (init)
379+
380+
if (init != NULL)
381+
/* This is a legacy program which supplied its own init
382+
routine. */
279383
(*init) (argc, argv, __environ MAIN_AUXVEC_PARAM);
384+
else
385+
/* This is a current program. Use the dynamic segment to find
386+
constructors. */
387+
call_init (argc, argv, __environ);
388+
#else /* !SHARED */
389+
call_init (argc, argv, __environ);
390+
#endif /* SHARED */
280391

281392
#ifdef SHARED
282393
/* Auditing checkpoint: we have a new object. */
@@ -365,3 +476,36 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
365476

366477
exit (result);
367478
}
479+
480+
/* Starting with glibc 2.34, the init parameter is always NULL. Older
481+
libcs are not prepared to handle that. The macro
482+
DEFINE_LIBC_START_MAIN_VERSION creates GLIBC_2.34 alias, so that
483+
newly linked binaries reflect that dependency. The macros below
484+
expect that the exported function is called
485+
__libc_start_main_impl. */
486+
#ifdef SHARED
487+
# define DEFINE_LIBC_START_MAIN_VERSION \
488+
DEFINE_LIBC_START_MAIN_VERSION_1 \
489+
strong_alias (__libc_start_main_impl, __libc_start_main_alias_2) \
490+
versioned_symbol (libc, __libc_start_main_alias_2, __libc_start_main, \
491+
GLIBC_2_34);
492+
493+
# if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_34)
494+
# define DEFINE_LIBC_START_MAIN_VERSION_1 \
495+
strong_alias (__libc_start_main_impl, __libc_start_main_alias_1) \
496+
compat_symbol (libc, __libc_start_main_alias_1, __libc_start_main, GLIBC_2_0);
497+
# else
498+
# define DEFINE_LIBC_START_MAIN_VERSION_1
499+
# endif
500+
#else /* !SHARED */
501+
/* Enable calling the function under its exported name. */
502+
# define DEFINE_LIBC_START_MAIN_VERSION \
503+
strong_alias (__libc_start_main_impl, __libc_start_main)
504+
#endif
505+
506+
/* Only define the version information if LIBC_START_MAIN was not set.
507+
If there is a wrapper file, it must expand
508+
DEFINE_LIBC_START_MAIN_VERSION on its own. */
509+
#if DO_DEFINE_LIBC_START_MAIN_VERSION
510+
DEFINE_LIBC_START_MAIN_VERSION
511+
#endif

0 commit comments

Comments
 (0)