fix(controller): reject submissions to closed or unknown queues (bug-1960)

Cause: handle_jobsubmit() never looked at the target queue, so a
submission that bypassed the apiserver's check (libxce, harness
xce_test, or a REST submit without a queue name) was accepted into a
closed queue, an unknown queue, or the reserved lost_and_found queue.

Fix: jobsubmit_check_queue() reads the queue's in-memory state under the
qmgr/queue read locks. handle_jobsubmit() calls it after the field
checks and before the in-flight jobid registry and any WAL append, for
single jobs and job arrays. It replies -E_XCE_QUEUE_CLOSED (new code,
"queue is closed") for a closed queue and -E_XCE_BAD_QUEUE for an
unknown or reserved one. Apply/replay (restore_job) is unchanged, so
committed submit facts for a now-closed queue still apply.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-10 10:38:12 +00:00
parent 387cc99486
commit b239db5fa3
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
7 changed files with 272 additions and 1 deletions

View file

@ -8,6 +8,7 @@
#include <time.h>
#include "cluster.h"
#include "config_repl.h" /* XCE_LOST_AND_FOUND_NAME -- bug-1960 */
#include "controller.capnp.h"
#include "jobj.h"
#include "persist.h"
@ -2608,6 +2609,45 @@ jobsubmit_class_t jobsubmit_classify(uint64_t first_jobid, uint64_t size)
return JOBSUBMIT_OK;
}
/* bug-1960: queue admission for a NEW client submission. Returns 0 when
* `queue` takes new jobs; -E_XCE_BAD_QUEUE when the controller has no such
* queue or it is the reserved recovery queue (jobs reach lost_and_found only
* through WAL replay, never by submission; the apiserver rejects it too);
* -E_XCE_QUEUE_CLOSED when it is closed (LSF semantics: a closed queue takes
* no new submissions, its pending jobs still dispatch -- bug-1938).
*
* Reads in-memory queue state only, under the qmgr and queue read locks
* (the queue_ctrl()/get_queue_exec() lookup pattern), no blocking I/O. It
* belongs to handle_jobsubmit() alone: apply/replay of an already-committed
* WAL_JOB_SUBMIT fact (restore_job) must not consult it, so a job accepted
* before its queue was closed still applies on a standby or after restart. */
static int jobsubmit_check_queue(cluster_t* c, const char* queue)
{
queue_t* q = NULL;
uint32_t status;
if (strcmp(queue, XCE_LOST_AND_FOUND_NAME) == 0)
return -E_XCE_BAD_QUEUE;
lock(c->qmgr, RTTI_LOCK_READ);
key_find(c->qmgr, (void*)queue, (void**)&q);
if (q != NULL) {
reference(q);
}
unlock(c->qmgr);
if (q == NULL)
return -E_XCE_BAD_QUEUE;
lock(q, RTTI_LOCK_READ);
status = q->queue.status;
unlock(q);
dereference(q);
return (status & QUEUE_STAT_CLOSED) ? -E_XCE_QUEUE_CLOSED : 0;
}
int handle_jobsubmit(cluster_t* c, endpoint_t* ep, xce_request_t* request)
{
uint64_t jobid;
@ -2664,6 +2704,29 @@ int handle_jobsubmit(cluster_t* c, endpoint_t* ep, xce_request_t* request)
return -errno;
}
/* bug-1960: reject a submission to a closed, unknown or reserved queue
* before the in-flight registry claims its jobid range and before any
* WAL fact is appended -- nothing is stored, the client gets the
* queue's status alone. Covers the single-job and the array path
* (handle_jobsubmit_array diverges below). */
rc = jobsubmit_check_queue(c, submit->submit->queue);
if (rc < 0) {
char idstr[64];
fmt_jobid(idstr, sizeof(idstr), submit->jobid);
ERROR("jobsubmit: rejected submission %s (%lu elements) to queue "
"'%s': %s",
idstr, (unsigned long)submit->array_size, submit->submit->queue,
(rc == -E_XCE_QUEUE_CLOSED)
? "queue is closed"
: "no such queue or not a submission queue");
reply.status = rc;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
return rc;
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_JOB, HOOK_BEFORE_EVENT,
HOOK_DETAIL_JOB_NEW, c, submit);