elf: Ignore LD_PROFILE if LD_PROFILE_OUTPUT is not set (bug 33797)

The previous default for LD_PROFILE_OUTPUT, /var/tmp, is insecure
because it's typically a 1777 directory, and other systems could
place malicious files there which interfere with execution.

Requiring the user to specify a profiling directory mitigates
the impact of bug 33797.  Clear LD_PROFILE_OUTPUT alongside
with LD_PROFILE.

Rework the test not to use predictable file names.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Florian Weimer 2026-01-15 22:29:46 +01:00
parent 0bbeb1fd13
commit 7b543dcdf9
4 changed files with 44 additions and 23 deletions

6
NEWS
View File

@ -100,7 +100,11 @@ Deprecated and removed features, and other changes affecting compatibility:
Changes to build and runtime requirements:
[Add changes to build and runtime requirements here]
* The LD_PROFILE functionality no longer has a default directory for the
profile data it writes. Instead, developers are required to set a
directory explicitly using the LD_PROFILE_OUTPUT environment variable.
To restore the previous, insecure behavior, processes can be run with
LD_PROFILE_OUTPUT=/var/tmp.
Security related changes:

View File

@ -359,7 +359,6 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
._dl_fpu_control = _FPU_DEFAULT,
._dl_pagesize = EXEC_PAGESIZE,
._dl_inhibit_cache = 0,
._dl_profile_output = "/var/tmp",
/* Function pointers. */
._dl_debug_printf = _dl_debug_printf,
@ -2708,6 +2707,15 @@ process_envvars_default (struct dl_main_state *state)
}
}
/* There is no fixed, safe directory to store profiling data, so
activate LD_PROFILE only if LD_PROFILE_OUTPUT is set as well. */
if (GLRO(dl_profile) != NULL && GLRO(dl_profile_output) == NULL)
{
_dl_error_printf ("\
warning: LD_PROFILE ignored because LD_PROFILE_OUTPUT not specified\n");
GLRO(dl_profile) = NULL;
}
/* If we have to run the dynamic linker in debugging mode and the
LD_DEBUG_OUTPUT environment variable is given, we write the debug
messages to this file. */

View File

@ -40,7 +40,11 @@ static char SETGID_CHILD[] = "setgid-child";
# define PROFILE_LIB "tst-sonamemove-runmod2.so"
#endif
#define LD_DEBUG_OUTPUT "/tmp/some-file"
/* Computed path for LD_DEBUG_OUTPUT. */
static char *debugoutputpath;
/* Expected file name for erroneous LD_PROFILE output. */
static char *profilepath;
struct envvar_t
{
@ -57,13 +61,14 @@ static const struct envvar_t filtered_envvars[] =
{ "LD_LIBRARY_PATH", FILTERED_VALUE },
{ "LD_PRELOAD", FILTERED_VALUE },
{ "LD_PROFILE", PROFILE_LIB },
{ "LD_PROFILE_OUTPUT", "/var/tmp" }, /* Not actually used. */
{ "MALLOC_ARENA_MAX", FILTERED_VALUE },
{ "MALLOC_PERTURB_", FILTERED_VALUE },
{ "MALLOC_TRACE", FILTERED_VALUE },
{ "MALLOC_TRIM_THRESHOLD_", FILTERED_VALUE },
{ "RES_OPTIONS", FILTERED_VALUE },
{ "LD_DEBUG", "all" },
{ "LD_DEBUG_OUTPUT", LD_DEBUG_OUTPUT },
{ "LD_DEBUG_OUTPUT", "overwritten" }, /* Not actually used. */
{ "LD_WARN", FILTERED_VALUE },
{ "LD_VERBOSE", FILTERED_VALUE },
{ "LD_BIND_NOW", "0" },
@ -79,7 +84,7 @@ static const struct envvar_t unfiltered_envvars[] =
static void
unlink_ld_debug_output (pid_t pid)
{
char *output = xasprintf ("%s.%d", LD_DEBUG_OUTPUT, pid);
char *output = xasprintf ("%s.%d", debugoutputpath, pid);
unlink (output);
free (output);
}
@ -121,18 +126,12 @@ test_child (void)
}
/* Also check if no profile file was created.
The parent sets LD_DEBUG_OUTPUT="/tmp/some-file"
which should be filtered. Then it falls back to "/var/tmp".
Note: LD_PROFILE is not supported for static binaries. */
{
char *profilepath = xasprintf ("/var/tmp/%s.profile", PROFILE_LIB);
if (!access (profilepath, R_OK))
{
printf ("FAIL: LD_PROFILE file at %s was created!\n", profilepath);
ret = 1;
}
free (profilepath);
}
if (!access (profilepath, R_OK))
{
printf ("FAIL: LD_PROFILE file at %s was created!\n", profilepath);
ret = 1;
}
return ret;
}
@ -145,6 +144,11 @@ do_test (int argc, char **argv)
if (argc >= 2 && strstr (argv[1], LD_SO) != 0)
FAIL_UNSUPPORTED ("dynamic test requires --enable-hardcoded-path-in-tests");
profilepath = xasprintf ("%s/%s.profile",
support_objdir_root, PROFILE_LIB);
debugoutputpath = xasprintf ("%s/tst-env-setuid-file",
support_objdir_root);
/* Setgid child process. */
if (argc == 2 && strcmp (argv[1], SETGID_CHILD) == 0)
{
@ -165,7 +169,6 @@ do_test (int argc, char **argv)
if (ret != 0)
exit (1);
return 0;
}
else
{
@ -179,20 +182,25 @@ do_test (int argc, char **argv)
e++)
setenv (e->env, e->value, 1);
/* Dynamically computed values. */
setenv ("LD_DEBUG_OUTPUT", debugoutputpath, 1);
setenv ("LD_PROFILE_OUTPUT", support_objdir_root, 1);
/* Ensure that the profile output does not exist from a previous run
(e.g. if test_dir, which defaults to /tmp, is mounted nosuid.)
Note: support_capture_subprogram_self_sgid creates the SGID binary
in test_dir. */
{
char *profilepath = xasprintf ("/var/tmp/%s.profile", PROFILE_LIB);
unlink (profilepath);
free (profilepath);
}
unlink (profilepath);
support_capture_subprogram_self_sgid (SETGID_CHILD);
return 0;
/* And clean up afterwards if necessary. */
unlink (profilepath);
}
free (profilepath);
free (debugoutputpath);
return 0;
}
#define TEST_FUNCTION_ARGV do_test

View File

@ -16,6 +16,7 @@
"LD_ORIGIN_PATH\0" \
"LD_PRELOAD\0" \
"LD_PROFILE\0" \
"LD_PROFILE_OUTPUT\0" \
"LD_SHOW_AUXV\0" \
"LD_VERBOSE\0" \
"LD_WARN\0" \