de86d1
From 81bf9b1a9f6def4a6f742a6b41ddc92005ab638f Mon Sep 17 00:00:00 2001
de86d1
From: Jakub Zelenka <bukka@php.net>
de86d1
Date: Sat, 2 Oct 2021 22:53:41 +0100
de86d1
Subject: [PATCH] Fix bug #81026 (PHP-FPM oob R/W in root process leading to
de86d1
 priv escalation)
de86d1
de86d1
The main change is to store scoreboard procs directly to the variable sized
de86d1
array rather than indirectly through the pointer.
de86d1
de86d1
Signed-off-by: Stanislav Malyshev <stas@php.net>
de86d1
---
de86d1
 sapi/fpm/fpm/fpm_children.c    |  14 ++---
de86d1
 sapi/fpm/fpm/fpm_request.c     |   4 +-
de86d1
 sapi/fpm/fpm/fpm_scoreboard.c  | 106 ++++++++++++++++++++-------------
de86d1
 sapi/fpm/fpm/fpm_scoreboard.h  |  11 ++--
de86d1
 sapi/fpm/fpm/fpm_status.c      |   4 +-
de86d1
 sapi/fpm/fpm/fpm_worker_pool.c |   2 +-
de86d1
 6 files changed, 81 insertions(+), 60 deletions(-)
de86d1
de86d1
diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c
de86d1
index fd121372f37c..912f77c11aa7 100644
de86d1
--- a/sapi/fpm/fpm/fpm_children.c
de86d1
+++ b/sapi/fpm/fpm/fpm_children.c
de86d1
@@ -246,7 +246,7 @@ void fpm_children_bury() /* {{{ */
de86d1
 
de86d1
 			fpm_child_unlink(child);
de86d1
 
de86d1
-			fpm_scoreboard_proc_free(wp->scoreboard, child->scoreboard_i);
de86d1
+			fpm_scoreboard_proc_free(child);
de86d1
 
de86d1
 			fpm_clock_get(&tv1);
de86d1
 
de86d1
@@ -256,9 +256,9 @@ void fpm_children_bury() /* {{{ */
de86d1
 				if (!fpm_pctl_can_spawn_children()) {
de86d1
 					severity = ZLOG_DEBUG;
de86d1
 				}
de86d1
-				zlog(severity, "[pool %s] child %d exited %s after %ld.%06d seconds from start", child->wp->config->name, (int) pid, buf, tv2.tv_sec, (int) tv2.tv_usec);
de86d1
+				zlog(severity, "[pool %s] child %d exited %s after %ld.%06d seconds from start", wp->config->name, (int) pid, buf, tv2.tv_sec, (int) tv2.tv_usec);
de86d1
 			} else {
de86d1
-				zlog(ZLOG_DEBUG, "[pool %s] child %d has been killed by the process management after %ld.%06d seconds from start", child->wp->config->name, (int) pid, tv2.tv_sec, (int) tv2.tv_usec);
de86d1
+				zlog(ZLOG_DEBUG, "[pool %s] child %d has been killed by the process management after %ld.%06d seconds from start", wp->config->name, (int) pid, tv2.tv_sec, (int) tv2.tv_usec);
de86d1
 			}
de86d1
 
de86d1
 			fpm_child_close(child, 1 /* in event_loop */);
de86d1
@@ -324,7 +324,7 @@ static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /
de86d1
 		return 0;
de86d1
 	}
de86d1
 
