Use of getlogin() in Multithreaded Application
Description
Use of getlogin() in Multithreaded Application is a vulnerability where a program uses the getlogin() function in a multithreaded context, potentially receiving incorrect or manipulated values due to the function's non-reentrant nature. The getlogin() function returns a pointer to a string containing the username associated with the calling process, but this function is not thread-safe. In multithreaded applications, concurrent processes can modify the returned value without synchronization, meaning the username string can change between the function call and when the value is used, leading to race conditions and potentially security bypasses.
Risk
Using getlogin() in multithreaded applications creates serious security vulnerabilities. Race conditions can cause the function to return the wrong username, leading to authorization decisions being made for the incorrect user. Attackers who can influence the process environment may be able to manipulate the returned username to impersonate other users. Since getlogin() relies on the controlling terminal, processes without a terminal may receive NULL or unpredictable values. The function can also be fooled by modifying the utmp file. Using usernames for security decisions is inherently risky as names can overlap and be forged, potentially leading to privilege escalation or unauthorized access.
Solution
Replace getlogin() with getlogin_r(), which is the reentrant version that uses a caller-provided buffer and provides thread safety. Better yet, avoid relying on usernames for security decisions entirely and use numeric user IDs (UIDs) via getuid() or geteuid(), which are more reliable and harder to forge. When username information is necessary, use getpwuid() with the UID to retrieve user information safely. Implement proper synchronization if username-based checks are unavoidable. Never use getlogin() in security-critical contexts such as authorization checks, privilege assignments, or audit logging.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Race conditions can cause the application to operate on incorrect user data, leading to data corruption or incorrect processing. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Security checks based on getlogin() can be bypassed through race conditions or terminal manipulation, allowing unauthorized access. |
| Other | Scope: Other Varies by Context - The function may return NULL for processes without controlling terminals, causing unexpected behavior or crashes. |
Example Code
Vulnerable Code
// Vulnerable: Using getlogin() in multithreaded application
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <pthread.h>
// Vulnerable: Non-reentrant getlogin() in threaded code
void* check_user_access(void* arg) {
char* resource = (char*)arg;
// Vulnerable: getlogin() is not thread-safe
char* username = getlogin();
if (username == NULL) {
printf("Failed to get username\n");
return NULL;
}
// Vulnerable: Race condition between getlogin() and use
// Another thread could change the returned value
// Vulnerable: Using username for security decision
if (strcmp(username, "admin") == 0) {
printf("Admin access granted to %s\n", resource);
grant_admin_access(resource);
} else {
printf("User %s accessing %s\n", username, resource);
grant_user_access(resource);
}
return NULL;
}
int main() {
pthread_t threads[10];
// Multiple threads using getlogin() simultaneously
for (int i = 0; i < 10; i++) {
pthread_create(&threads[i], NULL, check_user_access, "shared_resource");
}
for (int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
// Vulnerable: Security decision based on getlogin()
#include <pwd.h>
#include <grp.h>
int is_trusted_user(void) {
// Vulnerable: getlogin() unreliable and non-reentrant
char* login = getlogin();
if (login == NULL) {
return 0; // Deny if can't determine
}
// Vulnerable: Getting password entry based on unreliable name
struct passwd* pwd = getpwnam(login);
if (pwd == NULL) {
return 0;
}
// Vulnerable: Group check based on potentially wrong user
if (is_trusted_group(pwd->pw_gid)) {
return 1; // Grant access - but to whom?
}
return 0;
}
// Vulnerable: Authorization based on getlogin()
void authorize_action(const char* action) {
char* username = getlogin();
// Vulnerable: username could be NULL, wrong, or manipulated
printf("Authorizing %s for user %s\n", action, username ? username : "unknown");
// Race condition: another thread might have changed the login
if (username && strcmp(username, "root") == 0) {
perform_privileged_action(action);
}
}
// Vulnerable: Logging with getlogin()
void log_user_action(const char* action) {
// Vulnerable: Non-reentrant, may return wrong user
char* user = getlogin();
// Vulnerable: Log might attribute action to wrong user
syslog(LOG_INFO, "User %s performed: %s",
user ? user : "unknown", action);
// If race condition occurred, audit trail is corrupted
}
Fixed Code
// Fixed: Using getlogin_r() - the reentrant version
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#define LOGIN_NAME_MAX 256
// Fixed: Thread-safe user access check
void* check_user_access_safe(void* arg) {
char* resource = (char*)arg;
char username[LOGIN_NAME_MAX];
// Fixed: Use reentrant getlogin_r() with local buffer
if (getlogin_r(username, sizeof(username)) != 0) {
// Fixed: Handle error properly
fprintf(stderr, "Failed to get username: %s\n", strerror(errno));
return NULL;
}
// Fixed: username is now in thread-local buffer
// No race condition with other threads
// Note: Still shouldn't use username for security decisions
// Use UID instead (see below)
return NULL;
}
// Fixed: Using UID instead of username for security
void* check_user_access_secure(void* arg) {
char* resource = (char*)arg;
// Fixed: Use numeric UID - more reliable than username
uid_t uid = getuid();
uid_t euid = geteuid();
// Fixed: Check against known admin UID
// UID 0 is root on Unix systems
if (euid == 0) {
printf("Admin access to %s\n", resource);
grant_admin_access(resource);
} else {
printf("User %d accessing %s\n", uid, resource);
grant_user_access(resource);
}
return NULL;
}
int main() {
pthread_t threads[10];
for (int i = 0; i < 10; i++) {
pthread_create(&threads[i], NULL, check_user_access_secure, "shared_resource");
}
for (int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
// Fixed: Proper user identification
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
// Fixed: Use UID for security decisions
int is_trusted_user_secure(void) {
// Fixed: Get real and effective UIDs
uid_t real_uid = getuid();
uid_t effective_uid = geteuid();
// Fixed: Use getpwuid_r (reentrant) for user info if needed
struct passwd pwd;
struct passwd* result;
char buffer[1024];
if (getpwuid_r(effective_uid, &pwd, buffer, sizeof(buffer), &result) != 0) {
return 0; // Error getting user info
}
if (result == NULL) {
return 0; // User not found
}
// Fixed: Check group membership using GID
if (is_trusted_group(pwd.pw_gid)) {
return 1;
}
// Fixed: Check supplementary groups
gid_t groups[NGROUPS_MAX];
int ngroups = getgroups(NGROUPS_MAX, groups);
for (int i = 0; i < ngroups; i++) {
if (is_trusted_group(groups[i])) {
return 1;
}
}
return 0;
}
// Fixed: Thread-safe authorization
void authorize_action_secure(const char* action) {
uid_t uid = getuid();
gid_t gid = getgid();
// Fixed: Log with reliable identifiers
syslog(LOG_INFO, "UID %d GID %d requesting: %s", uid, gid, action);
// Fixed: Authorization based on UID/GID, not username
if (uid == 0 || is_authorized_uid(uid, action)) {
perform_privileged_action(action);
} else {
deny_action(action);
}
}
// Fixed: Thread-safe logging with proper user identification
#include <pthread.h>
#include <syslog.h>
// Fixed: Thread-safe audit logging
void log_user_action_secure(const char* action) {
uid_t uid = getuid();
uid_t euid = geteuid();
gid_t gid = getgid();
// Fixed: Get username in thread-safe manner if really needed
struct passwd pwd;
struct passwd* result;
char buffer[1024];
char username[256] = "unknown";
if (getpwuid_r(uid, &pwd, buffer, sizeof(buffer), &result) == 0 && result != NULL) {
strncpy(username, pwd.pw_name, sizeof(username) - 1);
username[sizeof(username) - 1] = '\0';
}
// Fixed: Log includes UID for reliable identification
syslog(LOG_INFO, "Action by %s (uid=%d euid=%d gid=%d): %s",
username, uid, euid, gid, action);
}
// Fixed: Complete secure user context
typedef struct {
uid_t uid;
uid_t euid;
gid_t gid;
gid_t egid;
char username[256];
gid_t groups[NGROUPS_MAX];
int ngroups;
} UserContext;
int get_user_context(UserContext* ctx) {
if (ctx == NULL) return -1;
ctx->uid = getuid();
ctx->euid = geteuid();
ctx->gid = getgid();
ctx->egid = getegid();
ctx->ngroups = getgroups(NGROUPS_MAX, ctx->groups);
// Fixed: Thread-safe username lookup
struct passwd pwd;
struct passwd* result;
char buffer[1024];
if (getpwuid_r(ctx->uid, &pwd, buffer, sizeof(buffer), &result) == 0 && result) {
strncpy(ctx->username, pwd.pw_name, sizeof(ctx->username) - 1);
ctx->username[sizeof(ctx->username) - 1] = '\0';
} else {
snprintf(ctx->username, sizeof(ctx->username), "uid:%d", ctx->uid);
}
return 0;
}
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern relates to race conditions in authentication that have been observed in various Unix applications.
References
- MITRE Corporation. "CWE-558: Use of getlogin() in Multithreaded Application." https://cwe.mitre.org/data/definitions/558.html
- CERT. "POS30-C. Use the readdir_r() function rather than readdir()."
- IEEE Std 1003.1 (POSIX). "getlogin, getlogin_r - get login name."