commit 99d4faba1f65d318c962b056d8cc76d60c7b9ac6
Author: GONG Jie <jgong@suse.com>
Date:   Wed Aug 28 12:42:30 2024 +0800

    Description: Enhance how validations are done to avoid DoSes by excessive CPU
    consumption
    
    Origin: backport, https://downloads.isc.org/isc/bind9/9.16.48/patches/0005-CVE-2023-50387-CVE-2023-50868.patch

diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c
index 5834a58..e7034b1 100644
--- a/lib/dns/dst_api.c
+++ b/lib/dns/dst_api.c
@@ -112,6 +112,7 @@ static isc_result_t	frombuffer(dns_name_t *name,
 				   dns_rdataclass_t rdclass,
 				   isc_buffer_t *source,
 				   isc_mem_t *mctx,
+				   isc_boolean_t no_rdata,
 				   dst_key_t **keyp);
 
 static isc_result_t	algorithm_status(unsigned int alg);
@@ -643,6 +644,14 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
 isc_result_t
 dst_key_fromdns(dns_name_t *name, dns_rdataclass_t rdclass,
 		isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp)
+{
+	return (dst_key_fromdns_ex(name, rdclass, source, mctx, ISC_FALSE, keyp));
+}
+
+isc_result_t
+dst_key_fromdns_ex(dns_name_t *name, dns_rdataclass_t rdclass,
+		isc_buffer_t *source, isc_mem_t *mctx, isc_boolean_t no_rdata,
+		dst_key_t **keyp)
 {
 	isc_uint8_t alg, proto;
 	isc_uint32_t flags, extflags;
@@ -672,7 +681,7 @@ dst_key_fromdns(dns_name_t *name, dns_rdataclass_t rdclass,
 	}
 
 	result = frombuffer(name, alg, flags, proto, rdclass, source,
-			    mctx, &key);
+			    mctx, no_rdata, &key);
 	if (result != ISC_R_SUCCESS)
 		return (result);
 	key->key_id = id;
@@ -694,7 +703,7 @@ dst_key_frombuffer(dns_name_t *name, unsigned int alg,
 	REQUIRE(dst_initialized);
 
 	result = frombuffer(name, alg, flags, protocol, rdclass, source,
-			    mctx, &key);
+			    mctx, ISC_FALSE, &key);
 	if (result != ISC_R_SUCCESS)
 		return (result);
 
