Summary
The secure_getenv() interception in the one-shot-token LD_PRELOAD library has a critical initialization bug: it does not call init_token_list() before checking if a variable is sensitive, causing all token protection to be bypassed when secure_getenv() is the first call into the library.
Root Cause
In containers/agent/one-shot-token/one-shot-token.c:
getenv() (line 248-298) - CORRECT:
char *getenv(const char *name) {
init_real_getenv();
pthread_mutex_lock(&token_mutex); // ← acquires mutex
if (!tokens_initialized) {
init_token_list(); // ← initializes token list
}
int token_idx = get_token_index(name); // ← checks AFTER init
...
}
secure_getenv() (line 312-362) - BUGGY:
char *secure_getenv(const char *name) {
init_real_secure_getenv();
init_real_getenv();
if (real_secure_getenv == NULL) {
return getenv(name); // fallback path is fine
}
int token_idx = get_token_index(name); // ← NO mutex, NO init_token_list()!
if (token_idx < 0) {
return real_secure_getenv(name); // ← raw token returned!
}
pthread_mutex_lock(&token_mutex); // ← mutex acquired too late
...
}
Attack Scenario
On Linux (where secure_getenv is available), if a program's first environment variable read uses secure_getenv():
tokens_initialized == 0, num_tokens == 0
get_token_index("GITHUB_TOKEN") returns -1 (empty list)
real_secure_getenv("GITHUB_TOKEN") returns the raw token value
- Token is NEVER cached, NEVER unset from
/proc/self/environ
- All subsequent reads (via
getenv or secure_getenv) also get the raw value since it was never cleared
Many C libraries use secure_getenv() for locale, timezone, and other initialization before any application code calls getenv().
Fix
Add token list initialization to secure_getenv() before get_token_index():
char *secure_getenv(const char *name) {
init_real_secure_getenv();
init_real_getenv();
if (real_secure_getenv == NULL) {
return getenv(name);
}
// Initialize token list on first call (thread-safe)
pthread_mutex_lock(&token_mutex);
if (!tokens_initialized) {
init_token_list();
}
int token_idx = get_token_index(name);
if (token_idx < 0) {
pthread_mutex_unlock(&token_mutex);
return real_secure_getenv(name);
}
// ... rest of function (mutex already held)
}
Context
PR #702 claimed to fix "secure_getenv() initialization bug" but the fix does not appear to have landed in the current main branch code. The bug is present as of commit abaf8dc.
Severity
High - Complete bypass of one-shot token protection for any program whose first env var read uses secure_getenv().
Summary
The
secure_getenv()interception in the one-shot-token LD_PRELOAD library has a critical initialization bug: it does not callinit_token_list()before checking if a variable is sensitive, causing all token protection to be bypassed whensecure_getenv()is the first call into the library.Root Cause
In
containers/agent/one-shot-token/one-shot-token.c:getenv()(line 248-298) - CORRECT:secure_getenv()(line 312-362) - BUGGY:Attack Scenario
On Linux (where
secure_getenvis available), if a program's first environment variable read usessecure_getenv():tokens_initialized == 0,num_tokens == 0get_token_index("GITHUB_TOKEN")returns -1 (empty list)real_secure_getenv("GITHUB_TOKEN")returns the raw token value/proc/self/environgetenvorsecure_getenv) also get the raw value since it was never clearedMany C libraries use
secure_getenv()for locale, timezone, and other initialization before any application code callsgetenv().Fix
Add token list initialization to
secure_getenv()beforeget_token_index():Context
PR #702 claimed to fix "secure_getenv() initialization bug" but the fix does not appear to have landed in the current
mainbranch code. The bug is present as of commit abaf8dc.Severity
High - Complete bypass of one-shot token protection for any program whose first env var read uses
secure_getenv().