de86d1
-	if (0 > fpm_scoreboard_proc_alloc(wp->scoreboard, &c->scoreboard_i)) {
de86d1
+	if (0 > fpm_scoreboard_proc_alloc(c)) {
de86d1
 		fpm_stdio_discard_pipes(c);
de86d1
 		fpm_child_free(c);
de86d1
 		return 0;
de86d1
@@ -336,7 +336,7 @@ static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /
de86d1
 
de86d1
 static void fpm_resources_discard(struct fpm_child_s *child) /* {{{ */
de86d1
 {
de86d1
-	fpm_scoreboard_proc_free(child->wp->scoreboard, child->scoreboard_i);
de86d1
+	fpm_scoreboard_proc_free(child);
de86d1
 	fpm_stdio_discard_pipes(child);
de86d1
 	fpm_child_free(child);
de86d1
 }
de86d1
@@ -349,10 +349,10 @@ static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */
de86d1
 		if (wp == child->wp) {
de86d1
 			continue;
de86d1
 		}
de86d1
-		fpm_scoreboard_free(wp->scoreboard);
de86d1
+		fpm_scoreboard_free(wp);
de86d1
 	}
de86d1
 
de86d1
-	fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, getpid());
de86d1
+	fpm_scoreboard_child_use(child, getpid());
de86d1
 	fpm_stdio_child_use_pipes(child);
de86d1
 	fpm_child_free(child);
de86d1
 }
de86d1
diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c
de86d1
index c80aa144628f..0a6f6a7cfbf0 100644
de86d1
--- a/sapi/fpm/fpm/fpm_request.c
de86d1
+++ b/sapi/fpm/fpm/fpm_request.c
de86d1
@@ -285,7 +285,7 @@ int fpm_request_is_idle(struct fpm_child_s *child) /* {{{ */
de86d1
 	struct fpm_scoreboard_proc_s *proc;
de86d1
 
de86d1
 	/* no need in atomicity here */
de86d1
-	proc = fpm_scoreboard_proc_get(child->wp->scoreboard, child->scoreboard_i);
de86d1
+	proc = fpm_scoreboard_proc_get_from_child(child);
de86d1
 	if (!proc) {
de86d1
 		return 0;
de86d1
 	}
de86d1
@@ -300,7 +300,7 @@ int fpm_request_last_activity(struct fpm_child_s *child, struct timeval *tv) /*
de86d1
 
de86d1
 	if (!tv) return -1;
de86d1
 
de86d1
-	proc = fpm_scoreboard_proc_get(child->wp->scoreboard, child->scoreboard_i);
de86d1
+	proc = fpm_scoreboard_proc_get_from_child(child);
de86d1
 	if (!proc) {
de86d1
 		return -1;
de86d1
 	}
de86d1
diff --git a/sapi/fpm/fpm/fpm_scoreboard.c b/sapi/fpm/fpm/fpm_scoreboard.c
de86d1
index 328f999f0c9b..7e9da4d6848a 100644
de86d1
--- a/sapi/fpm/fpm/fpm_scoreboard.c
de86d1
+++ b/sapi/fpm/fpm/fpm_scoreboard.c
de86d1
@@ -6,6 +6,7 @@
de86d1
 #include <time.h>
de86d1
 
de86d1
 #include "fpm_config.h"
de86d1
+#include "fpm_children.h"
de86d1
 #include "fpm_scoreboard.h"
de86d1
 #include "fpm_shm.h"
de86d1
 #include "fpm_sockets.h"
de86d1
@@ -23,7 +24,6 @@ static float fpm_scoreboard_tick;
de86d1
 int fpm_scoreboard_init_main() /* {{{ */