@@ -1754,7 +1763,8 @@ computeid(dst_key_t *key) {
 static isc_result_t
 frombuffer(dns_name_t *name, unsigned int alg, unsigned int flags,
 	   unsigned int protocol, dns_rdataclass_t rdclass,
-	   isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp)
+	   isc_buffer_t *source, isc_mem_t *mctx, isc_boolean_t no_rdata,
+	   dst_key_t **keyp)
 {
 	dst_key_t *key;
 	isc_result_t ret;
@@ -1779,10 +1789,12 @@ frombuffer(dns_name_t *name, unsigned int alg, unsigned int flags,
 			return (DST_R_UNSUPPORTEDALG);
 		}
 
-		ret = key->func->fromdns(key, source);
-		if (ret != ISC_R_SUCCESS) {
-			dst_key_free(&key);
-			return (ret);
+		if (!no_rdata) {
+			ret = key->func->fromdns(key, source);
+			if (ret != ISC_R_SUCCESS) {
+				dst_key_free(&key);
+				return (ret);
+			}
 		}
 	}
 
diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h
index b3cfe99..16f8db9 100644
--- a/lib/dns/include/dns/validator.h
+++ b/lib/dns/include/dns/validator.h
@@ -165,6 +165,7 @@ struct dns_validator {
 	unsigned int			depth;
 	unsigned int			authcount;
 	unsigned int			authfail;
+	isc_boolean_t			failed;
 };
 
 /*%
diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h
index 1497edc..e3c2e20 100644
--- a/lib/dns/include/dst/dst.h
+++ b/lib/dns/include/dst/dst.h
@@ -375,6 +375,10 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory);
  */
 
 isc_result_t
+dst_key_fromdns_ex(dns_name_t *name, dns_rdataclass_t rdclass,
+		   isc_buffer_t *source, isc_mem_t *mctx, isc_boolean_t no_rdata,
+		   dst_key_t **keyp);
+isc_result_t
 dst_key_fromdns(dns_name_t *name, dns_rdataclass_t rdclass,
 		isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
 /*%<
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 5f75bc0..3bed591 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -8345,7 +8345,7 @@ dns_resolver_create(dns_view_t *view,
 		if (result != ISC_R_SUCCESS)
 			goto cleanup_buckets;
 		res->buckets[i].task = NULL;
-		result = isc_task_create(taskmgr, 0, &res->buckets[i].task);
+		result = isc_task_create(taskmgr, ISC_TASK_QUANTUM_SLOW, &res->buckets[i].task);
 		if (result != ISC_R_SUCCESS) {
 			DESTROYLOCK(&res->buckets[i].lock);
 			goto cleanup_buckets;
diff --git a/lib/dns/validator.c b/lib/dns/validator.c
index 1d0b709..d478044 100644
--- a/lib/dns/validator.c
+++ b/lib/dns/validator.c
@@ -1201,6 +1201,12 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
  * val->key at it.
  *
  * If val->key is non-NULL, this returns the next matching key.
+ * If val->key is already non-NULL, start searching from the next position in
+ * 'rdataset' to find the *next* key that could have signed 'siginfo', then
+ * set val->key to that.
+ *
+ * Returns ISC_R_SUCCESS if a possible matching key has been found,
+ * ISC_R_NOTFOUND if not. Any other value indicates error.
  */
 static isc_result_t
 get_dst_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo,
@@ -1210,56 +1216,59 @@ get_dst_key(dns_validator_t *val, dns_rdata_rrsig_t *siginfo,
 	isc_buffer_t b;
 	dns_rdata_t rdata = DNS_RDATA_INIT;
 	dst_key_t *oldkey = val->key;
-	isc_boolean_t foundold;
+	isc_boolean_t no_rdata = ISC_FALSE;
 
 	if (oldkey == NULL)
-		foundold = ISC_TRUE;
+		result = dns_rdataset_first(rdataset);
 	else {
-		foundold = ISC_FALSE;
+		dst_key_free(&oldkey);
 		val->key = NULL;
+		result = dns_rdataset_next(rdataset);
 	}
 
-	result = dns_rdataset_first(rdataset);
 	if (result != ISC_R_SUCCESS)
-		goto failure;
+		goto done;
+
 	do {
 		dns_rdataset_current(rdataset, &rdata);
 
 		isc_buffer_init(&b, rdata.data, rdata.length);
 		isc_buffer_add(&b, rdata.length);
 		INSIST(val->key == NULL);
-		result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b,
-					 val->view->mctx, &val->key);
-		if (result != ISC_R_SUCCESS)
-			goto failure;
-		if (siginfo->algorithm ==
-		    (dns_secalg_t)dst_key_alg(val->key) &&
-		    siginfo->keyid ==
-		    (dns_keytag_t)dst_key_id(val->key) &&
-		    dst_key_iszonekey(val->key))
+		result = dst_key_fromdns_ex(&siginfo->signer, rdata.rdclass, &b,
+					 val->view->mctx, no_rdata,
+					 &val->key);
+		if (result == ISC_R_SUCCESS)
 		{
-			if (foundold)
-				/*
-				 * This is the key we're looking for.
-				 */
-				return (ISC_R_SUCCESS);
-			else if (dst_key_compare(oldkey, val->key) == ISC_TRUE)
+			if (siginfo->algorithm ==
+				(dns_secalg_t)dst_key_alg(val->key) &&
+				siginfo->keyid ==
+				(dns_keytag_t)dst_key_id(val->key) &&
+				(dst_key_flags(val->key) & DNS_KEYFLAG_REVOKE) ==
+				0 &&
+				dst_key_iszonekey(val->key))
 			{
-				foundold = ISC_TRUE;
-				dst_key_free(&oldkey);
+				if (no_rdata) {
+					/* Retry with full key */
+					dns_rdata_reset(&rdata);
+					dst_key_free(&val->key);
+					no_rdata = ISC_FALSE;
+					continue;
+				}
+				/* This is the key we're looking for. */
+				goto done;
 			}
+			dst_key_free(&val->key);
 		}
-		dst_key_free(&val->key);
 		dns_rdata_reset(&rdata);
 		result = dns_rdataset_next(rdataset);
+		no_rdata = ISC_TRUE;
 	} while (result == ISC_R_SUCCESS);
+
+done:
 	if (result == ISC_R_NOMORE)
 		result = ISC_R_NOTFOUND;
 
- failure:
-	if (oldkey != NULL)
-		dst_key_free(&oldkey);
-
 	return (result);
 }
 
@@ -1627,38 +1636,13 @@ validate(dns_validator_t *val, isc_boolean_t resume) {
 			resume = ISC_FALSE;
 			continue;
 		}
-
-		do {
-			result = verify(val, val->key, &rdata,
-					val->siginfo->keyid);
-			if (result == ISC_R_SUCCESS)
-				break;
-			if (val->keynode != NULL) {
-				dns_keynode_t *nextnode = NULL;
-				result = dns_keytable_findnextkeynode(
-							val->keytable,
-							val->keynode,
-							&nextnode);
-				dns_keytable_detachkeynode(val->keytable,
-							   &val->keynode);
-				val->keynode = nextnode;
-				if (result != ISC_R_SUCCESS) {
-					val->key = NULL;
-					break;
-				}
-				val->key = dns_keynode_key(val->keynode);
-				if (val->key == NULL)
-					break;
-			} else {
-				if (get_dst_key(val, val->siginfo, val->keyset)
-				    != ISC_R_SUCCESS)
-					break;
-			}
-		} while (1);
-		if (result != ISC_R_SUCCESS)
+		result = verify(val, val->key, &rdata,
+				val->siginfo->keyid);
+		if (result != ISC_R_SUCCESS) {
+			val->failed = ISC_TRUE;
 			validator_log(val, ISC_LOG_DEBUG(3),
 				      "failed to verify rdataset");
-		else {
+		} else {
 			isc_stdtime_t now;
 
 			isc_stdtime_get(&now);
@@ -1701,6 +1685,10 @@ validate(dns_validator_t *val, isc_boolean_t resume) {
 				      isc_result_totext(result));
 			resume = ISC_FALSE;
 		}
+		if (val->failed) {
+			result = ISC_R_NOMORE;
+			break;
+		}
 	}
 	if (result != ISC_R_NOMORE) {
 		validator_log(val, ISC_LOG_DEBUG(3),
diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in
index 3b13b85..c1d547f 100644
--- a/lib/dns/win32/libdns.def.in
+++ b/lib/dns/win32/libdns.def.in
@@ -1183,6 +1183,7 @@ dst_key_format
 dst_key_free
 dst_key_frombuffer
 dst_key_fromdns
+dst_key_fromdns_ex
 dst_key_fromfile
 dst_key_fromgssapi
 dst_key_fromlabel
diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h
index a42a7e6..a3754b2 100644
--- a/lib/isc/include/isc/task.h
+++ b/lib/isc/include/isc/task.h
@@ -102,8 +102,15 @@ ISC_LANG_BEGINDECLS
  ***/
 
 typedef enum {
-		isc_taskmgrmode_normal = 0,
-		isc_taskmgrmode_privileged
+	isc_taskqueue_normal = 0,
+	isc_taskqueue_slow = 1,
+} isc_taskqueue_t;
+
+#define ISC_TASK_QUANTUM_SLOW 1024
+
+typedef enum {
+	isc_taskmgrmode_normal = 0,
+	isc_taskmgrmode_privileged
 } isc_taskmgrmode_t;
 
 /*% Task and task manager methods */
diff --git a/lib/isc/task.c b/lib/isc/task.c
index 81785e9..6ac151f 100644
--- a/lib/isc/task.c
+++ b/lib/isc/task.c
@@ -110,6 +110,7 @@ struct isc__task {
 	isc_eventlist_t			events;
 	isc_eventlist_t			on_shutdown;
 	unsigned int			quantum;
+	unsigned int			qid;
 	unsigned int			flags;
 	isc_stdtime_t			now;
 	char				name[16];
@@ -143,11 +144,11 @@ struct isc__taskmgr {
 	/* Locked by task manager lock. */
 	unsigned int			default_quantum;
 	LIST(isc__task_t)		tasks;
-	isc__tasklist_t			ready_tasks;
-	isc__tasklist_t			ready_priority_tasks;
+	isc__tasklist_t			ready_tasks[2];
+	isc__tasklist_t			ready_priority_tasks[2];
 	isc_taskmgrmode_t		mode;
 #ifdef ISC_PLATFORM_USETHREADS
-	isc_condition_t			work_available;
+	isc_condition_t			work_available[2];
 	isc_condition_t			exclusive_granted;
 	isc_condition_t			paused;
 #endif /* ISC_PLATFORM_USETHREADS */
@@ -250,13 +251,13 @@ ISC_TASKFUNC_SCOPE isc_taskmgrmode_t
 isc__taskmgr_mode(isc_taskmgr_t *manager0);
 
 static inline isc_boolean_t
-empty_readyq(isc__taskmgr_t *manager);
+empty_readyq(isc__taskmgr_t *manager, isc_taskqueue_t qid);
 
 static inline isc__task_t *
-pop_readyq(isc__taskmgr_t *manager);
+pop_readyq(isc__taskmgr_t *manager, isc_taskqueue_t qid);
 
 static inline void
-push_readyq(isc__taskmgr_t *manager, isc__task_t *task);
+push_readyq(isc__taskmgr_t *manager, isc__task_t *task, isc_taskqueue_t qid);
 
 static struct isc__taskmethods {
 	isc_taskmethods_t methods;
@@ -327,7 +328,8 @@ task_finished(isc__task_t *task) {
 		 * any idle worker threads so they
 		 * can exit.
 		 */
-		BROADCAST(&manager->work_available);
+		BROADCAST(&manager->work_available[isc_taskqueue_normal]);
+		BROADCAST(&manager->work_available[isc_taskqueue_slow]);
 	}
 #endif /* USE_WORKER_THREADS */
 	UNLOCK(&manager->lock);
@@ -364,7 +366,13 @@ isc__task_create(isc_taskmgr_t *manager0, unsigned int quantum,
 	task->references = 1;
 	INIT_LIST(task->events);
 	INIT_LIST(task->on_shutdown);
-	task->quantum = quantum;
+	if (quantum >= ISC_TASK_QUANTUM_SLOW) {
+		task->qid = isc_taskqueue_slow;
+		task->quantum = quantum - ISC_TASK_QUANTUM_SLOW;
+	} else {
+		task->qid = isc_taskqueue_normal;
+		task->quantum = quantum;
+	}
 	task->flags = 0;
 	task->now = 0;
 	memset(task->name, 0, sizeof(task->name));
@@ -473,10 +481,12 @@ task_ready(isc__task_t *task) {
 	XTRACE("task_ready");
 
 	LOCK(&manager->lock);
-	push_readyq(manager, task);
+	LOCK(&task->lock);
+	push_readyq(manager, task, task->qid);
+	UNLOCK(&task->lock);
 #ifdef USE_WORKER_THREADS
 	if (manager->mode == isc_taskmgrmode_normal || has_privilege)
-		SIGNAL(&manager->work_available);
+		SIGNAL(&manager->work_available[task->qid]);
 #endif /* USE_WORKER_THREADS */
 	UNLOCK(&manager->lock);
 }
@@ -931,13 +941,13 @@ isc__task_getcurrenttime(isc_task_t *task0, isc_stdtime_t *t) {
  * Caller must hold the task manager lock.
  */
 static inline isc_boolean_t
-empty_readyq(isc__taskmgr_t *manager) {
+empty_readyq(isc__taskmgr_t *manager, isc_taskqueue_t qid) {
 	isc__tasklist_t queue;
 
 	if (manager->mode == isc_taskmgrmode_normal)
-		queue = manager->ready_tasks;
+		queue = manager->ready_tasks[qid];
 	else
-		queue = manager->ready_priority_tasks;
+		queue = manager->ready_priority_tasks[qid];
 
 	return (ISC_TF(EMPTY(queue)));
 }
@@ -951,18 +961,18 @@ empty_readyq(isc__taskmgr_t *manager) {
  * Caller must hold the task manager lock.
  */
 static inline isc__task_t *
-pop_readyq(isc__taskmgr_t *manager) {
+pop_readyq(isc__taskmgr_t *manager, isc_taskqueue_t qid) {
 	isc__task_t *task;
 
 	if (manager->mode == isc_taskmgrmode_normal)
-		task = HEAD(manager->ready_tasks);
+		task = HEAD(manager->ready_tasks[qid]);
 	else
-		task = HEAD(manager->ready_priority_tasks);
+		task = HEAD(manager->ready_priority_tasks[qid]);
 
 	if (task != NULL) {
-		DEQUEUE(manager->ready_tasks, task, ready_link);
+		DEQUEUE(manager->ready_tasks[qid], task, ready_link);
 		if (ISC_LINK_LINKED(task, ready_priority_link))
-			DEQUEUE(manager->ready_priority_tasks, task,
+			DEQUEUE(manager->ready_priority_tasks[qid], task,
 				ready_priority_link);
 	}
 
@@ -976,15 +986,15 @@ pop_readyq(isc__taskmgr_t *manager) {
  * Caller must hold the task manager lock.
  */
 static inline void
-push_readyq(isc__taskmgr_t *manager, isc__task_t *task) {
-	ENQUEUE(manager->ready_tasks, task, ready_link);
+push_readyq(isc__taskmgr_t *manager, isc__task_t *task, isc_taskqueue_t qid) {
+	ENQUEUE(manager->ready_tasks[qid], task, ready_link);
 	if ((task->flags & TASK_F_PRIVILEGED) != 0)
-		ENQUEUE(manager->ready_priority_tasks, task,
+		ENQUEUE(manager->ready_priority_tasks[qid], task,
 			ready_priority_link);
 }
 
 static void
-dispatch(isc__taskmgr_t *manager) {
+dispatch(isc__taskmgr_t *manager, isc_taskqueue_t qid) {
 	isc__task_t *task;
 #ifndef USE_WORKER_THREADS
 	unsigned int total_dispatch_count = 0;
@@ -1062,26 +1072,26 @@ dispatch(isc__taskmgr_t *manager) {
 		 * If a pause has been requested, don't do any work
 		 * until it's been released.
 		 */
-		while ((empty_readyq(manager) || manager->pause_requested ||
+		while ((empty_readyq(manager, qid) || manager->pause_requested ||
 			manager->exclusive_requested) && !FINISHED(manager))
 		{
 			XTHREADTRACE(isc_msgcat_get(isc_msgcat,
 						    ISC_MSGSET_GENERAL,
 						    ISC_MSG_WAIT, "wait"));
-			WAIT(&manager->work_available, &manager->lock);
+			WAIT(&manager->work_available[qid], &manager->lock);
 			XTHREADTRACE(isc_msgcat_get(isc_msgcat,
 						    ISC_MSGSET_TASK,
 						    ISC_MSG_AWAKE, "awake"));
 		}
 #else /* USE_WORKER_THREADS */
 		if (total_dispatch_count >= DEFAULT_TASKMGR_QUANTUM ||
-		    empty_readyq(manager))
+		    empty_readyq(manager, qid))
 			break;
 #endif /* USE_WORKER_THREADS */
 		XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_TASK,
 					    ISC_MSG_WORKING, "working"));
 
-		task = pop_readyq(manager);
+		task = pop_readyq(manager, qid);
 		if (task != NULL) {
 			unsigned int dispatch_count = 0;
 			isc_boolean_t done = ISC_FALSE;
@@ -1242,7 +1252,9 @@ dispatch(isc__taskmgr_t *manager) {
 				 * might even hurt rather than help.
 				 */
 #ifdef USE_WORKER_THREADS
-				push_readyq(manager, task);
+			  LOCK(&task->lock);
+			  push_readyq(manager, task, qid);
+			  UNLOCK(&task->lock);
 #else
 				ENQUEUE(new_ready_tasks, task, ready_link);
 				if ((task->flags & TASK_F_PRIVILEGED) != 0)
@@ -1259,19 +1271,23 @@ dispatch(isc__taskmgr_t *manager) {
 		 * we're stuck.  Automatically drop privileges at that
 		 * point and continue with the regular ready queue.
 		 */
-		if (manager->tasks_running == 0 && empty_readyq(manager)) {
+		if (manager->tasks_running == 0 && empty_readyq(manager, isc_taskqueue_normal) && empty_readyq(manager, isc_taskqueue_slow)) {
 			manager->mode = isc_taskmgrmode_normal;
-			if (!empty_readyq(manager))
-				BROADCAST(&manager->work_available);
+			if (!empty_readyq(manager, isc_taskqueue_normal)) {
+				BROADCAST(&manager->work_available[isc_taskqueue_normal]);
+			}
+			if (!empty_readyq(manager, isc_taskqueue_slow)) {
+				BROADCAST(&manager->work_available[isc_taskqueue_slow]);
+			}
 		}
 #endif
 	}
 
 #ifndef USE_WORKER_THREADS
-	ISC_LIST_APPENDLIST(manager->ready_tasks, new_ready_tasks, ready_link);
-	ISC_LIST_APPENDLIST(manager->ready_priority_tasks, new_priority_tasks,
+	ISC_LIST_APPENDLIST(manager->ready_tasks[qid], new_ready_tasks, ready_link);
+	ISC_LIST_APPENDLIST(manager->ready_priority_tasks[qid], new_priority_tasks,
 			    ready_priority_link);
-	if (empty_readyq(manager))
+	if (empty_readyq(manager, qid))
 		manager->mode = isc_taskmgrmode_normal;
 #endif
 
@@ -1283,13 +1299,37 @@ static isc_threadresult_t
 #ifdef _WIN32
 WINAPI
 #endif
-run(void *uap) {
+run_normal(void *uap) {
+	isc__taskmgr_t *manager = uap;
+
+	XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
+				    ISC_MSG_STARTING, "starting"));
+
+	dispatch(manager, isc_taskqueue_normal);
+
+	XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
+				    ISC_MSG_EXITING, "exiting"));
+
+#ifdef OPENSSL_LEAKS
+	ERR_remove_state(0);
+#endif
+
+	return ((isc_threadresult_t)0);
+}
+#endif /* USE_WORKER_THREADS */
+
+#ifdef USE_WORKER_THREADS
+static isc_threadresult_t
+#ifdef _WIN32
+WINAPI
+#endif
+run_slow(void *uap) {
 	isc__taskmgr_t *manager = uap;
 
 	XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
 				    ISC_MSG_STARTING, "starting"));
 
-	dispatch(manager);
+	dispatch(manager, isc_taskqueue_slow);
 
 	XTHREADTRACE(isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
 				    ISC_MSG_EXITING, "exiting"));
@@ -1308,7 +1348,8 @@ manager_free(isc__taskmgr_t *manager) {
 
 #ifdef USE_WORKER_THREADS
 	(void)isc_condition_destroy(&manager->exclusive_granted);
-	(void)isc_condition_destroy(&manager->work_available);
+	(void)isc_condition_destroy(&manager->work_available[isc_taskqueue_normal]);
+	(void)isc_condition_destroy(&manager->work_available[isc_taskqueue_slow]);
 	(void)isc_condition_destroy(&manager->paused);
 	isc_mem_free(manager->mctx, manager->threads);
 #endif /* USE_WORKER_THREADS */
@@ -1375,12 +1416,20 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers,
 #ifdef USE_WORKER_THREADS
 	manager->workers = 0;
 	manager->threads = isc_mem_allocate(mctx,
-					    workers * sizeof(isc_thread_t));
+					    2 * workers * sizeof(isc_thread_t));
 	if (manager->threads == NULL) {
 		result = ISC_R_NOMEMORY;
 		goto cleanup_lock;
 	}
-	if (isc_condition_init(&manager->work_available) != ISC_R_SUCCESS) {
+	if (isc_condition_init(&manager->work_available[isc_taskqueue_normal]) != ISC_R_SUCCESS) {
+		UNEXPECTED_ERROR(__FILE__, __LINE__,
+				 "isc_condition_init() %s",
+				 isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
+						ISC_MSG_FAILED, "failed"));
+		result = ISC_R_UNEXPECTED;
+		goto cleanup_threads;
+	}
+	if (isc_condition_init(&manager->work_available[isc_taskqueue_slow]) != ISC_R_SUCCESS) {
 		UNEXPECTED_ERROR(__FILE__, __LINE__,
 				 "isc_condition_init() %s",
 				 isc_msgcat_get(isc_msgcat, ISC_MSGSET_GENERAL,
@@ -1409,8 +1458,10 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers,
 		default_quantum = DEFAULT_DEFAULT_QUANTUM;
 	manager->default_quantum = default_quantum;
 	INIT_LIST(manager->tasks);
-	INIT_LIST(manager->ready_tasks);
-	INIT_LIST(manager->ready_priority_tasks);
+	INIT_LIST(manager->ready_tasks[isc_taskqueue_normal]);
+	INIT_LIST(manager->ready_tasks[isc_taskqueue_slow]);
+	INIT_LIST(manager->ready_priority_tasks[isc_taskqueue_normal]);
+	INIT_LIST(manager->ready_priority_tasks[isc_taskqueue_slow]);
 	manager->tasks_running = 0;
 	manager->exclusive_requested = ISC_FALSE;
 	manager->pause_requested = ISC_FALSE;
@@ -1425,7 +1476,15 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers,
 	 * Start workers.
 	 */
 	for (i = 0; i < workers; i++) {
-		if (isc_thread_create(run, manager,
+		if (isc_thread_create(run_normal, manager,
+				      &manager->threads[manager->workers]) ==
+		    ISC_R_SUCCESS) {
+			manager->workers++;
+			started++;
+		}
+	}
+	for (; i < workers * 2; i++) {
+		if (isc_thread_create(run_slow, manager,
 				      &manager->threads[manager->workers]) ==
 		    ISC_R_SUCCESS) {
 			manager->workers++;
@@ -1438,7 +1497,7 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers,
 		manager_free(manager);
 		return (ISC_R_NOTHREADS);
 	}
-	isc_thread_setconcurrency(workers);
+	isc_thread_setconcurrency(workers * 2);
 #endif /* USE_WORKER_THREADS */
 #ifdef USE_SHARED_MANAGER
 	manager->refs = 1;
@@ -1453,7 +1512,8 @@ isc__taskmgr_create(isc_mem_t *mctx, unsigned int workers,
  cleanup_exclusivegranted:
 	(void)isc_condition_destroy(&manager->exclusive_granted);
  cleanup_workavailable:
-	(void)isc_condition_destroy(&manager->work_available);
+	(void)isc_condition_destroy(&manager->work_available[isc_taskqueue_slow]);
+	(void)isc_condition_destroy(&manager->work_available[isc_taskqueue_normal]);
  cleanup_threads:
 	isc_mem_free(mctx, manager->threads);
  cleanup_lock:
@@ -1538,7 +1598,7 @@ isc__taskmgr_destroy(isc_taskmgr_t **managerp) {
 	     task = NEXT(task, link)) {
 		LOCK(&task->lock);
 		if (task_shutdown(task))
-			push_readyq(manager, task);
+			push_readyq(manager, task, task->qid);
 		UNLOCK(&task->lock);
 	}
 #ifdef USE_WORKER_THREADS
@@ -1547,7 +1607,8 @@ isc__taskmgr_destroy(isc_taskmgr_t **managerp) {
 	 * there's work left to do, and if there are already no tasks left
 	 * it will cause the workers to see manager->exiting.
 	 */
-	BROADCAST(&manager->work_available);
+	BROADCAST(&manager->work_available[isc_taskqueue_normal]);
+	BROADCAST(&manager->work_available[isc_taskqueue_slow]);
 	UNLOCK(&manager->lock);
 
 	/*
@@ -1610,7 +1671,8 @@ isc__taskmgr_ready(isc_taskmgr_t *manager0) {
 		return (ISC_FALSE);
 
 	LOCK(&manager->lock);
-	is_ready = !empty_readyq(manager);
+	is_ready = !empty_readyq(manager, isc_taskqueue_normal) ||
+	           !empty_readyq(manager, isc_taskqueue_slow);
 	UNLOCK(&manager->lock);
 
 	return (is_ready);
@@ -1627,7 +1689,8 @@ isc__taskmgr_dispatch(isc_taskmgr_t *manager0) {
 	if (manager == NULL)
 		return (ISC_R_NOTFOUND);
 
-	dispatch(manager);
+	dispatch(manager, isc_taskqueue_normal);
+	dispatch(manager, isc_taskqueue_slow);
 
 	return (ISC_R_SUCCESS);
 }
@@ -1651,7 +1714,8 @@ isc__taskmgr_resume(isc_taskmgr_t *manager0) {
 	LOCK(&manager->lock);
 	if (manager->pause_requested) {
 		manager->pause_requested = ISC_FALSE;
-		BROADCAST(&manager->work_available);
+		BROADCAST(&manager->work_available[isc_taskqueue_normal]);
+		BROADCAST(&manager->work_available[isc_taskqueue_slow]);
 	}
 	UNLOCK(&manager->lock);
 }
@@ -1724,7 +1788,8 @@ isc__task_endexclusive(isc_task_t *task0) {
 	LOCK(&manager->lock);
 	REQUIRE(manager->exclusive_requested);
 	manager->exclusive_requested = ISC_FALSE;
-	BROADCAST(&manager->work_available);
+	BROADCAST(&manager->work_available[isc_taskqueue_normal]);
+	BROADCAST(&manager->work_available[isc_taskqueue_slow]);
 	UNLOCK(&manager->lock);
 #else
 	UNUSED(task0);
@@ -1750,10 +1815,10 @@ isc__task_setprivilege(isc_task_t *task0, isc_boolean_t priv) {
 
 	LOCK(&manager->lock);
 	if (priv && ISC_LINK_LINKED(task, ready_link))
-		ENQUEUE(manager->ready_priority_tasks, task,
+		ENQUEUE(manager->ready_priority_tasks[task->qid], task,
 			ready_priority_link);
 	else if (!priv && ISC_LINK_LINKED(task, ready_priority_link))
-		DEQUEUE(manager->ready_priority_tasks, task,
+		DEQUEUE(manager->ready_priority_tasks[task->qid], task,
 			ready_priority_link);
 	UNLOCK(&manager->lock);
 }
