charon: Avoid potential TOCTOU issues when accessing/writing PID file

The previous code could potentially truncate and change ownership of
a file that's a symlink to an unintended target file (requires the
attacker to be able to create the symlink in the directory the PID file
is located, which generally requires root privileges).
This commit is contained in:
Tobias Brunner
2026-07-24 08:47:35 +02:00
parent 7bf9b6bad8
commit 18a104657e
+35 -17
View File
@@ -186,15 +186,22 @@ static void segv_handler(int signal)
static bool check_pidfile() static bool check_pidfile()
{ {
struct stat stb; struct stat stb;
int fd, flags = 0;
if (stat(PID_FILE, &stb) == 0) #ifndef WIN32
flags |= O_NOFOLLOW;
#endif
fd = open(PID_FILE, O_RDONLY | flags);
if (fd != -1)
{ {
pidfile = fopen(PID_FILE, "r"); if (fstat(fd, &stb) == 0 && S_ISREG(stb.st_mode))
if (pidfile)
{ {
char buf[64]; char buf[64];
pid_t pid = 0; pid_t pid = 0;
pidfile = fdopen(fd, "r");
if (pidfile)
{
memset(buf, 0, sizeof(buf)); memset(buf, 0, sizeof(buf));
if (fread(buf, 1, sizeof(buf), pidfile)) if (fread(buf, 1, sizeof(buf), pidfile))
{ {
@@ -203,26 +210,34 @@ static bool check_pidfile()
} }
fclose(pidfile); fclose(pidfile);
pidfile = NULL; pidfile = NULL;
}
else
{
close(fd);
}
if (pid && pid != getpid() && kill(pid, 0) == 0) if (pid && pid != getpid() && kill(pid, 0) == 0)
{ {
DBG1(DBG_DMN, "charon already running ('"PID_FILE"' exists)"); DBG1(DBG_DMN, "charon already running ('"PID_FILE"' exists)");
return TRUE; return TRUE;
} }
} }
else
{
close(fd);
}
}
if (fd != -1 || errno != ENOENT)
{
DBG1(DBG_DMN, "removing pidfile '"PID_FILE"', process not running"); DBG1(DBG_DMN, "removing pidfile '"PID_FILE"', process not running");
unlink(PID_FILE); unlink(PID_FILE);
} }
/* create new pidfile */ /* create new pidfile securely without following symlinks */
pidfile = fopen(PID_FILE, "w"); fd = open(PID_FILE, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (pidfile)
{
int fd;
fd = fileno(pidfile);
if (fd == -1) if (fd == -1)
{ {
DBG1(DBG_DMN, "unable to determine fd for '"PID_FILE"'"); DBG1(DBG_DMN, "unable to create pidfile '"PID_FILE"'");
return TRUE; return TRUE;
} }
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
@@ -248,15 +263,18 @@ static bool check_pidfile()
ignore_result(fchown(fd, -1, ignore_result(fchown(fd, -1,
lib->caps->get_gid(lib->caps))); lib->caps->get_gid(lib->caps)));
} }
pidfile = fdopen(fd, "w");
if (!pidfile)
{
DBG1(DBG_DMN, "unable to open pidfile '"PID_FILE"': %s",
strerror(errno));
close(fd);
unlink(PID_FILE);
return TRUE;
}
fprintf(pidfile, "%d\n", getpid()); fprintf(pidfile, "%d\n", getpid());
fflush(pidfile); fflush(pidfile);
return FALSE; return FALSE;
}
else
{
DBG1(DBG_DMN, "unable to create pidfile '"PID_FILE"'");
return TRUE;
}
} }
/** /**