diff options
Diffstat (limited to 'stdlib/getenv.c')
-rw-r--r-- | stdlib/getenv.c | 141 |
1 files changed, 129 insertions, 12 deletions
diff --git a/stdlib/getenv.c b/stdlib/getenv.c index bea69e0..1a7b0bf 100644 --- a/stdlib/getenv.c +++ b/stdlib/getenv.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2024 Free Software Foundation, Inc. +/* Copyright (C) 1991-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -15,24 +15,141 @@ License along with the GNU C Library; if not, see <https://www.gnu.org/licenses/>. */ -#include <stdlib.h> +#include <atomic.h> +#include <setenv.h> #include <string.h> #include <unistd.h> char * getenv (const char *name) { - if (__environ == NULL || name[0] == '\0') - return NULL; - - size_t len = strlen (name); - for (char **ep = __environ; *ep != NULL; ++ep) + while (true) { - if (name[0] == (*ep)[0] - && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=') - return *ep + len + 1; - } + /* Used to deal with concurrent unsetenv. */ + environ_counter start_counter = atomic_load_acquire (&__environ_counter); + + /* We use relaxed MO for loading the string pointers because we + assume the strings themselves are immutable and that loads + through the string pointers carry a dependency. (This + depends on the the release MO store to __environ in + __add_to_environ.) Objects pointed to by pointers stored in + the __environ array are never modified or deallocated (except + perhaps if putenv is used, but then synchronization is the + responsibility of the applications). The backing store for + __environ is allocated zeroed. In summary, we can assume + that the pointers we observe are either valid or null, and + that only initialized string contents is visible. */ + char **start_environ = atomic_load_relaxed (&__environ); + if (start_environ == NULL || name[0] == '\0') + return NULL; + + size_t len = strlen (name); + for (char **ep = start_environ; ; ++ep) + { + char *entry = atomic_load_relaxed (ep); + if (entry == NULL) + break; + + /* If there is a match, return that value. It was valid at + one point, so we can return it. */ + if (name[0] == entry[0] + && strncmp (name, entry, len) == 0 && entry[len] == '=') + return entry + len + 1; + } + + /* The variable was not found. This might be a false negative + because unsetenv has shuffled around entries. Check if it is + necessary to retry. */ + + /* See Hans Boehm, Can Seqlocks Get Along with Programming Language + Memory Models?, Section 4. This is necessary so that loads in + the loop above are not ordered past the counter check below. */ + atomic_thread_fence_acquire (); + + if (atomic_load_acquire (&__environ_counter) == start_counter) + /* If we reach this point and there was a concurrent + unsetenv call which removed the key we tried to find, the + NULL return value is valid. We can also try again, not + find the value, and then return NULL (assuming there are + no further concurrent unsetenv calls). + + However, if getenv is called to find a value that is + present originally and not removed by any of the + concurrent unsetenv calls, we must not return NULL here. + + If the counter did not change, there was at most one + write to the array in unsetenv while the scanning loop + above was running. This means that there are at most two + different versions of the array to consider. For the + sake of argument, we assume that each load can make an + independent choice which version to use. An arbitrary + number of unsetenv and setenv calls may have happened + since start of getenv. Lets write E[0], E[1], ... for + the original environment elements, a(0) < (1) < ... for a + sequence of increasing integers that are the indices of + the environment variables remaining after the removals, and + N[0], N[1], ... for the new variables added by setenv or + putenv. Then at the start of the last unsetenv call, the + environment contains + + E[a(0)], E[a(1)], ..., N[0], N[1], ... - return NULL; + (the N[0], N[1], .... are optional.) Let's assume that + we are looking for the value E[j]. Then one of the + a(i) == j (otherwise we may return NULL here because + of a unsetenv for the value we are looking for). In the + discussion below it will become clear that the N[k] do + not actually matter. + + The two versions of array we can choose from differ only + in one element, say E[a(i)]. There are two cases: + + Case (A): E[a(i)] is an element being removed by unsetenv + (the target of the first write). We can see the original + version: + + ..., E[a(i-1)], E[a(i)], E[a(i+1)], ..., N[0], ... + ------- + And the overwritten version: + + ..., E[a(i-1)], E[a(i+1)], E[a(i+1)], ..., N[0], ... + --------- + + (The valueE[a(i+1)] can be the terminating NULL.) + As discussed, we are not considering the removal of the + variable being searched for, so a(i) != j, and the + variable getenv is looking for is available in either + version, and we would have found it above. + + Case (B): E[a(i)] is an element that has already been + moved forward and is now itself being overwritten with + its sucessor value E[a(i+1)]. The two versions of the + array look like this: + + ..., E[a(i-1)], E[a(i)], E[a(i)], E[a(i+1)], ..., N[0], ... + ------- + And with the overwrite in place: + + ..., E[a(i-1)], E[a(i)], E[a(i+1)], E[a(i+1)], ..., N[0], ... + --------- + + The key observation here is that even in the second + version with the overwrite present, the scanning loop + will still encounter the overwritten value E[a(i)] in the + previous array element. This means that as long as the + E[j] is still present among the initial E[a(...)] (as we + assumed because there is no concurrent unsetenv for + E[j]), we encounter it while scanning here in getenv. + + In summary, if there was at most one write, a negative + result is a true negative, and we can return NULL. This + is different from the seqlock paper, which retries if + there was any write at all. It avoids the need for a + second, unwritten copy for async-signal-safety. */ + return NULL; + /* If there was one more write, retry. This will never happen + in a signal handler that interrupted unsetenv because the + suspended unsetenv call cannot change the counter value. */ + } } libc_hidden_def (getenv) |