Skip to content

Commit 71a975b

Browse files
committed
Fix git's usage of getenv() on systems with exotic getenv implementations
According to the POSIX specification, "The return value from getenv() may point to static data which may be overwritten by subsequent calls to getenv(), setenv(), [putenv()] or unsetenv()." and "The getenv() function need not be reentrant." [1] Many getenv() usages in git currently require that the returned values remain valid at least across subsequent getenv() calls. In some cases getenv() is used in background threads. This only works with typical getenv() implementations that return pointers into the char **environ array. As demonstrated by Jonathan Nieder's GETENV_POISON patch [2], more than half of git's test-suite fails with a most hazardous, yet POSIX compliant getenv() implementation that overwrites its own return values. This patch makes git's getenv() usage fully POSIX compliant, without having to fix the many getenv() references throughout git. This is achieved by overriding getenv() with a version that is thread-safe and ensures that all returned values remain valid until the process ends. Identical return values are unified in a hash table to save memory. The functionality can be enabled on platforms that require it by defining UNSAFE_GETENV in the Makefile. [1] http://pubs.opengroup.org/onlinepubs/009695399/functions/getenv.html [2] http://article.gmane.org/gmane.comp.version-control.git/161356 Signed-off-by: Karsten Blees <[email protected]>
1 parent a562e25 commit 71a975b

File tree

4 files changed

+110
-1
lines changed

4 files changed

+110
-1
lines changed

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ all::
242242
# dependency rules.
243243
#
244244
# Define NATIVE_CRLF if your platform uses CRLF for line endings.
245+
#
246+
# Define UNSAFE_GETENV if getenv isn't thread-safe or overwrites it's own
247+
# return values.
245248

246249
GIT-VERSION-FILE: FORCE
247250
@$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1409,6 +1412,10 @@ ifdef NO_SETENV
14091412
COMPAT_CFLAGS += -DNO_SETENV
14101413
COMPAT_OBJS += compat/setenv.o
14111414
endif
1415+
ifdef UNSAFE_GETENV
1416+
COMPAT_CFLAGS += -DUNSAFE_GETENV
1417+
COMPAT_OBJS += compat/safe-getenv.o
1418+
endif
14121419
ifdef NO_MKDTEMP
14131420
COMPAT_CFLAGS += -DNO_MKDTEMP
14141421
COMPAT_OBJS += compat/mkdtemp.o

compat/mingw.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ char *mingw_getcwd(char *pointer, int len);
198198
#define getcwd mingw_getcwd
199199

200200
char *mingw_getenv(const char *name);
201-
#define getenv mingw_getenv
201+
#define stdlib_getenv mingw_getenv
202202

203203
struct hostent *mingw_gethostbyname(const char *host);
204204
#define gethostbyname mingw_gethostbyname

compat/safe-getenv.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Fixes git's usage of getenv() on systems with exotic getenv implementations.
3+
*/
4+
#include "../git-compat-util.h"
5+
#include "../cache.h"
6+
7+
#ifdef NO_PTHREADS
8+
9+
#define mutex_init() (void)0
10+
#define mutex_lock() (void)0
11+
#define mutex_unlock() (void)0
12+
13+
#else
14+
15+
#include <pthread.h>
16+
17+
static pthread_mutex_t getenv_mutex;
18+
#define mutex_init() pthread_mutex_init(&getenv_mutex, NULL)
19+
#define mutex_lock() pthread_mutex_lock(&getenv_mutex)
20+
#define mutex_unlock() pthread_mutex_unlock(&getenv_mutex)
21+
22+
#endif
23+
24+
#undef getenv
25+
#ifdef stdlib_getenv
26+
# define unsafe_getenv stdlib_getenv
27+
#else
28+
# define unsafe_getenv getenv
29+
#endif
30+
31+
/* FNV-1 string hash function, see http://www.isthe.com/chongo/tech/comp/fnv */
32+
#define FNV32_BASE ((unsigned int) 0x811c9dc5)
33+
#define FNV32_PRIME ((unsigned int) 0x01000193)
34+
35+
unsigned int strhash(const char *str)
36+
{
37+
unsigned int c, hash = FNV32_BASE;
38+
while ((c = (unsigned char) *str++))
39+
hash = (hash * FNV32_PRIME) ^ c;
40+
return hash;
41+
}
42+
43+
/*
44+
* Entry in a pool of strings that have ever been returned by getenv().
45+
*/
46+
struct getenv_pool_entry {
47+
struct getenv_pool_entry *next;
48+
char value[1]; /* over-allocated to required size */
49+
};
50+
51+
char *safe_getenv(const char *key)
52+
{
53+
static struct hash_table getenv_pool;
54+
static int initialized = 0;
55+
int hash;
56+
char *value;
57+
struct getenv_pool_entry *ent;
58+
void **pnext;
59+
60+
/* lazy initialize mutex + getenv_pool */
61+
if (!initialized) {
62+
init_hash(&getenv_pool);
63+
mutex_init();
64+
initialized = 1;
65+
}
66+
67+
/* get value from original getenv */
68+
mutex_lock();
69+
value = unsafe_getenv(key);
70+
if (!value) {
71+
mutex_unlock();
72+
return NULL;
73+
}
74+
75+
/* lookup and return existing value from pool */
76+
hash = strhash(value);
77+
ent = lookup_hash(hash, &getenv_pool);
78+
while (ent) {
79+
if (!strcmp(value, ent->value)) {
80+
mutex_unlock();
81+
return ent->value;
82+
}
83+
ent = ent->next;
84+
}
85+
86+
/* not in pool, copy value and insert */
87+
ent = xmalloc(sizeof(*ent) + strlen(value));
88+
ent->next = NULL;
89+
strcpy(ent->value, value);
90+
pnext = insert_hash(hash, ent, &getenv_pool);
91+
if (pnext)
92+
ent->next = *pnext;
93+
mutex_unlock();
94+
return ent->value;
95+
}

git-compat-util.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ extern ssize_t read_in_full(int fd, void *buf, size_t count);
321321
extern int gitsetenv(const char *, const char *, int);
322322
#endif
323323

324+
#ifdef UNSAFE_GETENV
325+
extern char *safe_getenv(const char *name);
326+
# define getenv safe_getenv
327+
#elif defined(stdlib_getenv)
328+
# define getenv stdlib_getenv
329+
#endif
330+
324331
#ifdef NO_MKDTEMP
325332
#define mkdtemp gitmkdtemp
326333
extern char *gitmkdtemp(char *);

0 commit comments

Comments
 (0)