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