feat(job-array-one-based-index): classify every job submission first (task 2.1)

handle_jobsubmit classifies (first_jobid, array_size) before the field
checks, the before-event hook and any registry access: a tagged array
starting at index 0 is answered with the new E_XCE_ARRAY_START0 and an
ERROR log asking for the client to be upgraded (7.1, wins per 7.9);
size 0, tags 2/3, untagged multi-element submissions and arrays whose
last element exceeds 32767 (checked without addition) get -EINVAL.
Nothing is stored for a rejected submission. This also closes a crash:
a size-0 submission used to reach payload building and overflow calloc.
Existing array tests move to tagged block|1 first IDs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-01 09:44:12 +00:00
parent ef728435d4
commit e6c484a0e7
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
11 changed files with 574 additions and 19 deletions

View file

@ -2431,6 +2431,31 @@ static int handle_jobsubmit_array(cluster_t* c, endpoint_t* ep,
return 0;
}
/* job-array-one-based-index (Req 7.1-7.5, 7.9; design "ControllerValidate"):
* classify a submission from its first ID and its wire element count only.
* 7.1 is checked first, so an index-0 array is answered as such whatever
* else is wrong with it (7.9); then 7.4, 7.5, 7.3 and 7.2, all INVALID.
* 7.2 is evaluated as size > 32768 - idx (idx >= 1 here), never as
* idx + size - 1 > 32767, so no 64-bit size can wrap past it. */
jobsubmit_class_t jobsubmit_classify(uint64_t first_jobid, uint64_t size)
{
uint64_t tag = EXTRACT_ID_TAG(first_jobid);
int is_array = is_array_tag(first_jobid);
uint64_t idx = array_idx(first_jobid);
if (is_array && idx == 0) /* 7.1 */
return JOBSUBMIT_INDEX0;
if (size == 0) /* 7.4 */
return JOBSUBMIT_INVALID;
if (tag != 0 && !is_array) /* 7.5 */
return JOBSUBMIT_INVALID;
if (!is_array && size > 1) /* 7.3 */
return JOBSUBMIT_INVALID;
if (is_array && size > (MAX_ARRAY_IDX + 1) - idx) /* 7.2 */
return JOBSUBMIT_INVALID;
return JOBSUBMIT_OK;
}
int handle_jobsubmit(cluster_t* c, endpoint_t* ep, xce_request_t* request)
{
uint64_t jobid;
@ -2444,6 +2469,38 @@ int handle_jobsubmit(cluster_t* c, endpoint_t* ep, xce_request_t* request)
init_reply_by_req(&reply, request);
/* job-array-one-based-index (Req 7.1-7.6, 7.9): classify before the
* field checks, the BEFORE_EVENT hook and any registry/table access, so
* a rejected submission is answered with its class's status alone and
* nothing is stored. */
switch (jobsubmit_classify(submit->jobid, submit->array_size)) {
case JOBSUBMIT_INDEX0: {
char idstr[64];
fmt_jobid(idstr, sizeof(idstr), submit->jobid);
ERROR("jobsubmit: rejected array submission %s (%lu elements) from "
"%s: array element numbers start at 1, this submission starts "
"at 0; the submitting client must be upgraded",
idstr, (unsigned long)submit->array_size,
(request->from != NULL && request->from->name != NULL)
? request->from->name
: "unknown");
reply.status = -E_XCE_ARRAY_START0;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
return -E_XCE_ARRAY_START0;
}
case JOBSUBMIT_INVALID:
reply.status = -EINVAL;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
return -EINVAL;
case JOBSUBMIT_OK:
break;
}
if (NULLSTR(submit->submit->user) || NULLSTR(submit->submit->queue)
|| (submit->submit->start_info == NULL)
|| NULLSTR(submit->submit->start_info->cmdline)) {