de86d1
 {
de86d1
 	struct fpm_worker_pool_s *wp;
de86d1
-	unsigned int i;
de86d1
 
de86d1
 #ifdef HAVE_TIMES
de86d1
 #if (defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK))
de86d1
@@ -40,7 +40,7 @@ int fpm_scoreboard_init_main() /* {{{ */
de86d1
 
de86d1
 
de86d1
 	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
de86d1
-		size_t scoreboard_size, scoreboard_nprocs_size;
de86d1
+		size_t scoreboard_procs_size;
de86d1
 		void *shm_mem;
de86d1
 
de86d1
 		if (wp->config->pm_max_children < 1) {
de86d1
@@ -53,22 +53,15 @@ int fpm_scoreboard_init_main() /* {{{ */
de86d1
 			return -1;
de86d1
 		}
de86d1
 
de86d1
-		scoreboard_size        = sizeof(struct fpm_scoreboard_s) + (wp->config->pm_max_children) * sizeof(struct fpm_scoreboard_proc_s *);
de86d1
-		scoreboard_nprocs_size = sizeof(struct fpm_scoreboard_proc_s) * wp->config->pm_max_children;
de86d1
-		shm_mem                = fpm_shm_alloc(scoreboard_size + scoreboard_nprocs_size);
de86d1
+		scoreboard_procs_size = sizeof(struct fpm_scoreboard_proc_s) * wp->config->pm_max_children;
de86d1
+		shm_mem = fpm_shm_alloc(sizeof(struct fpm_scoreboard_s) + scoreboard_procs_size);
de86d1
 
de86d1
 		if (!shm_mem) {
de86d1
 			return -1;
de86d1
 		}
de86d1
-		wp->scoreboard         = shm_mem;
de86d1
+		wp->scoreboard = shm_mem;
de86d1
+		wp->scoreboard->pm = wp->config->pm;
de86d1
 		wp->scoreboard->nprocs = wp->config->pm_max_children;
de86d1
-		shm_mem               += scoreboard_size;
de86d1
-
de86d1
-		for (i = 0; i < wp->scoreboard->nprocs; i++, shm_mem += sizeof(struct fpm_scoreboard_proc_s)) {
de86d1
-			wp->scoreboard->procs[i] = shm_mem;
de86d1
-		}
de86d1
-
de86d1
-		wp->scoreboard->pm          = wp->config->pm;
de86d1
 		wp->scoreboard->start_epoch = time(NULL);
de86d1
 		strlcpy(wp->scoreboard->pool, wp->config->name, sizeof(wp->scoreboard->pool));
de86d1
 	}
de86d1
@@ -162,28 +155,48 @@ struct fpm_scoreboard_s *fpm_scoreboard_get() /* {{{*/
de86d1
 }
de86d1
 /* }}} */
de86d1
 
