Skip to content

Commit 24f3e84

Browse files
committed
Add thread safety to some environment accesses
The previous commit added a mutex specifically for protecting against simultaneous accesses of the environment. This commit changes the normal getenv, putenv, and clearenv functions to use it, to avoid races. This makes the code simpler in places where we've gotten burned and added stuff to avoid races. Other places where we haven't known we were getting burned could have existed until now. Now that comes automatically, and we can remove the special cases we earlier stumbled over. getenv() returns a pointer to static memory, which can be overwritten at any moment from another thread, or even another getenv from the same thread. This commit changes the accesses to be under control of a mutex, and in the case of getenv, a mortalized copy is created so that there is no possible race.
1 parent 2bc5f86 commit 24f3e84

File tree

8 files changed

+86
-37
lines changed

8 files changed

+86
-37
lines changed

embed.fnc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@
453453
:
454454
: Individual flags may be separated by non-tab whitespace.
455455

456+
CipRTX |char * |mortal_getenv |NN const char * str
457+
456458
#if defined(PERL_IMPLICIT_SYS)
457459
ATo |PerlInterpreter*|perl_alloc_using \
458460
|NN struct IPerlMem *ipM \

embed.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@
313313
#define mg_size(a) Perl_mg_size(aTHX_ a)
314314
#define mini_mktime Perl_mini_mktime
315315
#define moreswitches(a) Perl_moreswitches(aTHX_ a)
316+
#define mortal_getenv Perl_mortal_getenv
316317
#define mro_get_linear_isa(a) Perl_mro_get_linear_isa(aTHX_ a)
317318
#define mro_method_changed_in(a) Perl_mro_method_changed_in(aTHX_ a)
318319
#define my_atof(a) Perl_my_atof(aTHX_ a)

inline.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,6 +2586,59 @@ S_my_memrchr(const char * s, const char c, const STRLEN len)
25862586

25872587
#endif
25882588

2589+
PERL_STATIC_INLINE char *
2590+
Perl_mortal_getenv(const char * str)
2591+
{
2592+
/* This implements a (mostly) thread-safe, sequential-call-safe getenv().
2593+
*
2594+
* It's (mostly) thread-safe because it uses a mutex to prevent
2595+
* simultaneous access from other threads that use the same mutex, and
2596+
* makes a copy of the result before releasing that mutex. All of the Perl
2597+
* core uses that mutex, but, like all mutexes, everything has to cooperate
2598+
* for it to completely work. It is possible for code from, say XS, to not
2599+
* use this mutex, defeating the safety.
2600+
*
2601+
* On some platforms, getenv() is not sequential-call-safe, because
2602+
* subsequent calls destroy the static storage inside the C library
2603+
* returned by an earlier call. The result must be copied or completely
2604+
* acted upon before a subsequent getenv call. Those calls could come from
2605+
* another thread. Again, making a copy while controlling the mutex
2606+
* prevents these problems..
2607+
*
2608+
* To prevent leaks, the copy is made by creating a new SV containing it,
2609+
* mortalizing the SV, and returning the SV's string (the copy). Thus this
2610+
* is a drop-in replacement for getenv().
2611+
*
2612+
* A complication is that this can be called during phases where the
2613+
* mortalization process isn't available. These are in interpreter
2614+
* destruction or early in construction. khw believes that at these times
2615+
* there shouldn't be anything else going on, so plain getenv is safe AS
2616+
* LONG AS the caller acts on the return before calling it again. */
2617+
2618+
char * ret;
2619+
dTHX;
2620+
2621+
PERL_ARGS_ASSERT_MORTAL_GETENV;
2622+
2623+
/* Can't mortalize without stacks. khw believes that no other threads
2624+
* should be running, so no need to lock things, and this may be during a
2625+
* phase when locking isn't even available */
2626+
if (UNLIKELY(PL_scopestack_ix == 0)) {
2627+
return getenv(str);
2628+
}
2629+
2630+
ENV_LOCK;
2631+
2632+
ret = getenv(str);
2633+
2634+
if (ret != NULL) {
2635+
ret = SvPVX(sv_2mortal(newSVpv(ret, 0)));
2636+
}
2637+
2638+
ENV_UNLOCK;
2639+
return ret;
2640+
}
2641+
25892642
/*
25902643
* ex: set ts=8 sts=4 sw=4 et:
25912644
*/

