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: 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: Security related changes:

View File

@ -359,7 +359,6 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
._dl_fpu_control = _FPU_DEFAULT, ._dl_fpu_control = _FPU_DEFAULT,
._dl_pagesize = EXEC_PAGESIZE, ._dl_pagesize = EXEC_PAGESIZE,
._dl_inhibit_cache = 0, ._dl_inhibit_cache = 0,
._dl_profile_output = "/var/tmp",
/* Function pointers. */ /* Function pointers. */
._dl_debug_printf = _dl_debug_printf, ._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 /* If we have to run the dynamic linker in debugging mode and the
LD_DEBUG_OUTPUT environment variable is given, we write the debug LD_DEBUG_OUTPUT environment variable is given, we write the debug
messages to this file. */ messages to this file. */

View File

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

View File

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