de86d1
-struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get(struct fpm_scoreboard_s *scoreboard, int child_index) /* {{{*/
de86d1
+static inline struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get_ex(
de86d1
+		struct fpm_scoreboard_s *scoreboard, int child_index, unsigned int nprocs) /* {{{*/
de86d1
 {
de86d1
 	if (!scoreboard) {
de86d1
-		scoreboard = fpm_scoreboard;
de86d1
+		return NULL;
de86d1
 	}
de86d1
 
de86d1
-	if (!scoreboard) {
de86d1
+	if (child_index < 0 || (unsigned int)child_index >= nprocs) {
de86d1
 		return NULL;
de86d1
 	}
de86d1
 
de86d1
+	return &scoreboard->procs[child_index];
de86d1
+}
de86d1
+/* }}} */
de86d1
+
de86d1
+struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get(
de86d1
+		struct fpm_scoreboard_s *scoreboard, int child_index) /* {{{*/
de86d1
+{
de86d1
+	if (!scoreboard) {
de86d1
+		scoreboard = fpm_scoreboard;
de86d1
+	}
de86d1
+
de86d1
 	if (child_index < 0) {
de86d1
 		child_index = fpm_scoreboard_i;
de86d1
 	}
de86d1
 
de86d1
-	if (child_index < 0 || (unsigned int)child_index >= scoreboard->nprocs) {
de86d1
-		return NULL;
de86d1
-	}
de86d1
+	return fpm_scoreboard_proc_get_ex(scoreboard, child_index, scoreboard->nprocs);
de86d1
+}
de86d1
+/* }}} */
de86d1
 
de86d1
-	return scoreboard->procs[child_index];
de86d1
+struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get_from_child(struct fpm_child_s *child) /* {{{*/
de86d1
+{
de86d1
+	struct fpm_worker_pool_s *wp = child->wp;
de86d1
+	unsigned int nprocs = wp->config->pm_max_children;
de86d1
+	struct fpm_scoreboard_s *scoreboard = wp->scoreboard;
de86d1
+	int child_index = child->scoreboard_i;
de86d1
+
de86d1
+	return fpm_scoreboard_proc_get_ex(scoreboard, child_index, nprocs);
de86d1
 }
de86d1
 /* }}} */
de86d1
 
de86d1
+
de86d1
 struct fpm_scoreboard_s *fpm_scoreboard_acquire(struct fpm_scoreboard_s *scoreboard, int nohang) /* {{{ */
de86d1
 {
de86d1
 	struct fpm_scoreboard_s *s;
de86d1
@@ -234,28 +247,28 @@ void fpm_scoreboard_proc_release(struct fpm_scoreboard_proc_s *proc) /* {{{ */
de86d1
 	proc->lock = 0;
de86d1
 }
de86d1
 
de86d1
-void fpm_scoreboard_free(struct fpm_scoreboard_s *scoreboard) /* {{{ */
de86d1
+void fpm_scoreboard_free(struct fpm_worker_pool_s *wp) /* {{{ */
de86d1
 {
de86d1
-	size_t scoreboard_size, scoreboard_nprocs_size;
de86d1
+	size_t scoreboard_procs_size;
de86d1
+	struct fpm_scoreboard_s *scoreboard = wp->scoreboard;
de86d1
 
de86d1
 	if (!scoreboard) {
de86d1
 		zlog(ZLOG_ERROR, "**scoreboard is NULL");
de86d1
 		return;
de86d1
 	}
de86d1
 
de86d1
-	scoreboard_size        = sizeof(struct fpm_scoreboard_s) + (scoreboard->nprocs) * sizeof(struct fpm_scoreboard_proc_s *);
de86d1
-	scoreboard_nprocs_size = sizeof(struct fpm_scoreboard_proc_s) * scoreboard->nprocs;
de86d1
+	scoreboard_procs_size = sizeof(struct fpm_scoreboard_proc_s) * wp->config->pm_max_children;
de86d1
 
de86d1
-	fpm_shm_free(scoreboard, scoreboard_size + scoreboard_nprocs_size);
de86d1
+	fpm_shm_free(scoreboard, sizeof(struct fpm_scoreboard_s) + scoreboard_procs_size);
de86d1
 }
de86d1
 /* }}} */
de86d1
 
de86d1
-void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_index, pid_t pid) /* {{{ */
de86d1
+void fpm_scoreboard_child_use(struct fpm_child_s *child, pid_t pid) /* {{{ */
de86d1
 {
de86d1
 	struct fpm_scoreboard_proc_s *proc;
de86d1
-	fpm_scoreboard = scoreboard;
de86d1
-	fpm_scoreboard_i = child_index;
de86d1
-	proc = fpm_scoreboard_proc_get(scoreboard, child_index);
de86d1
+	fpm_scoreboard = child->wp->scoreboard;
de86d1
+	fpm_scoreboard_i = child->scoreboard_i;
de86d1
+	proc = fpm_scoreboard_proc_get_from_child(child);
de86d1
 	if (!proc) {
de86d1
 		return;
de86d1
 	}
de86d1
@@ -264,18 +277,22 @@ void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_ind
de86d1
 }
de86d1
 /* }}} */
de86d1
 