iperlsys.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -562,10 +562,18 @@ struct IPerlEnvInfo
562562
(*PL_Env->pGetChildIO)(PL_Env, ptr)
563563
#endif
564564

565-
#else /* PERL_IMPLICIT_SYS */
566-
567-
#define PerlEnv_putenv(str) putenv((str))
568-
#define PerlEnv_getenv(str) getenv((str))
565+
#else /* below is ! PERL_IMPLICIT_SYS */
566+
# ifdef USE_ITHREADS
567+
568+
/* Use the comma operator to return 0/non-zero, while avoiding putting
569+
* this in an inline function */
570+
# define PerlEnv_putenv(str) (ENV_LOCK, (putenv(str) \
571+
? (ENV_UNLOCK, 1) \
572+
: (ENV_UNLOCK, 0)))
573+
# else
574+
# define PerlEnv_putenv(str) putenv(str)
575+
# endif
576+
#define PerlEnv_getenv(str) mortal_getenv(str)
569577
#define PerlEnv_getenv_len(str,l) getenv_len((str), (l))
570578
#ifdef HAS_ENVGETENV
571579
# define PerlEnv_ENVgetenv(str) ENVgetenv((str))
@@ -588,7 +596,9 @@ struct IPerlEnvInfo
588596
#define PerlEnv_get_childdir() win32_get_childdir()
589597
#define PerlEnv_free_childdir(d) win32_free_childdir((d))
590598
#else
591-
#define PerlEnv_clearenv() clearenv()
599+
#define PerlEnv_clearenv(str) (ENV_LOCK, (clearenv(str) \
600+
? (ENV_UNLOCK, 1) \
601+
: (ENV_UNLOCK, 0)))
592602
#define PerlEnv_get_childenv() get_childenv()
593603
#define PerlEnv_free_childenv(e) free_childenv((e))
594604
#define PerlEnv_get_childdir() get_childdir()