de86d1
-void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_index) /* {{{ */
de86d1
+void fpm_scoreboard_proc_free(struct fpm_child_s *child) /* {{{ */
de86d1
 {
de86d1
+	struct fpm_worker_pool_s *wp = child->wp;
de86d1
+	struct fpm_scoreboard_s *scoreboard = wp->scoreboard;
de86d1
+	int child_index = child->scoreboard_i;
de86d1
+
de86d1
 	if (!scoreboard) {
de86d1
 		return;
de86d1
 	}
de86d1
 
de86d1
-	if (child_index < 0 || (unsigned int)child_index >= scoreboard->nprocs) {
de86d1
+	if (child_index < 0 || child_index >= wp->config->pm_max_children) {
de86d1
 		return;
de86d1
 	}
de86d1
 
de86d1
-	if (scoreboard->procs[child_index] && scoreboard->procs[child_index]->used > 0) {
de86d1
-		memset(scoreboard->procs[child_index], 0, sizeof(struct fpm_scoreboard_proc_s));
de86d1
+	if (scoreboard->procs[child_index].used > 0) {
de86d1
+		memset(&scoreboard->procs[child_index], 0, sizeof(struct fpm_scoreboard_proc_s));
de86d1
 	}
de86d1
 
de86d1
 	/* set this slot as free to avoid search on next alloc */
de86d1
@@ -283,41 +300,44 @@ void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_ind
de86d1
 }
de86d1
 /* }}} */
de86d1
 
de86d1
-int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_index) /* {{{ */
de86d1
+int fpm_scoreboard_proc_alloc(struct fpm_child_s *child) /* {{{ */
de86d1
 {
de86d1
 	int i = -1;
de86d1
+	struct fpm_worker_pool_s *wp = child->wp;
de86d1
+	struct fpm_scoreboard_s *scoreboard = wp->scoreboard;
de86d1
+	int nprocs = wp->config->pm_max_children;
de86d1
 
de86d1
-	if (!scoreboard || !child_index) {
de86d1
+	if (!scoreboard) {
de86d1
 		return -1;
de86d1
 	}
de86d1
 
de86d1
 	/* first try the slot which is supposed to be free */
de86d1
-	if (scoreboard->free_proc >= 0 && (unsigned int)scoreboard->free_proc < scoreboard->nprocs) {
de86d1
-		if (scoreboard->procs[scoreboard->free_proc] && !scoreboard->procs[scoreboard->free_proc]->used) {
de86d1
+	if (scoreboard->free_proc >= 0 && scoreboard->free_proc < nprocs) {
de86d1
+		if (!scoreboard->procs[scoreboard->free_proc].used) {
de86d1
 			i = scoreboard->free_proc;
de86d1
 		}
de86d1
 	}
de86d1
 
de86d1
 	if (i < 0) { /* the supposed free slot is not, let's search for a free slot */
de86d1
 		zlog(ZLOG_DEBUG, "[pool %s] the proc->free_slot was not free. Let's search", scoreboard->pool);
de86d1
-		for (i = 0; i < (int)scoreboard->nprocs; i++) {
de86d1
-			if (scoreboard->procs[i] && !scoreboard->procs[i]->used) { /* found */
de86d1
+		for (i = 0; i < nprocs; i++) {
de86d1
+			if (!scoreboard->procs[i].used) { /* found */
de86d1
 				break;
de86d1
 			}
de86d1
 		}
de86d1
 	}
de86d1
 
de86d1
 	/* no free slot */
de86d1
-	if (i < 0 || i >= (int)scoreboard->nprocs) {
de86d1
+	if (i < 0 || i >= nprocs) {
de86d1
 		zlog(ZLOG_ERROR, "[pool %s] no free scoreboard slot", scoreboard->pool);
de86d1
 		return -1;
de86d1
 	}
de86d1
 
de86d1
-	scoreboard->procs[i]->used = 1;
de86d1
-	*child_index = i;
de86d1
+	scoreboard->procs[i].used = 1;
de86d1
+	child->scoreboard_i = i;
de86d1
 
de86d1
 	/* supposed next slot is free */
de86d1
-	if (i + 1 >= (int)scoreboard->nprocs) {
de86d1
+	if (i + 1 >= nprocs) {
de86d1
 		scoreboard->free_proc = 0;
de86d1
 	} else {
de86d1
 		scoreboard->free_proc = i + 1;
de86d1
diff --git a/sapi/fpm/fpm/fpm_scoreboard.h b/sapi/fpm/fpm/fpm_scoreboard.h
de86d1
index 1fecde1d0feb..9d5981e1c739 100644
de86d1
--- a/sapi/fpm/fpm/fpm_scoreboard.h
de86d1
+++ b/sapi/fpm/fpm/fpm_scoreboard.h
de86d1
@@ -63,7 +63,7 @@ struct fpm_scoreboard_s {
de86d1
 	unsigned int nprocs;
de86d1
 	int free_proc;
de86d1
 	unsigned long int slow_rq;
de86d1
-	struct fpm_scoreboard_proc_s *procs[];
de86d1
+	struct fpm_scoreboard_proc_s procs[];
de86d1
 };
de86d1
 
de86d1
 int fpm_scoreboard_init_main();
de86d1
@@ -72,18 +72,19 @@ int fpm_scoreboard_init_child(struct fpm_worker_pool_s *wp);
de86d1
 void fpm_scoreboard_update(int idle, int active, int lq, int lq_len, int requests, int max_children_reached, int slow_rq, int action, struct fpm_scoreboard_s *scoreboard);
de86d1
 struct fpm_scoreboard_s *fpm_scoreboard_get();
de86d1
 struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get(struct fpm_scoreboard_s *scoreboard, int child_index);
de86d1
+struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get_from_child(struct fpm_child_s *child);
de86d1
 
de86d1
 struct fpm_scoreboard_s *fpm_scoreboard_acquire(struct fpm_scoreboard_s *scoreboard, int nohang);
de86d1
 void fpm_scoreboard_release(struct fpm_scoreboard_s *scoreboard);
de86d1
 struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_acquire(struct fpm_scoreboard_s *scoreboard, int child_index, int nohang);
de86d1
 void fpm_scoreboard_proc_release(struct fpm_scoreboard_proc_s *proc);
de86d1
 
de86d1
-void fpm_scoreboard_free(struct fpm_scoreboard_s *scoreboard);
de86d1
+void fpm_scoreboard_free(struct fpm_worker_pool_s *wp);
de86d1
 
de86d1
-void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_index, pid_t pid);
de86d1
+void fpm_scoreboard_child_use(struct fpm_child_s *child, pid_t pid);
de86d1
 
de86d1
-void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_index);
de86d1
-int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_index);
de86d1
+void fpm_scoreboard_proc_free(struct fpm_child_s *child);
de86d1
+int fpm_scoreboard_proc_alloc(struct fpm_child_s *child);
de86d1
 
de86d1
 #ifdef HAVE_TIMES
de86d1
 float fpm_scoreboard_get_tick();
de86d1
diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c
de86d1
index 36d224063583..de8db9d61a25 100644
de86d1
--- a/sapi/fpm/fpm/fpm_status.c
de86d1
+++ b/sapi/fpm/fpm/fpm_status.c
de86d1
@@ -498,10 +498,10 @@ int fpm_status_handle_request(void) /* {{{ */
de86d1
 
de86d1
 			first = 1;
de86d1
 			for (i=0; i<scoreboard_p->nprocs; i++) {
de86d1
-				if (!scoreboard_p->procs[i] || !scoreboard_p->procs[i]->used) {
de86d1
+				if (!scoreboard_p->procs[i].used) {
de86d1
 					continue;
de86d1
 				}
de86d1
-				proc = *scoreboard_p->procs[i];
de86d1
+				proc = scoreboard_p->procs[i];
de86d1
 
de86d1
 				if (first) {
de86d1
 					first = 0;
de86d1
diff --git a/sapi/fpm/fpm/fpm_worker_pool.c b/sapi/fpm/fpm/fpm_worker_pool.c
de86d1
index d04528f4e0d0..65a9b226b1ae 100644
de86d1
--- a/sapi/fpm/fpm/fpm_worker_pool.c
de86d1
+++ b/sapi/fpm/fpm/fpm_worker_pool.c
de86d1
@@ -54,7 +54,7 @@ static void fpm_worker_pool_cleanup(int which, void *arg) /* {{{ */
de86d1
 		fpm_worker_pool_config_free(wp->config);
de86d1
 		fpm_children_free(wp->children);
de86d1
 		if ((which & FPM_CLEANUP_CHILD) == 0 && fpm_globals.parent_pid == getpid()) {
de86d1
-			fpm_scoreboard_free(wp->scoreboard);
de86d1
+			fpm_scoreboard_free(wp);
de86d1
 		}
de86d1
 		fpm_worker_pool_free(wp);
de86d1
 	}