locale.c

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -791,16 +791,6 @@ S_emulate_setlocale(const int category,
791791
if (! default_name || strEQ(default_name, "")) {
792792
default_name = "C";
793793
}
794-
else if (PL_scopestack_ix != 0) {
795-
/* To minimize other threads messing with the environment,
796-
* we copy the variable, making it a temporary. But this
797-
* doesn't work upon program initialization before any
798-
* scopes are created, and at this time, there's nothing
799-
* else going on that would interfere. So skip the copy
800-
* in that case */
801-
default_name = savepv(default_name);
802-
SAVEFREEPV(default_name);
803-
}
804794

805795
if (category != LC_ALL) {
806796
const char * const name = PerlEnv_getenv(category_names[index]);
@@ -835,22 +825,19 @@ S_emulate_setlocale(const int category,
835825

836826
for (i = 0; i < LC_ALL_INDEX; i++) {
837827
const char * const env_override
838-
= savepv(PerlEnv_getenv(category_names[i]));
828+
= PerlEnv_getenv(category_names[i]);
839829
const char * this_locale = ( env_override
840830
&& strNE(env_override, ""))
841831
? env_override
842832
: default_name;
843833
if (! emulate_setlocale(categories[i], this_locale, i, TRUE))
844834
{
845-
Safefree(env_override);
846835
return NULL;
847836
}
848837

849838
if (strNE(this_locale, default_name)) {
850839
did_override = TRUE;
851840
}
852-
853-
Safefree(env_override);
854841
}
855842

856843
/* If all the categories are the same, we can set LC_ALL to
@@ -3310,7 +3297,7 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
33103297
#else /* USE_LOCALE */
33113298
# ifdef __GLIBC__
33123299

3313-
const char * const language = savepv(PerlEnv_getenv("LANGUAGE"));
3300+
const char * const language = PerlEnv_getenv("LANGUAGE");
33143301

33153302
# endif
33163303

@@ -3320,8 +3307,8 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
33203307
: "";
33213308
const char* trial_locales[5]; /* 5 = 1 each for "", LC_ALL, LANG, "", C */
33223309
unsigned int trial_locales_count;
3323-
const char * const lc_all = savepv(PerlEnv_getenv("LC_ALL"));
3324-
const char * const lang = savepv(PerlEnv_getenv("LANG"));
3310+
const char * const lc_all = PerlEnv_getenv("LC_ALL");
3311+
const char * const lang = PerlEnv_getenv("LANG");
33253312
bool setlocale_failure = FALSE;
33263313
unsigned int i;
33273314

@@ -3909,15 +3896,6 @@ Perl_init_i18nl10n(pTHX_ int printwarn)
39093896
}
39103897

39113898
# endif
3912-
# ifdef __GLIBC__
3913-
3914-
Safefree(language);
3915-
3916-
# endif
3917-
3918-
Safefree(lc_all);
3919-
Safefree(lang);
3920-
39213899
#endif /* USE_LOCALE */
39223900
#ifdef DEBUGGING
39233901

perl.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,10 +2283,6 @@ S_parse_body(pTHX_ char **env, XSINIT_t xsinit)
22832283
#endif
22842284
(s = PerlEnv_getenv("PERL5OPT")))
22852285
{
2286-
/* s points to static memory in getenv(), which may be overwritten at
2287-
* any time; use a mortal copy instead */
2288-
s = SvPVX(sv_2mortal(newSVpv(s, 0)));
2289-
22902286
while (isSPACE(*s))
22912287
s++;
22922288
if (*s == '-' && *(s+1) == 'T') {

proto.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,13 @@ PERL_CALLCONV void * Perl_more_bodies(pTHX_ const svtype sv_type, const size_t b
20452045
PERL_CALLCONV const char* Perl_moreswitches(pTHX_ const char* s);
20462046
#define PERL_ARGS_ASSERT_MORESWITCHES \
20472047
assert(s)
2048+
#ifndef PERL_NO_INLINE_FUNCTIONS
2049+
PERL_STATIC_INLINE char * Perl_mortal_getenv(const char * str)
2050+
__attribute__warn_unused_result__;
2051+
#define PERL_ARGS_ASSERT_MORTAL_GETENV \
2052+
assert(str)
2053+
#endif
2054+
20482055
PERL_CALLCONV const struct mro_alg * Perl_mro_get_from_name(pTHX_ SV *name);
20492056
#define PERL_ARGS_ASSERT_MRO_GET_FROM_NAME \
20502057
assert(name)

util.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,7 +2139,8 @@ Perl_my_setenv(pTHX_ const char *nam, const char *val)
21392139
# endif
21402140

21412141
# ifdef USE_ITHREADS
2142-
/* only parent thread can modify process environment */
2142+
/* only parent thread can modify process environment, so no need to use a
2143+
* mutex */
21432144
if (PL_curinterp == aTHX)
21442145
# endif
21452146
{
@@ -5169,7 +5170,8 @@ Perl_my_clearenv(pTHX)
51695170
# else /* ! (PERL_IMPLICIT_SYS || WIN32) */
51705171
# if defined(USE_ENVIRON_ARRAY)
51715172
# if defined(USE_ITHREADS)
5172-
/* only the parent thread can clobber the process environment */
5173+
/* only the parent thread can clobber the process environment, so no need
5174+
* to use a mutex */
51735175
if (PL_curinterp == aTHX)
51745176
# endif /* USE_ITHREADS */
51755177
{

0 commit comments

Comments
 (0)